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

104

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

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

было бы идеально, но явно не сработает.

Codebeef
источник
1
Многие из этих ответов периодически терпят неудачу в Rails 4 и говорят, что они работают. См. Мой ответ для объяснения.
Рик Смит

Ответы:

116

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

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

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

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


источник
@jkndrkn - Я не понимаю, о чем ты. У меня ActiveRecord::VERSION::STRING == "3.2.11"здесь (с адаптером sqlite3), и все выше работает для меня.
Феликс Рабе
Возможно, то, что я испытал, выразилось только с драйвером mysql.
jkndrkn
@jkndrkn У меня работает с драйвером MySQL для Rails 3.2.18
lulalala
работал у меня. спас мою жизнь. используется на рельсах 4.0.0 / postgres
9.3.5
См. Мой ответ о том, как это сделать на Rails 4.
Рик Смит
30

Пытаться

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

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

П.Дж. Дэвис
источник
2
не уверен, почему вас не голосуют против, это отлично работает для меня
semanticart
Это продолжает работать с activerecord 3.2.11. Ответ, отправленный Джеффом Дином 2 октября 2009 года, больше не работает.
jkndrkn
не работает, похоже, работает только потому, что вызывает p.save, который, вероятно, возвращает false. получит ActiveRecord :: RecordNotFound, если вы запустили p.save!
Алан
29

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

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

Хотя, как указано в документации , это позволит обойти безопасность массового назначения.

Сэмюэл Хини
источник
Хорошая находка, @Samuel Heaney, я могу убедиться, что это отлично работает с activerecord 3.2.13.
mkralla11
У меня тоже сработало; не нужно было включать отдельное поле.
Shalott
2
Увы, это больше не работает на Rails 4, они удалили хэш опций
Хорхе Сампайо
@JorgeSampayo - в Rails 4 вам это не нужно, так как защищенные атрибуты можно удалить в пользу StrongParams.
PinnyM 02
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хотя это требует дополнительного запроса к базе данных, потому что он будет работать все время. Это ошибка, которая может не отображаться в вашей среде разработки, но может незаметно повредить вашу производственную базу данных, все время говоря, что она работает.

Рик Смит
источник
3
Это также был единственный способ заставить его работать в ситуации rails 3.2.22 в консоли. Ответы с вариантами «сохранить» не повлияли.
JosephK
2
Для рельсов 4+ использую:Post.new.update(id: 10, title: 'Test')
Spencer
6

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

p = Post.new(:id => 10, :title => 'Test')
p.save(false)
Codebeef
источник
Хотя это может работать, он также отключает все проверки, что может быть не тем, что предполагалось.
Джордан Моншармон,
Это именно то, что мне нужно для исходных данных, где важны идентификаторы. Спасибо.
JD.
Первоначально я проголосовал за, полагая, что это сработает для исходных данных, как указал @JD, но затем я попробовал это с помощью activerecord 3.2.13, и все еще получаю ошибку «Не удается назначить защищенные атрибуты». Итак, проголосовали против :(
mkralla11
1
К сожалению, это не работает в rails 4, вы получаете NoMethodError: undefined метод `[] 'для false: FalseClass
Хорхе Сампайо
@JorgeSampayo Это все еще работает, если вы проходите validate: false, а не просто false. Однако вы по-прежнему сталкиваетесь с проблемой защищенного атрибута - есть другой способ обойти то, что я изложил в своем ответе.
PinnyM 02
6

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

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(Проверено с ActiveRecord 2.3.5)

Ник Бендерс
источник
6

мы можем переопределить 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)
user510319
источник
5
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
Шон Кэмерон
источник
2

Поместите эту функцию create_with_id в начало файла seed.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"})

Даррен Хикс
источник
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">

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

Удачи!.

CristianOrellanaBak
источник
Мне нужно было создать idметку времени на основе Unix. Я сделал это внутри before_create. Работает отлично.
WM
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}`
PinnyM
источник
Не работает для меня, не {}добавляя атрибуты, как ответ Самуэля выше (Rails3).
Christopher Oezbek
@PinnyM Ваш ответ Rails 4 не работает для меня. idпо-прежнему 10.
Рик Смит
@RickSmith в приведенном примере idбыло передано как 10 - так что это именно то, что должно быть. Если это не то, чего вы ожидали, не могли бы вы пояснить это на примере?
PinnyM
Извините, я хотел сказать, что все еще не 10. См. Мой ответ для объяснения.
Рик Смит
@RickSmith - интересно, эта проблема присуща только MySQL? В любом случае, обычно первичные ключи назначаются напрямую для начальных данных. Если это так, вам, как правило, НЕ следует пытаться вводить значения ниже порога автоинкремента, или вам следует отключить автоинкремент для этого набора команд.
PinnyM
0

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

Никола
источник
0

вы можете вставить идентификатор по 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
Wxianfeng
источник