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

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

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

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

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

Rick Smith 10.09.2015 19:09
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
105
1
54 475
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Пытаться

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

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

не уверен, почему вас отвергают, это отлично работает для меня

semanticart 15.07.2009 19:51

Это продолжает работать с activerecord 3.2.11. Ответ, отправленный Джеффом Дином 2 октября 2009 года, больше не работает.

jkndrkn 30.01.2013 20:41

не работает, похоже, работает только потому, что вызывает p.save, который, вероятно, возвращает false. получит ActiveRecord :: RecordNotFound, если вы запустили p.save!

Alan 08.10.2014 23:54

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

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

Хотя это может работать, он также отключает все проверки, что может быть не тем, что предполагалось.

Jordan Moncharmont 31.08.2012 21:10

Это именно то, что мне нужно для исходных данных, где важны идентификаторы. Спасибо.

JD. 23.11.2012 08:58

Первоначально я проголосовал за, думая, что это сработает для исходных данных, как указал @JD, но затем я попробовал это с помощью activerecord 3.2.13, и я все еще получаю ошибку «Невозможно назначить защищенные атрибуты». Итак, проголосовали против :(

mkralla11 11.09.2013 19:13

К сожалению, это не работает в rails 4, вы получаете метод NoMethodError: undefined `[] 'для false: FalseClass

Jorge Sampayo 16.05.2014 20:16

@JorgeSampayo Это все еще работает, если вы передадите validate: false, а не просто false. Однако вы по-прежнему сталкиваетесь с проблемой защищенного атрибута - есть другой способ обойти то, что я изложил в своем ответе.

PinnyM 02.07.2015 20:30
Ответ принят как подходящий

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), и все вышесказанное у меня работает.

Felix Rabe 17.02.2013 22:43

Возможно, то, что я испытал, выразилось только с драйвером mysql.

jkndrkn 18.02.2013 23:30

@jkndrkn У меня работает с драйвером MySQL для Rails 3.2.18

lulalala 05.08.2014 06:15

работал у меня. спас мою жизнь. используется на рельсах 4.0.0 / postgres 9.3.5

allenwlee 10.11.2014 18:44

См. мой ответ, чтобы узнать, как это сделать в Rails 4.

Rick Smith 10.06.2016 02:27

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

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(Проверено с ActiveRecord 2.3.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)

Поместите эту функцию 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"})

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

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

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

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

Хорошая находка, @Samuel Heaney, я могу убедиться, что это отлично работает с activerecord 3.2.13.

mkralla11 11.09.2013 19:19

У меня тоже сработало; не нужно было включать отдельное поле.

shalott 23.03.2014 18:56

Увы, на Rails 4 это больше не работает, они убрали хеш опций

Jorge Sampayo 16.05.2014 20:13

@JorgeSampayo - в Rails 4 вам это не нужно, поскольку защищенные атрибуты можно удалить в пользу StrongParams.

PinnyM 02.07.2015 20:32

В этом случае возникла аналогичная проблема, когда необходимо было перезаписать 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. Работает отлично.

W.M. 23.09.2017 23:25

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

Не работает для меня без добавления атрибутов в {}, как ответ Самуэля выше (Rails3).

Christopher Oezbek 28.07.2015 13:41

@PinnyM Ваш ответ Rails 4 не работает для меня. id по-прежнему 10.

Rick Smith 10.09.2015 00:31

@RickSmith в данном примере id был передан как 10 - так что это именно то, что должно быть. Если это не то, чего вы ожидали, не могли бы вы пояснить это на примере?

PinnyM 10.09.2015 15:33

Извините, я все еще хотел сказать нет 10. См. Мой ответ для объяснения.

Rick Smith 10.09.2015 19:07

@RickSmith - интересно, эта проблема присуща только MySQL? В любом случае, обычно первичные ключи назначаются напрямую для начальных данных. В таком случае вам, как правило, НЕ следует пытаться вводить значения ниже порогового значения автоинкремента, или вам следует отключить автоинкремент для этого набора команд.

PinnyM 11.09.2015 18:30

@PinnyM Не уверен, является ли он уникальным для MySQL. Теоретически вы правы в отношении исходных данных, но моя внутренняя паранойя все время говорит мне, что если есть какой-то вариант использования, о котором я не думал, он укусит меня как можно хуже. Может у меня посттравматический стресс?

Rick Smith 11.09.2015 19:46

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

Это также был единственный способ заставить его работать в ситуации rails 3.2.22 в консоли. Ответы с вариантами «сохранить» не повлияли.

JosephK 23.03.2016 16:06

Для рельсов 4+ использую: Post.new.update(id: 10, title: 'Test')

Spencer 03.05.2017 05:09

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

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

Другие вопросы по теме