108

有没有办法在创建时覆盖模型的 id 值?就像是:

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

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

4

13 回答 13

124

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 时,最简单的方法是保持所有外键引用相同。

于 2009-10-02T20:55:30.430 回答
31

你也可以使用这样的东西:

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

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

于 2013-06-07T16:19:24.227 回答
30

尝试

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

那应该给你你正在寻找的东西。

于 2009-01-10T20:55:40.083 回答
21

对于 Rails 4:

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

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

至少对于 MySQL,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即使它需要额外的数据库查询,因为它会一直工作。这是一个可能不会出现在您的开发环境中的错误,但可能会悄悄地破坏您的生产数据库,同时说它正在工作。

于 2015-09-09T23:01:04.240 回答
7

我们可以覆盖 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)
于 2010-11-17T03:24:51.653 回答
6

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

p = Post.new(:id => 10, :title => 'Test')
p.save(false)
于 2009-03-31T12:44:41.000 回答
6

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

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(使用 ActiveRecord 2.3.5 测试)

于 2010-01-13T18:43:28.703 回答
6
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
于 2013-03-22T05:49:42.480 回答
2

将此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"})

于 2012-11-29T16:03:44.143 回答
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">

回调工作正常。

祝你好运!。

于 2014-07-27T20:41:25.887 回答
0

对于 Rails 3,最简单的方法是使用new细化without_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}`
于 2015-07-02T17:26:14.373 回答
0

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

于 2016-06-20T20:32:07.307 回答
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
于 2017-11-14T10:33:01.163 回答