Sostituzione dell'ID durante la creazione in ActiveRecord


104

C'è un modo per sovrascrivere il valore dell'ID di un modello durante la creazione? Qualcosa di simile a:

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

sarebbe l'ideale, ma ovviamente non funzionerà.


1
Molte di queste risposte falliscono in modo intermittente con Rails 4 e dicono che stanno funzionando. Vedi la mia risposta per una spiegazione.
Rick Smith

Risposte:


116

id è solo attr_protected, motivo per cui non è possibile utilizzare l'assegnazione di massa per impostarlo. Tuttavia, quando lo si imposta manualmente, funziona semplicemente:

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

Non sono sicuro di quale fosse la motivazione originale, ma lo faccio quando converto i modelli ActiveHash in ActiveRecord. ActiveHash ti consente di utilizzare la stessa semantica appartiene_to in ActiveRecord, ma invece di avere una migrazione e creare una tabella e incorrere nel sovraccarico del database a ogni chiamata, devi semplicemente memorizzare i tuoi dati in file yml. Le chiavi esterne nel database fanno riferimento agli ID in memoria nel file yml.

ActiveHash è ottimo per elenchi di selezione e tabelle piccole che cambiano di rado e cambiano solo dagli sviluppatori. Quindi, quando si passa da ActiveHash ad ActiveRecord, è più semplice mantenere uguali tutti i riferimenti alla chiave esterna.


@jkndrkn - Non so cosa intendi. Ho ActiveRecord::VERSION::STRING == "3.2.11"qui (con l'adattatore sqlite3) e quanto sopra funziona per me.
Felix Rabe

Forse quello che ho provato si è espresso solo con il driver mysql.
jkndrkn

@jkndrkn Funziona per me usando il driver MySQL per Rails 3.2.18
lulalala

ha funzionato per me. salvato la mia vita. usato su rotaie 4.0.0 / postgres 9.3.5
allenwlee

Vedi la mia risposta su come farlo su Rails 4.
Rick Smith

30

Provare

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

questo dovrebbe darti quello che stai cercando.


2
Non sono sicuro del motivo per cui stai ricevendo un voto negativo, questo funziona alla grande per me
semanticart

Questo continua a funzionare da activerecord 3.2.11. La risposta pubblicata da Jeff Dean il 2 ottobre 2009 non funziona più.
jkndrkn

non funziona, sembra funzionare solo perché la chiamata a p.save, che probabilmente sta restituendo false. otterrà un ActiveRecord :: RecordNotFound se hai eseguito p.save!
Alan

29

Potresti anche usare qualcosa del genere:

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

Sebbene, come affermato nei documenti , ciò aggirerà la sicurezza dell'assegnazione di massa.


Bella scoperta, @Samuel Heaney, posso verificare che funzioni perfettamente con activerecord 3.2.13.
mkralla11

Ha funzionato anche per me; non era necessario includere un campo separato.
shalott

2
Purtroppo, questo non funziona più su Rails 4, hanno rimosso le opzioni hash
Jorge Sampayo

@JorgeSampayo - in Rails 4 non dovresti averne bisogno poiché gli attributi protetti possono essere rimossi a favore di StrongParams.
PinnyM

21

Per Rails 4:

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

Altre risposte di Rails 4 non hanno funzionato per me. Molti di loro sembravano cambiare durante il controllo usando la Rails Console, ma quando ho controllato i valori nel database MySQL, sono rimasti invariati. Altre risposte funzionavano solo a volte.

Almeno per MySQL, l'assegnazione di un idsotto il numero ID di incremento automatico non funziona a meno che non si utilizzi update_column. Per esempio,

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

Se cambi createper usare new+ saveavrai ancora questo problema. Anche la modifica manuale di idsimili p.id = 10produce questo problema.

In generale, lo userei update_columnper cambiare idanche se costa una query di database extra perché funzionerà tutto il tempo. Questo è un errore che potrebbe non essere visualizzato nel tuo ambiente di sviluppo, ma può danneggiare silenziosamente il tuo database di produzione per tutto il tempo dicendo che funziona.


3
Questo è stato anche l'unico modo per farlo funzionare in una situazione di rails 3.2.22 nella console. Le risposte che utilizzavano variazioni su "salva" non hanno avuto effetto.
JosephK

2
Per rail 4+ utilizzo:Post.new.update(id: 10, title: 'Test')
Spencer

6

In realtà, si scopre che facendo i seguenti lavori:

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

Sebbene possa funzionare, disattiva anche tutte le convalide, il che potrebbe non essere ciò che la richiesta intendeva.
Jordan Moncharmont

Questo è esattamente ciò di cui avevo bisogno per i dati seed in cui gli ID contano. Grazie.
JD.

Inizialmente ho votato in più, pensando che avrebbe funzionato per i dati seed, come ha sottolineato @JD, ma poi l'ho provato con activerecord 3.2.13 e ricevo ancora l'errore "Impossibile assegnare attributi protetti". Quindi, downvoted :(
mkralla11

1
Sfortunatamente, questo non funziona in rails 4, ottieni NoMethodError: metodo undefined `[] 'per false: FalseClass
Jorge Sampayo

@ JorgeSampayo Funziona ancora se passi validate: false, invece che semplicemente false. Tuttavia, ti imbatti ancora nel problema degli attributi protetti: c'è un modo separato per aggirare quello che ho delineato nella mia risposta.
PinnyM

6

Come sottolinea Jeff, id si comporta come se fosse attr_protected. Per evitare ciò, è necessario sovrascrivere l'elenco degli attributi protetti predefiniti. Fai attenzione a farlo ovunque le informazioni sugli attributi possano provenire dall'esterno. Il campo id è protetto per impostazione predefinita per un motivo.

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(Testato con ActiveRecord 2.3.5)


6

possiamo eseguire l'override di 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)

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

Questo non mi colpisce come il genere di cose che normalmente vorresti fare, ma funziona abbastanza bene se devi popolare una tabella con un set fisso di ID (ad esempio quando crei valori predefiniti usando un'attività rake) e tu desidera sovrascrivere l'incremento automatico (in modo che ogni volta che si esegue l'attività la tabella venga popolata con gli stessi ID):

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

2

Metti questa funzione create_with_id nella parte superiore del tuo seed.rb e poi usala per creare il tuo oggetto dove sono desiderati gli id ​​espliciti.

def create_with_id(clazz, params)
obj = clazz.send(:new, params)
obj.id = params[:id]
obj.save!
    obj
end

e usalo in questo modo

create_with_id( Foo, {id:1,name:"My Foo",prop:"My other property"})

invece di usare

Foo.create({id:1,name:"My Foo",prop:"My other property"})


2

Questo caso è un problema simile che è stato necessario sovrascrivere idcon una sorta di data personalizzata:

# 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

E poi :

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

I callback funzionano bene.

In bocca al lupo!.


Avevo bisogno di creare un idtimestamp basato su Unix. L'ho fatto dentro before_create. Funziona bene.
WM

0

Per Rails 3, il modo più semplice per farlo è usare newcon il without_protectionraffinamento, quindi save:

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

Per i dati seed, potrebbe avere senso aggirare la convalida che puoi fare in questo modo:

Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save(validate: false)

In realtà abbiamo aggiunto un metodo di supporto ad ActiveRecord :: Base che viene dichiarato immediatamente prima dell'esecuzione dei file seed:

class ActiveRecord::Base
  def self.seed_create(attributes)
    new(attributes, without_protection: true).save(validate: false)
  end
end

E adesso:

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

Per Rails 4, dovresti usare StrongParams invece di attributi protetti. Se questo è il caso, sarai semplicemente in grado di assegnare e salvare senza passare alcun flag a new:

Post.new(id: 10, title: 'Test').save      # optionally pass `{validate: false}`

Non funziona per me senza inserire attributi {}come risposta di Samuel sopra (Rails3).
Christopher Oezbek

La risposta di @PinnyM Your Rails 4 non funziona per me. idsono ancora 10.
Rick Smith

@RickSmith nell'esempio fornito, è idstato passato come 10, quindi è esattamente quello che dovrebbe essere. Se non è quello che ti aspettavi, puoi chiarire con un esempio?
PinnyM

Scusa, volevo dire ancora non 10. Vedi la mia risposta per una spiegazione.
Rick Smith

@RickSmith - interessante, questo problema è unico per MySQL? In ogni caso, l'uso generale per l'assegnazione diretta delle chiavi primarie è per i dati seed. Se è così, in genere NON dovresti tentare di inserire valori che sono al di sotto della soglia di incremento automatico, oppure dovresti disattivare l'incremento automatico per quel set di comandi.
PinnyM

0

In Rails 4.2.1 con Postgresql 9.5.3, Post.create(:id => 10, :title => 'Test')funziona finché non c'è già una riga con id = 10.


0

puoi inserire id da 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.