Ü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.


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.


@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.


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.


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.


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)

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)


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)

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

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


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!.


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}`

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.


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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.