在ActiveRecord中创建时覆盖ID


104

有什么方法可以在创建时覆盖模型的id值?就像是:

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

会是理想的,但显然行不通。


1
这些答案中的许多答案会在Rails 4中间歇性地失败,并表示它们正在工作。请参阅我的答案以获得解释。
里克·史密斯

Answers:


116

id只是attr_protected,这就是为什么您不能使用质量分配来设置它的原因。但是,当手动设置它时,它将起作用:

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适配器),上面的代码对我有用。
Felix Rabe 2013年

也许我所经历的只是用mysql驱动程序表达了自己。
jkndrkn

@jkndrkn为我使用Rails 3.2.18的MySQL驱动程序为我工作
lulalala 2014年

为我工作。救了我一命。在滑轨4.0.0 / postgres 9.3.5上使用
allenwlee 2014年

请参阅我的答案,了解如何在Rails 4上执行此操作。–
Rick Smith,

30

尝试

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

那应该给你你想要的东西。


2
不知道为什么您会因此而感到失望,这对我来说很有用
语义

从activerecord 3.2.11开始,它继续起作用。Jeff Dean在2009年10月2日发布的答案不再有效。
jkndrkn

不起作用,只是似乎起作用,因为调用p.save可能返回false。如果您运行p.save,将获得ActiveRecord :: RecordNotFound!
艾伦

29

您还可以使用如下所示的内容:

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

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


很好的发现,@ Samuel Heaney,我可以验证它与activerecord 3.2.13完美兼容。
mkralla11 2013年

也为我工作;不必包含一个单独的字段。
shalott 2014年

2
las,这不再适用于Rails 4,他们删除了选项哈希
Jorge Sampayo 2014年

@JorgeSampayo-在Rails 4中,您不需要使用此属性,因为可以删除受保护的属性以使用StrongParams。
PinnyM 2015年

21

对于Rails 4:

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

其他轨道4个回答并没有为我工作。使用Rails Console进行检查时,其中许多似乎已更改,但是当我检查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您仍然会遇到此问题。手动更改id类似内容p.id = 10也会产生此问题。

通常,即使它需要额外的数据库查询,我也会使用它update_column来更改,id因为它将一直有效。此错误可能不会出现在您的开发环境中,但会在说它正在运行的同时悄悄地破坏您的生产数据库。


3
这也是我在控制台中在3.2.22的Rails情况下工作的唯一方法。使用“保存”变体的答案无效。
JosephK

2
对于4+滑轨,我使用:Post.new.update(id: 10, title: 'Test')
Spencer

6

实际上,事实证明,执行以下操作:

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

尽管可能有效,但它也会关闭所有验证,而这可能并非所要求的。
约旦·蒙查蒙特

这正是ID至关重要的种子数据所需要的。谢谢。
京东。

正如@JD所指出的,我最初投票认为是适用于种子数据,但是后来我用activerecord 3.2.13进行了尝试,但仍然收到“无法分配受保护的属性”错误。因此,被
否决了

1
不幸的是,这在rails 4中不起作用,您会得到NoM​​ethodError:值为false的未定义方法“ []”:FalseClass
Jorge Sampayo 2014年

@JorgeSampayo如果通过validate: false,这仍然有效,而不是简单地false。但是,您仍然遇到保护属性的问题-我在回答中概述了一种单独的方法。
PinnyM

6

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

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(使用ActiveRecord 2.3.5测试)


6

我们可以覆盖attribute_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)

5
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

2

将此create_with_id函数放在您seed.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"})


2

这种情况是类似的问题,有必要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">

回调工作正常。

祝好运!。


我需要创建一个id基于Unix的时间戳。我已经在里面做了before_create。工作良好。
WM

0

对于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}`

如果不将属性放入{}上面塞缪尔(Samuel)的答案中,这对我不起作用(Rails3)。
Christopher Oezbek,2015年

@PinnyM您的Rails 4答案对我不起作用。 id仍然是10
瑞克史密斯

在给定的示例中,@ RickSmith id被传递为10-因此,它应该是正确的。如果那不是您所期望的,可以举个例子澄清一下吗?
PinnyM 2015年

抱歉,我的意思仍然不是 10。请参阅我的答案以获取解释。
里克·史密斯

@RickSmith-有趣的是,这个问题是MySQL特有的吗?无论如何,直接分配主键的一般用法是用于种子数据。如果是这样,通常应该不要尝试输入低于自动增量阈值的值,或者应该为该组命令关闭自动增量。
PinnyM 2015年

0

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


0

您可以通过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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.