【问题标题】:Get ID of Rails Model before saving...?在保存之前获取 Rails 模型的 ID...?
【发布时间】:2010-08-12 11:01:52
【问题描述】:

如何在保存之前获取 rails 模型的 id?

例如,如果我创建了一个新模型实例,如何在保存之前获取它的 ID?

我知道 id 是在保存时根据数据库创建的,但有解决方法吗?

【问题讨论】:

  • 对 Rails 有更多了解的人可以确认这一点,但通常 ID 是由数据库生成的,因此在插入行之前无法知道它是什么。您当然可以自己生成 ID,但这会引发大量蠕虫...

标签: ruby-on-rails


【解决方案1】:

我也在找这个,找到了答案:

假设模型名是“Model”,表名是“models”

模型.rb

before_save {
    next_id=Model.connection.select_value("Select nextval('models_id_seq')")
}

这将输出您的记录在保存时为 id 所采用的值

【讨论】:

  • 我不认为这是thread-safe,这意味着两个并发进程将获取相同的id然后都使用它。
  • 您可以手动递增 seq,这不是一个好习惯,但有线程安全的方法可以实现。
  • 这应该是线程安全的; nextval 递增序列并以原子方式返回新值 (postgresql.org/docs/8.1/static/functions-sequence.html)
  • 这是安全的,before_save 已经在事务中,db nextval 是原子的。
  • before_create 似乎更合适。在您的示例中使用 before_save 会导致序列增加,即使记录已更新。
【解决方案2】:

通常当人们认为他们需要这样做时,他们实际上并不需要这样做。就像约翰说的那样,解释一下你要做什么,有人可能会建议一种方法来做到这一点,而不必事先知道 id。

【讨论】:

  • 没错,这是我正在尝试开发的一个棘手的过程。我应该重新考虑设计...!
  • 能否请您指向一个链接,我可以在其中阅读有关铁路方式的信息?
【解决方案3】:

这与其说是 Rails 问题,不如说是数据库问题。这是一个在任何 Web 应用程序框架中都会出现的问题,并且解决方案在所有地方都是相同的。您必须使用数据库事务。

基本流程将像这样工作。

  • 开启交易
  • 保存您的模型
  • 使用数据库分配的ID
  • 如果事实证明您实际上不想将此模型保留在数据库中,请回滚事务。
  • 如果您想将模型保留在数据库中,请提交事务。

您会从这种方法中注意到的主要事情是,您的 ID 中将存在您回滚事务的间隙。

【讨论】:

    【解决方案4】:

    使用自动递增整数主键的默认 Rails 约定,在保存模型之前无法获取模型的 ID,因为它是在新行插入相关表时由 RDBMS 生成的。

    你真正想解决什么问题?

    【讨论】:

    • 我认为你应该把它作为一个单独的问题发布在这里。
    • 有时在 before_save 而不是 after_save 上做一些需要 id 的事情是有意义的,如果你问得好,99% 的 dbs 会给你它,rails 会允许你把它分配给before_save 上的模型对象。在 before_save 上没有 id 不是 Rails 约定,这是一个很容易绕过的可疑的实现好奇心。
    【解决方案5】:

    大多数时候,当我需要一个 id 时,可以将其分组到一个短列表中。 当创建嵌套关联或关联的连接时。 假设我们有::user 具有 :pets:user_pets 关联,我们将在其中保存它们的类型。

    如果我们有一个正确配置的“has_many: through Association”,我们就可以 User.pets.create(name: "Rex") 但这太简单了,因为我们要创建 :pet 输入 :user_pets

    u = User.create(name: "Cesar")
    u.id # => 1 # works fine
    
    p = u.pets.create(name: 'Rex') 
    # => rails will create UserPets => {id: 1, user_id: 1, pet_id: 1} for us
    
    # But now we have a problem, how do we find :user_pets of our :pet?
    # remember we still need to update the :type, the ugly (wrong) way:
    up = p.user_pets.first
    up.type = 'dog'
    up.save! # working but wrong
    
    # Do you see the problems here? We could use an id
    P = Pet.new( name: "Destroyer" )
    p.id # will not work, as the pet not saved yet to receive an id
    up = UserPet.new( user_id: U.id, pet_id: p.id ) 
    # => UserPet {id: 2, user_id: 1, pet_id: nil} # as we expected there is no id.
    
    # What solutions do we have? Use nested creation!
    # Good
    up = UserPet.new(user_id: u.id, type: "dog")
    # even better
    up = u.user_pets.new(type: "dog") 
    # it's just a shortcut for the code above, 
    # it will add :user_id for us, so let's just remember it.
    
    # Now lets add another 'new' from our creatd 'user_pet'
    p = up.pets.new(name: "Millan")
    user.save!
    # => UserPet: {id: 3, user_id: 1, pet_id: 2, type: 'dog'} # => Pet: {id: 2, name: "Sam"}
    # everything is working! YEY!
    
    # we can even better, than writing in the beginning "User.create", 
    # we can write "User.new" and save it with all the nested elements.
    

    您看到这是如何为我们创建所有 ID 的吗?让我们转向更复杂的事情! 现在我们有一个额外的表:shampoo,它与:user_pet 完全一样,属于:pet:user 我们需要在不知道:user:pet的id的情况下创建它

    u = User.new('Mike')
    up = u.user_pets.new(type: "cat") 
    p = up.pets.new(name: "Rowe")
    
    # But what are we doing now?
    # If we do:
    s = u.shampoos.new(name: "Dirty Job") 
    # => Shampoo: {user_id: 2, pet_id: nil, name: "..."}
    # We get "pet_id: nil", not what we want.
    
    # Or if we do:
    s = p.shampoos.new(name: "Schneewittchen") 
    # => Shampoo: {user_id: nil, pet_id: 3, name: "..."}
    # We get "user_id: nil", in both cases we do not get what we want.
    
    # So we need to get the id of not created record, again.
    # For this we can just do as in the first example (order is not important)
    s = u.shampoos.new(name: "Mission Impossible") 
    # => Shampoo: {id: 3, user_id: 2, pet_id: nil, name: "..."}
    s.pet = p # this will give the missing id, to the shampoo on save.
    # Finish with save of the object:
    u.save! #=> Shampoo: {id: 3, user_id: 2, pet_id: 3, name: '...'} # => Pet: ...
    # Done!
    

    当您需要元素 ID 时,我试图涵盖最常见的原因,以及如何克服这个问题。希望对你有用。

    【讨论】:

      【解决方案6】:

      我不相信有任何解决方法,因为 id 实际上是由数据库本身生成的。在将对象保存到数据库之前,该 ID 不应该可用。

      【讨论】:

        【解决方案7】:

        考虑在创建实例后立即执行您想要的操作。

        after_create do
          print self.id
        end
        

        【讨论】:

        【解决方案8】:

        首先了解数据库的结构。

        • Id 是使用序列生成的
        • 增量为 1(在创建序列时指定)
        • 最后进入数据库的最高值为id

        如果您想获取要保存的记录的id

        然后你可以使用以下:

         1. id = Model.last.id + 1
            model = Model.new(id: id)
            model.save
            # But if data can be delete from that dataabse this will not work correctly.
          
         2. id = Model.connection.select_value("Select nextval('models_id_seq')")
            model = Model.new(id: id)
            model.save
            # Here in this case if you did not specified 'id' while creating new object, record will saved with next id. 
            
            e.g. 
            id
            => 2234
            model = Model.new(id: id) 
            model.save 
            # Record will be created using 'id' as 2234  
            
            model = Model.new()
            model.save
            # Record will be created using next value of 'id' as 2235  
             
        

        希望这会对你有所帮助。

        【讨论】:

          【解决方案9】:

          我在创建数据导入器时遇到了类似的情况。我正在创建一堆不同类型的记录并在保存之前将它们关联起来。保存时,一些记录抛出验证错误,因为它们有一条尚未保存的记录的 validate_presence_of。

          如果您使用的是 postgres,则活动记录会通过在数据库中保存名为 models_id_seq(sales_id_seq 等)的序列来增加它分配给模型的 id。您可以获取此序列中的下一个 id,并使用以下函数将其递增。

          def next_model_id
              ActiveRecord::Base.connection.execute("SELECT NEXTVAL('models_id_seq')").first["nextval"].to_i
          end
          

          但是,此解决方案不是一个好的做法,因为无法保证活动记录将来会以这种方式保留 id 序列。如果它在我的项目中只使用一次,我只会使用它,为我节省了大量工作,并且在为什么不应该经常使用它方面有很好的记录。

          【讨论】:

            【解决方案10】:

            我知道这是一个老问题,但如果有人需要参考它,不妨抛出我的答案

            用户模型

            class User < ActiveRecord::Base
            before_create :set_default_value
            
            def set_default_value
               self.value ||= "#{User.last.id+1}"
            end
            

            【讨论】:

            • 这对各种竞争条件开放。如果保存记录和设置值之间的时间有任何差异,您将在具有合理流量的网站上获得重复。
            • 可以通过验证来缓解
            • Rails 验证不会做任何事情来减轻这种风险,只有数据库唯一性约束才能帮助解决这个问题(并且通常会引发异常)。解释一下,如果到您的数据库的往返时间是 10 毫秒以提取/检查最后一个用户 ID,并且在此时间范围内创建了另一个用户,那么您将有两个相同的值。如果这是一个单用户站点,没有人会在意,但是任何具有远程高流量的站点都是您应该避免的风险。如果你真的需要这种功能,也许看看 postgres 序列或类似的东西?
            • 不考虑竞争条件,User.last 仍然不能保证最高 id,例如当 default_scope 具有 order(:name)
            • 竞赛条件会违反您的登录,因此会完全违反业务流程。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-01-21
            • 1970-01-01
            • 2017-07-31
            • 1970-01-01
            相关资源
            最近更新 更多