Переопределение идентификатора для создания в ActiveRecord

Есть ли способ переопределить значение идентификатора модели для создания? Что-то вроде:

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

будет идеальным, но, очевидно, не будет работать.

Ответ 1

id просто attr_protected, поэтому вы не можете использовать массовое присвоение для его установки. Однако при настройке вручную оно работает:

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

Я не уверен, какова была первоначальная мотивация, но я делаю это при преобразовании моделей ActiveHash в ActiveRecord. ActiveHash позволяет использовать те же атрибуты belongs_to для семантики в ActiveRecord, но вместо миграции и создания таблицы, а также из-за накладных расходов на базу данных при каждом вызове вы просто сохраняете свои данные в yml файлах. Внешние ключи в базе данных ссылаются на идентификаторы in-memory в yml.

ActiveHash отлично подходит для picklists и небольших таблиц, которые редко меняются и меняются только разработчиками. Поэтому при переходе с ActiveHash на ActiveRecord проще всего сохранить все ссылки на внешние ключи.

Ответ 2

Попробуйте

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

который должен дать вам то, что вы ищете.

Ответ 3

Вы также можете использовать что-то вроде этого:

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

Хотя, как указано в docs, это обходит защиту массового присвоения.

Ответ 4

Для Rails 4:

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

Другие ответы Rails 4 сделали для меня не. Многие из них, похоже, изменились при проверке с помощью Rails Console, но когда я проверил значения в базе данных 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, даже если он требует дополнительного запроса к базе данных, потому что он будет работать все время. Это ошибка, которая может не отображаться в вашей среде разработки, но может спокойно испортить вашу производственную базу данных, показывая, что она работает.

Ответ 5

На самом деле получается, что выполнение следующих работ:

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

Ответ 6

Как указывает Джефф, id ведет себя так, как будто attr_protected. Чтобы этого не произошло, вам необходимо переопределить список защищенных по умолчанию атрибутов. Будьте осторожны, делая это везде, где информация об атрибутах может поступать извне. Поле id по умолчанию защищено по какой-либо причине.

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(Протестировано с помощью ActiveRecord 2.3.5)

Ответ 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)

Ответ 8

Post.create!(:title => "Test") { |t| t.id = 10 }

Это не влияет на меня как на то, что вы обычно хотели бы сделать, но оно работает достаточно хорошо, если вам нужно заполнить таблицу фиксированным набором идентификаторов (например, при создании значений по умолчанию с использованием функции rake), и вы хотите переопределить автоинкремент (так что каждый раз, когда вы запускаете задачу, таблица заполняется теми же идентификаторами):

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

Ответ 9

Поместите эту функцию create_with_id вверху вашего seeds.rb, а затем используйте ее для создания вашего объекта, где требуются явные идентификаторы.

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">

Обратные вызовы работают нормально.

Удачи!

Ответ 11

В Rails 4.2.1 с Postgresql 9.5.3, Post.create(:id => 10, :title => 'Test') работает до тех пор, пока не будет строки с id = 10.

Ответ 12

Для 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}`

Ответ 13

вы можете вставить id по sql:

  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