Überschreiben der ID beim Erstellen in ActiveRecord

104

Gibt es eine Möglichkeit, den ID-Wert eines Modells beim Erstellen zu überschreiben? Etwas wie:

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

wäre ideal, wird aber offensichtlich nicht funktionieren.

Codebeef
quelle
1
Viele dieser Antworten schlagen mit Rails 4 zeitweise fehl und sagen, dass sie funktionieren. Siehe meine Antwort für eine Erklärung.
Rick Smith

Antworten:

116

id ist nur attr_protected, weshalb Sie die Massenzuweisung nicht verwenden können, um sie festzulegen. Wenn Sie es jedoch manuell einstellen, funktioniert es einfach:

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

Ich bin mir nicht sicher, was die ursprüngliche Motivation war, aber ich mache das, wenn ich ActiveHash-Modelle in ActiveRecord konvertiere. Mit ActiveHash können Sie in ActiveRecord dieselbe Semantik zur Zugehörigkeit verwenden. Anstatt jedoch eine Migration durchzuführen und eine Tabelle zu erstellen und bei jedem Aufruf den Overhead der Datenbank zu verursachen, speichern Sie Ihre Daten einfach in yml-Dateien. Die Fremdschlüssel in der Datenbank verweisen auf die speicherinternen IDs in der yml.

ActiveHash eignet sich hervorragend für Auswahllisten und kleine Tabellen, die sich nur selten und nur von Entwicklern ändern. Wenn Sie also von ActiveHash zu ActiveRecord wechseln, ist es am einfachsten, alle Fremdschlüsselreferenzen gleich zu halten.


quelle
@jkndrkn - Ich weiß nicht was du meinst. Ich bin ActiveRecord::VERSION::STRING == "3.2.11"hier (mit dem sqlite3-Adapter) und das oben genannte funktioniert für mich.
Felix Rabe
Vielleicht hat sich das, was ich erlebt habe, nur mit dem MySQL-Treiber ausgedrückt.
jkndrkn
@jkndrkn Funktioniert für mich mit MySQL-Treiber für Rails 3.2.18
Lulalala
arbeitete für mich. hat mein Leben gerettet. verwendet auf Schienen 4.0.0 / postgres 9.3.5
Allenwlee
Siehe meine Antwort, wie dies auf Schienen 4 zu tun ist.
Rick Smith
30

Versuchen

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

das sollte dir geben, wonach du suchst.

PJ Davis
quelle
2
Ich bin mir nicht sicher, warum du herabgestimmt wirst. Das funktioniert großartig für mich
Semanticart
Dies funktioniert ab Activerecord 3.2.11 weiter. Die Antwort von Jeff Dean am 2. Oktober 2009 funktioniert nicht mehr.
jkndrkn
funktioniert nicht, scheint nur zu funktionieren, weil p.save aufgerufen wird, was wahrscheinlich false zurückgibt. wird ein ActiveRecord :: RecordNotFound erhalten, wenn Sie p.save ausgeführt haben!
Alan
29

Sie könnten auch so etwas verwenden:

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

Obwohl dies in den Dokumenten angegeben ist , wird dadurch die Sicherheit der Massenzuweisung umgangen.

Samuel Heaney
quelle
Netter Fund, @Samuel Heaney, ich kann überprüfen, ob dies mit activerecord 3.2.13 perfekt funktioniert.
mkralla11
Arbeitete auch für mich; Es war nicht erforderlich, ein separates Feld einzuschließen.
Shalott
2
Leider funktioniert dies nicht mehr auf Rails 4, sie haben den Options-Hash entfernt
Jorge Sampayo
@JorgeSampayo - In Rails 4 sollten Sie dies nicht benötigen, da die geschützten Attribute zugunsten von StrongParams entfernt werden können.
PinnyM
21

Für Schienen 4:

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

Andere Rails 4-Antworten haben bei mir nicht funktioniert. Viele von ihnen schienen sich bei der Überprüfung mit der Rails-Konsole zu ändern, aber als ich die Werte in der MySQL-Datenbank überprüfte, blieben sie unverändert. Andere Antworten funktionierten nur manchmal.

Zumindest für MySQL idfunktioniert das Zuweisen einer ID-Nummer unter der automatischen Inkrementierung nur , wenn Sie sie verwenden update_column. Beispielsweise,

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

Wenn Sie createzu new+ wechseln , tritt savedieses Problem weiterhin auf. Manuelles Ändern der idArtp.id = 10 erzeugt ebenfalls dieses Problem.

Im Allgemeinen würde ich das update_columnändern id, obwohl es eine zusätzliche Datenbankabfrage kostet, da es die ganze Zeit funktioniert. Dies ist ein Fehler, der möglicherweise nicht in Ihrer Entwicklungsumgebung angezeigt wird, aber Ihre Produktionsdatenbank stillschweigend beschädigen kann, während Sie sagen, dass sie funktioniert.

Rick Smith
quelle
3
Dies war auch die einzige Möglichkeit, es in einer Rails 3.2.22-Situation in der Konsole zum Laufen zu bringen. Die Antworten mit Variationen von 'Speichern' hatten keine Auswirkung.
JosephK
2
Für Schienen 4+ benutze ich:Post.new.update(id: 10, title: 'Test')
Spencer
6

Tatsächlich stellt sich heraus, dass Folgendes funktioniert:

p = Post.new(:id => 10, :title => 'Test')
p.save(false)
Codebeef
quelle
Während es möglicherweise funktioniert, werden auch alle Validierungen deaktiviert, was möglicherweise nicht den beabsichtigten Absichten entspricht.
Jordan Moncharmont
Dies ist genau das, was ich für Seed-Daten brauchte, bei denen IDs wichtig sind. Danke dir.
JD.
Ich habe ursprünglich upvoted, weil ich dachte, dass dies für Seed-Daten funktionieren würde, wie @JD hervorhob, aber dann habe ich es mit activerecord 3.2.13 versucht und erhalte immer noch den Fehler "Geschützte Attribute können nicht zugewiesen werden". Also, downvoted :(
mkralla11
1
Leider funktioniert dies nicht in Rails 4, Sie erhalten NoMethodError: undefinierte Methode `[] 'für false: FalseClass
Jorge Sampayo
@JorgeSampayo Dies funktioniert immer noch, wenn Sie bestehen validate: false, anstatt einfach false. Sie stoßen jedoch immer noch auf das Problem mit geschützten Attributen - es gibt einen separaten Weg, um das zu umgehen, was ich in meiner Antwort beschrieben habe.
PinnyM
6

Wie Jeff betont, verhält sich id so, als wäre es attr_protected. Um dies zu verhindern, müssen Sie die Liste der standardmäßig geschützten Attribute überschreiben. Seien Sie überall dort vorsichtig, wo Attributinformationen von außen kommen können. Das ID-Feld ist aus einem bestimmten Grund standardmäßig geschützt.

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(Getestet mit ActiveRecord 2.3.5)

Nic Benders
quelle
6

Wir können Attribute_protected_by_default überschreiben

class Example < ActiveRecord::Base
    def self.attributes_protected_by_default
        # default is ["id", "type"]
        ["type"]
    end
end

e = Example.new(:id => 10000)
user510319
quelle
5
Post.create!(:title => "Test") { |t| t.id = 10 }

Dies scheint mir nicht das zu sein, was Sie normalerweise tun möchten, aber es funktioniert ganz gut, wenn Sie eine Tabelle mit einem festen Satz von IDs füllen müssen (z. B. wenn Sie Standardeinstellungen mit einer Rechenaufgabe erstellen) und Sie Sie möchten die automatische Inkrementierung überschreiben (damit die Tabelle jedes Mal, wenn Sie die Aufgabe ausführen, mit denselben IDs gefüllt wird):

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

Fügen Sie diese Funktion create_with_id oben in Ihre seeds.rb ein und verwenden Sie sie dann, um Ihre Objekterstellung durchzuführen, wenn explizite IDs gewünscht werden.

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

und benutze es so

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

anstatt zu verwenden

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

Darren Hicks
quelle
2

Dieser Fall ist ein ähnliches Problem, das erforderlich war, um das idmit einer Art benutzerdefiniertem Datum zu überschreiben :

# 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

Und dann :

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

Rückrufe funktionieren gut.

Viel Glück!.

CristianOrellanaBak
quelle
Ich musste einen idauf Unix basierenden Zeitstempel erstellen . Ich habe das drinnen gemacht before_create. Funktioniert gut.
WM
0

Für Rails 3 ist der einfachste Weg, dies zu tun, die Verwendung newmit der without_protectionVerfeinerung und dann save:

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

Für Seed-Daten kann es sinnvoll sein, die Validierung zu umgehen, was Sie folgendermaßen tun können:

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

Wir haben ActiveRecord :: Base tatsächlich eine Hilfsmethode hinzugefügt, die unmittelbar vor der Ausführung von Seed-Dateien deklariert wird:

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

Und nun:

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

Für Rails 4 sollten Sie StrongParams anstelle von geschützten Attributen verwenden. In diesem Fall können Sie einfach zuweisen und speichern, ohne Flags an newfolgende Adresse zu übergeben :

Post.new(id: 10, title: 'Test').save      # optionally pass `{validate: false}`
PinnyM
quelle
Funktioniert bei mir nicht, ohne Attribute {}wie Samuels Antwort oben (Rails3) einzugeben.
Christopher Oezbek
@PinnyM Deine Rails 4 Antwort funktioniert bei mir nicht. idist noch 10.
Rick Smith
@RickSmith im angegebenen Beispiel idwurde als 10 übergeben - genau das sollte es also sein. Wenn Sie das nicht erwartet haben, können Sie dies anhand eines Beispiels klären?
PinnyM
Sorry, ich wollte noch nicht 10 sagen . Siehe meine Antwort für eine Erklärung.
Rick Smith
@ RickSmith - interessant, ist dieses Problem nur bei MySQL zu finden? In jedem Fall werden allgemeine Schlüssel für die direkte Zuweisung von Primärschlüsseln für Startdaten verwendet. In diesem Fall sollten Sie im Allgemeinen NICHT versuchen, Werte einzugeben, die unter dem Schwellenwert für die automatische Inkrementierung liegen, oder die automatische Inkrementierung für diesen Befehlssatz deaktivieren.
PinnyM
0

Funktioniert in Rails 4.2.1 mit Postgresql 9.5.3, Post.create(:id => 10, :title => 'Test')solange noch keine Zeile mit id = 10 vorhanden ist.

Nikola
quelle
0

Sie können die ID per SQL einfügen:

  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
quelle