【问题标题】:Overriding id on create in ActiveRecord在 ActiveRecord 中创建时覆盖 id
【发布时间】:2009-01-10 19:44:57
【问题描述】:

有没有办法在创建时覆盖模型的 id 值?比如:

Post.create(:id => 10, :title => 'Test')

会很理想,但显然行不通。

【问题讨论】:

  • 这些答案中的许多将在 Rails 4 中间歇性地失败,并说它们正在工作。有关说明,请参阅 my answer

标签: ruby-on-rails activerecord


【解决方案1】:

id 只是 attr_protected,这就是为什么你不能使用 mass-assignment 来设置它。但是,手动设置时,它就可以工作:

o = SomeObject.new
o.id = 8888
o.save!
o.reload.id # => 8888

我不确定最初的动机是什么,但我在将 ActiveHash 模型转换为 ActiveRecord 时会这样做。 ActiveHash 允许您在 ActiveRecord 中使用相同的 belongs_to 语义,但无需迁移和创建表,并且每次调用都会产生数据库开销,您只需将数据存储在 yml 文件中。数据库中的外键引用 yml 中的内存 id。

ActiveHash 非常适合不经常更改且仅由开发人员更改的选项列表和小表格。因此,从 ActiveHash 到 ActiveRecord 时,最简单的方法是保持所有外键引用相同。

【讨论】:

  • @jkndrkn - 我不知道你的意思。我在这里有ActiveRecord::VERSION::STRING == "3.2.11"(使用 sqlite3 适配器),以上对我有用。
  • 也许我所经历的只是用mysql驱动来表达。
  • @jkndrkn 为我使用 Rails 3.2.18 的 MySQL 驱动程序
  • 为我工作。救了我。用于 rails 4.0.0 / postgres 9.3.5
  • 请参阅my answer,了解如何在 Rails 4 上执行此操作。
【解决方案2】:

你也可以这样使用:

Post.create({:id => 10, :title => 'Test'}, :without_protection => true)

尽管如 docs 中所述,这将绕过批量分配安全性。

【讨论】:

  • 很好的发现,@Samuel Heaney,我可以验证这与 activerecord 3.2.13 完美配合。
  • 也为我工作;没有必要包含一个单独的字段。
  • 唉,这不再适用于 Rails 4,他们删除了选项哈希
  • @JorgeSampayo - 在 Rails 4 中你不应该需要这个,因为可以删除受保护的属性以支持 StrongParams。
【解决方案3】:

试试

a_post = Post.new do |p| 
  p.id = 10
  p.title = 'Test'
  p.save
end

这应该可以满足您的需求。

【讨论】:

  • 不知道为什么你会被否决,这对我很有用
  • 这从 activerecord 3.2.11 开始继续工作。 Jeff Dean 于 2009 年 10 月 2 日发布的答案不再有效。
  • 不起作用,似乎只是因为调用 p.save 才起作用,这可能返回 false。如果你运行 p.save,将会得到一个 ActiveRecord::RecordNotFound!
【解决方案4】:

对于 Rails 4:

Post.create(:title => 'Test').update_column(:id, 10)

Other Rails 4 answers 对我有用。在使用 Rails 控制台进行检查时,其中许多似乎发生了变化,但是当我检查 MySQL 数据库中的值时,它们保持不变。其他答案有时才有效。

至少对于 MySQL,在自动增量 ID 编号下方分配一个id不起作用,除非您使用 update_column。例如,

p = Post.create(:title => 'Test')
p.id
=> 20 # 20 was the id the auto increment gave it

p2 = Post.create(:id => 40, :title => 'Test')
p2.id
=> 40 # 40 > the next auto increment id (21) so allow it

p3 = Post.create(:id => 10, :title => 'Test')
p3.id
=> 10 # Go check your database, it may say 41.
# Assigning an id to a number below the next auto generated id will not update the db

如果您将create 更改为使用new + save,您仍然会遇到此问题。像p.id = 10 这样手动更改id 也会产生这个问题。

一般来说,我会使用update_column 来更改id,即使它需要额外的数据库查询,因为它会一直工作。这是一个可能不会出现在您的开发环境中的错误,但可能会悄悄地破坏您的生产数据库,同时说它正在工作。

【讨论】:

  • 这也是我让它在控制台中的 rails 3.2.22 情况下工作的唯一方法。使用“保存”变体的答案没有效果。
  • 对于 rails 4+ 我使用:Post.new.update(id: 10, title: 'Test')
【解决方案5】:

我们可以覆盖 attributes_protected_by_default

class Example < ActiveRecord::Base
    def self.attributes_protected_by_default
        # default is ["id", "type"]
        ["type"]
    end
end

e = Example.new(:id => 10000)

【讨论】:

    【解决方案6】:

    实际上,事实证明,做以下工作:

    p = Post.new(:id => 10, :title => 'Test')
    p.save(false)
    

    【讨论】:

    • 虽然它可能有效,但它也会关闭所有验证,这可能不是所要求的。
    • 这正是我对 ID 很重要的种子数据所需要的。谢谢。
    • 我最初赞成,认为这适用于种子数据,正如@JD 指出的那样,但后来我用 activerecord 3.2.13 尝试了它,但我仍然得到“无法分配受保护的属性”错误.所以,投反对票:(
    • 不幸的是,这在 rails 4 中不起作用,你会得到 NoMethodError: undefined method `[]' for false:FalseClass
    • @JorgeSampayo 如果你通过validate: false,而不是简单的false,这仍然有效。但是,您仍然遇到受保护的属性问题 - 我在回答中概述了另一种解决方法。
    【解决方案7】:

    正如 Jeff 指出的那样,id 的行为就像是 attr_protected。为防止这种情况,您需要覆盖默认受保护属性的列表。在属性信息可能来自外部的任何地方都要小心。 id 字段默认受保护是有原因的。

    class Post < ActiveRecord::Base
    
      private
    
      def attributes_protected_by_default
        []
      end
    end
    

    (使用 ActiveRecord 2.3.5 测试)

    【讨论】:

      【解决方案8】:
      Post.create!(:title => "Test") { |t| t.id = 10 }
      

      这并不像你通常想要做的那样让我印象深刻,但如果你需要用一组固定的 id 填充表(例如使用 rake 任务创建默认值时),它工作得很好) 并且您想要覆盖自动递增(这样每次您运行任务时,表都会填充相同的 id):

      post_types.each_with_index do |post_type|
        PostType.create!(:name => post_type) { |t| t.id = i + 1 }
      end
      

      【讨论】:

        【解决方案9】:

        把这个 create_with_id 函数放在你的种子.rb 的顶部,然后用它在需要显式 ID 的地方创建对象。

        def create_with_id(clazz, params)
        obj = clazz.send(:new, params)
        obj.id = params[:id]
        obj.save!
            obj
        end
        

        并像这样使用它

        create_with_id( Foo, {id:1,name:"My Foo",prop:"My other property"})
        

        而不是使用

        Foo.create({id:1,name:"My Foo",prop:"My other property"})

        【讨论】:

          【解决方案10】:

          这个案例是一个类似的问题,需要用一种自定义日期覆盖id

          # in app/models/calendar_block_group.rb
          class CalendarBlockGroup < ActiveRecord::Base
          ...
           before_validation :parse_id
          
           def parse_id
              self.id = self.date.strftime('%d%m%Y')
           end
          ...
          end
          

          然后:

          CalendarBlockGroup.create!(:date => Date.today)
          # => #<CalendarBlockGroup id: 27072014, date: "2014-07-27", created_at: "2014-07-27 20:41:49", updated_at: "2014-07-27 20:41:49">
          

          回调工作正常。

          祝你好运!

          【讨论】:

          • 我需要创建一个基于 Unix 时间戳的id。我已经在before_create 内做到了。工作正常。
          【解决方案11】:

          对于 Rails 3,最简单的方法是使用 newwithout_protection 细化,然后使用 save

          Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save
          

          对于种子数据,绕过验证可能有意义,您可以这样做:

          Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save(validate: false)
          

          我们实际上已经向 ActiveRecord::Base 添加了一个辅助方法,该方法在执行种子文件之前立即声明:

          class ActiveRecord::Base
            def self.seed_create(attributes)
              new(attributes, without_protection: true).save(validate: false)
            end
          end
          

          现在:

          Post.seed_create(:id => 10, :title => 'Test')
          

          对于 Rails 4,您应该使用 StrongParams 而不是受保护的属性。如果是这种情况,您无需将任何标志传递给new,就可以简单地分配和保存:

          Post.new(id: 10, title: 'Test').save      # optionally pass `{validate: false}`
          

          【讨论】:

          • 如果不将属性放入{} 作为上面塞缪尔的回答(Rails3),对我不起作用。
          • @PinnyM 您的 Rails 4 答案对我不起作用。 id 仍然是 10。
          • @RickSmith 在给出的示例中,id 被传递为 10 - 所以这正是它应该是的。如果这不是您所期望的,您能举个例子说明一下吗?
          • 对不起,我的意思是仍然 not 10 。请参阅我的答案以获得解释。
          • @RickSmith - 有趣的是,这个问题是 MySQL 独有的吗?无论如何,直接分配主键的一般用途是用于种子数据。如果是这样,您通常不应尝试输入低于自动增量阈值的值,或者您应该关闭该组命令的自动增量。
          【解决方案12】:

          在带有 Postgresql 9.5.3 的 Rails 4.2.1 中,只要没有 id = 10 的行,Post.create(:id =&gt; 10, :title =&gt; 'Test') 就可以工作。

          【讨论】:

            【解决方案13】:

            你可以通过sql插入id:

              arr = record_line.strip.split(",")
              sql = "insert into records(id, created_at, updated_at, count, type_id, cycle, date) values(#{arr[0]},#{arr[1]},#{arr[2]},#{arr[3]},#{arr[4]},#{arr[5]},#{arr[6]})"
              ActiveRecord::Base.connection.execute sql
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2015-07-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-10-25
              相关资源
              最近更新 更多