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


104

Чи є якийсь спосіб змінити значення id моделі при створенні? Щось на зразок:

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

було б ідеально, але, очевидно, не вийде.


1
Багато з цих відповідей періодично не відповідають програмі Rails 4 і стверджують, що вони працюють. Дивіться мою відповідь для пояснення.
Рік Сміт

Відповіді:


116

id просто attr_protected, тому ви не можете використовувати масове призначення для його встановлення. Однак, встановивши його вручну, він просто працює:

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

Я не впевнений, якою була оригінальна мотивація, але я це роблю, перетворюючи моделі ActiveHash в ActiveRecord. ActiveHash дозволяє використовувати ту саму семантику last_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
allenwlee

Дивіться мою відповідь, як це зробити на Rails 4.
Rick Smith

30

Спробуйте

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

це повинно дати вам те, що ви шукаєте.


2
не впевнений, чому ви ставитеся до уваги, це для мене чудово працює
semanticart

Це продовжує діяти як з діючої записи 3.2.11. Відповідь, опублікована Джеффом Діном 2 жовтня 2009 року, вже не працює.
jkndrkn

не працює, здається, працює лише тому, що виклик p.save, який, ймовірно, повертає помилково. отримає ActiveRecord :: RecordNotFound, якщо ви запустили p.save!
Алан

29

Ви також можете використовувати щось подібне:

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

Хоча, як зазначено в документах , це обійде безпеку масового призначення.


Приємна знахідка, @Samuel Heaney, я можу переконатися, що це чудово працює з activerecord 3.2.13.
mkralla11

Працював і для мене; не потрібно було включати окреме поле.
Шалот

2
На жаль, це більше не працює на Rails 4, вони видалили параметри хешу
Хорхе Самппайо

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

21

Для рейок 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
Це також був єдиний спосіб, коли я змусив її працювати в положенні рейки 3.2.22 в консолі. Відповіді з використанням варіантів "зберегти" не мали ефекту.
JosephK

2
Для рейок 4+ я використовую:Post.new.update(id: 10, title: 'Test')
Спенсер

6

Власне, виявляється, що виконуючи такі роботи:

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

Хоча це може працювати, він також вимикає всі перевірки, що може бути не тим, що запитував.
Jordan Moncharmont

Це саме те, що мені потрібно для даних насіння, де важливі ідентифікатори. Дякую.
JD.

Я спочатку схвалював, думаючи, що це буде працювати з насіннєвими даними, як вказував @JD, але потім я спробував це з activerecord 3.2.13, і я все одно отримую помилку "Не можу призначити захищені атрибути". Отже, знищено :(
mkralla11

1
На жаль, це не працює в рейки 4, ви отримуєте NoMethodError: undefined method `[] 'для false: FalseClass
Jorge Sampayo

@JorgeSampayo Це все ще працює, якщо ви проходите validate: false, а не просто false. Однак ви все ще стикаєтесь із проблемою захищеного атрибуту - існує окремий шлях, який я окреслив у своїй відповіді.
PinnyM

6

Як зазначає Джефф, id поводиться так, ніби захищеноtrtr_protected. Щоб цього не допустити, вам потрібно змінити список захищених за замовчуванням атрибутів. Будьте обережні, роблячи це де завгодно, щоб інформація про атрибути могла надходити ззовні. Поле id за замовчуванням захищено з причини.

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(Тестовано з ActiveRecord 2.3.5)


6

ми можемо замінити атрибути_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 }

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

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

Відкликання дзвінків працює чудово.

Щасти!.


Мені потрібно було створити на 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}`

Це не працює для мене без введення атрибутів у {}відповідь Самуїла вище (Rails3).
Крістофер Йозбек

@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

ви можете вставити 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.