Rails 4.唯一ID的计数器

时间:2014-11-15 06:36:59

标签: ruby-on-rails ruby postgresql rails-activerecord uniqueidentifier

我需要为我的模型生成一个唯一的数字,称为Shipment。 该号码必须采用以下格式:

  • 两位数 - 当月
  • 两位数 - 当年
  • 六位数 - 只是数字

例如:1114000001。11 - 当前月份。 14年。 0000001 - 只有六位数。

因此。我的想法是使用6位数的计数器。我想将它存储在数据库中,然后我可以使用lock并确保唯一性。但很明显,这是一个潜在的瓶颈。因为每个新的shipment都会排队等候访问counter

我的问题是:还有什么更好的?使用计数器或生成随机数,并查询数据库:

while Shipment.where(:id => supposedly_unique_number.).exists?

还有一个问题:我在哪里存储这个ounter?我只需要一个用于整个应用程序。仅为一个值创建独立表?

更新

我刚才意识到这个检查 Shipment.where(:id => supposedly_unique_number.).exists? 不保证唯一性。 :)

那么哪里可以更好地存放柜台? 我很高兴你的任何建议。

UPDATE2

mu太短,提到了数据库序列。 而且我找不到可以理解的信息如何使用它。 如果有人向我解释它是如何运作的,我将不胜感激。

Rails 4,Ruby 2,PostgreSql。

1 个答案:

答案 0 :(得分:1)

这里有几件事你需要考虑:

  1. 您的id将有前导零,因此它们不是数字,它们是包含数字字符的字符串。
  2. 您应该让数据库负责计数器。你在Ruby中尝试做的任何事情都将是一个令人讨厌的混乱和竞争条件。同样,您应该让数据库负责构建id
  3. 使用id的字符串是一项工作,以防止ActiveRecord认为它拥有你但不是那么糟糕。您需要创建没有id添加的隐式create_table的表,添加您自己的id,并将其设置为主键:

    create_table :shipments, :id => false do |t|
      # Don't bother with `t.string` when using PostgreSQL, it is pointless.
      t.text :id, :null => false
      ...
    end
    connection.execute('alter table shipments add primary key (id)')
    

    现在我们需要连接一个默认值,以便数据库可以生成我们的id。获取日期前缀很简单,只需使用to_char SQL function格式化当前时间(now()):

    to_char(now(), 'MMYY')
    

    要创建后缀,我们可以使用sequence。数据库序列只是数据库对象,它在存在多个数据库连接时以安全的方式返回递增的数字。首先,创建序列:

    connection.execute('create sequence shipments_id_seq owned by shipments.id')
    

    要从序列中获取下一个值,我们使用nextval SQL function

      

    nextval(regclass)
      提前序列并返回新值

    再次使用to_char对其进行格式化:

    to_char(nextval('shipments_id_seq'), 'FM000000')
    

    linked document解释了FM000000格式的含义。合并id的这两部分会给我们一个默认值:

    to_char(now(), 'MMYY') || to_char(nextval('shipments_id_seq'), 'FM000000')
    

    请注意||是SQL中的字符串连接。将其包含在另一个connection.execute中,我们可以将默认值附加到id列:

    connection.execute(%q{
      alter table shipments
      alter column id
      set default to_char(now(), 'MMYY') || to_char(nextval('shipments_id_seq'), 'FM000000')
    })
    

    如果您想在每个月初重置计数器(或接近六位数限制时),您可以使用setval SQL function

    最后,由于您使用的是ActiveRecord无法理解的各种内容,因此您需要从schema.rb切换到structure.sql来管理架构信息。您可以通过设置:

    在配置中执行此操作
    config.active_record.schema_format = :sql
    

    您将使用rake db:structure:dumpdb:structure:load代替架构任务。


    将所有这些放在一起将为您提供这样的迁移:

    def up
      create_table :shipments, :id => false do |t|
        # Don't bother with `t.string` when using PostgreSQL, it is pointless.
        t.text :id, :null => false
        ...
      end
      connection.execute('alter table shipments add primary key (id)')
      connection.execute('create sequence shipments_id_seq owned by shipments.id')
      connection.execute(%q{
        alter table shipments
        alter column id
        set default to_char(now(), 'MMYY') || to_char(nextval('shipments_id_seq'), 'FM000000')
      })
    end