新手问题 如何生成一个连续的数字作为 Model 的一列?

evil850209 · 2013年04月04日 · 最后由 ashchan 回复于 2013年04月07日 · 4074 次阅读

想写一个 model,有一列是 number,这列需要自动生成值就像主键一样。如何利用 rails 的特性自动生成一个连续的 number?也可以和主键值一样,只不过是一个 string 类型的列。

例如我存入一个 model,id 会是 1,此时 number 也会存入 00001.当存入第二个 model 的时候 id 是 2 而 number 自动存入 00002

谢谢各位。

auto_increment

不会。反正不能取数据库里面最后一条数据,然后加一保存,同时 2 个并发就会出现重复。

或者直接用 id,用的时候转换成 string,或者直接做个虚拟字段(应该叫虚拟属性还是啥滴),里面把 id 根据逻辑要求转换成对应的字符。

同一楼,数据库本身应该有解决方案哈

搞个 after_create :generate_number 来根据 id 生成 number。

@zlx_star 这个好像真不行吧,after_create 时 id 还没有生成呢

class App < ActiveRecord::Base

  after_save :generate_number

  private

  def generate_number
    if self.number.nil?
      self.number = self.id.to_s
      self.save
    end
  end
end
class CreateNumberings < ActiveRecord::Migration
  def self.up
    create_table :numberings do |t|
      t.column :name,:string  #名称
      t.column :types,:string  #类型
      t.column :template,:string,:default=>'' # 模板
      t.text :note
    end
  end

  def self.down
    drop_table :numberings
  end
end
class Numbering < ActiveRecord::Base

  #根据类型产生一个新的编码,同时保存到数据库中
  def self.new_number!(type)
    numbering = self.get_numbering(type)
    number = numbering.template.next_number
    numbering.update_attributes(:template=> number)
    number
  end

  #  #根据类型产生一个新的编码
  def self.new_number(type)
    numbering = self.get_numbering(type)
    numbering.template.next_number
  end

  private
  def self.get_numbering(type)
    numbering = Numbering.find_or_create_by_types(type)
    if numbering.template.blank?
#      model = type.to_s.singularize.camelize.constantize
      model = type.to_s.camelize.constantize
      numbering.update_attributes(:name=>model.class_name,:template=>"#{type}00000")
    end
    numbering
  end
end

class NilClass
  def next_number(qty=1)
  end

  def -(number)
  end

  def region_to(qty=1)    
  end

  def region_sizeof(number)
  end
end

class String
  #得到字符串的下一个编号,是对字符串中数值区域的值的增加,
  #如果该字符串没有数值区域,那么返回原字符串
  #例如
  # 'abc'.next_number         -> abc
  # 'abc100'.next_number      ->abc101
  # '100'.next_number         ->101
  # '100abc'.next_number      ->101abc
  # '0100abc'.next_number     ->0101abc
  # '0099abc'.next_number     ->0100abc
  # "abc100".next_number(10)  ->"abc110"
  # "100abc".next_number(10)  ->"110abc"
  # "100abc100".next_number(10)  ->"100abc110"
  def next_number(qty=1)
    self.reverse.sub(/(0*)\d+/) { |match| (match.reverse.to_i+qty.to_i).to_s.rjust(match.length,"0").reverse }.reverse      
  end

  #两个字符串相减,得到两个字符串之间相减的数量
  #例如:
  #  "abc"-"abc"           ->0
  #  "abc110"-"abc100"     ->10
  #  "110abc"-"100abc"     ->10
  def -(number)
    match1 = /\d+/.match(self)
    match2 = /\d+/.match(number)
    (match1 && match2) ? match1[0].to_i - match2[0].to_i : 0
  end

  #从当前区段得到下一区段,区段的间隔由qty指定,区段包括首尾部分
  #例如:
  # "a100".region_to(1)      ->"a100"
  # "a100".region_to(2)   ->"a101"
  def region_to(qty=1)    
    raise "参数不允许为0" if qty==0
    next_number(qty<0? qty+1:  qty-1)
  end
  #计算两个区段之间的大小,区段是包含首尾部分
  #例如:
  # "a100".region_sizeof("a100")    ->1
  # "a100".region_sizeof("a101")    ->2
  def region_sizeof(number)
    (self-number).abs+1
  end
end

上面 3 个代码块是核心模块了,哎,5 年前的东西了。杨总的结晶

#7 楼 @badboy 楼主的需求似乎没有这么复杂吧 o_O

@blacktulip 嗯,他的需求还很初创。不过我发的是我们 5 年前的实现,包含他的需求。

#5 楼 @evil850209

class Name < ActiveRecord::Base
  attr_accessible :blog

  before_validation :show_id
  after_validation :show_id
  before_save :show_id
  before_create :show_id
  after_create :show_id
  after_save :show_id

  private
  def show_id
    p self.id
  end
end
$ spring rails runner 'Name.create'
nil
nil
nil
nil
4
4

按照 http://guides.rubyonrails.org/active_record_validations_callbacks.html#available-callbacks 的 callbacks 顺序,在 save 之后就生成了 id。包括 around_createaround_save 的后半段代码。

#9 楼 @badboy 五年前用的是 ruby 多少版本?

#7 楼 @badboy 这个能保证 2 个同时并发时,数据不重复?

伪代码,保证数据一致性

事务 do
  object = Table.save
  Table.update(:number, object.id)
end
  1. 数据库必须加唯一索引,并发情况 Rails 挡不住
  2. 在 id 里面随机数,存之前检查是否重复,万一重复数据库唯一索引报错

@allenwei 如果是索引报错,还要手动去处理对吧。没有现成的 gem 吗?

#15 楼 @evil850209 6 楼的方案不行吗?该方案基本是针对你提出的针对主键 id 生成相应 string 的实现。

@ashchan 经测试,@blacktulip 的方案可以。谢谢各位 :)

#17 楼 @evil850209 嗯,@blacktulip 的方案可以稍微精简一下,并按你要求的 string 格式来:

def generate_number
  update_attribute(:number, "%05d" % id) unless number
end
需要 登录 后方可回复, 如果你还没有账号请 注册新账号