Mengganti id saat membuat di ActiveRecord

104

Apakah ada cara untuk mengganti nilai id model saat membuat? Sesuatu seperti:

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

akan ideal, tetapi jelas tidak akan berhasil.

Codebeef
sumber
1
Banyak dari jawaban ini terkadang akan gagal dengan Rails 4 dan mengatakan bahwa mereka berfungsi. Lihat jawaban saya untuk penjelasan.
Rick Smith

Jawaban:

116

id hanya attr_protected, itulah sebabnya Anda tidak dapat menggunakan tugas massal untuk menyetelnya. Namun, saat mengaturnya secara manual, itu hanya berfungsi:

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

Saya tidak yakin apa motivasi aslinya, tetapi saya melakukan ini saat mengonversi model ActiveHash ke ActiveRecord. ActiveHash memungkinkan Anda menggunakan semantik milik_to yang sama di ActiveRecord, tetapi alih-alih memiliki migrasi dan membuat tabel, dan menimbulkan overhead database pada setiap panggilan, Anda cukup menyimpan data Anda dalam file yml. Kunci asing dalam database mereferensikan id dalam memori di yml.

ActiveHash sangat bagus untuk daftar pilihan dan tabel kecil yang jarang berubah dan hanya diubah oleh pengembang. Jadi, saat beralih dari ActiveHash ke ActiveRecord, paling mudah adalah menyimpan semua referensi kunci asing yang sama.


sumber
@jkndrkn - Saya tidak tahu apa yang Anda maksud. Saya sampai di ActiveRecord::VERSION::STRING == "3.2.11"sini (dengan adaptor sqlite3) dan yang di atas berfungsi untuk saya.
Felix Rabe
Mungkin yang saya alami hanya terekspresikan dengan driver mysql.
jkndrkn
@jkndrkn Bekerja untuk saya menggunakan driver MySQL untuk Rails 3.2.18
lulalala
bekerja untuk saya. menyelamatkan hidupku. digunakan pada rel 4.0.0 / postgres 9.3.5
allenwlee
Lihat jawaban saya untuk bagaimana melakukan ini di Rails 4.
Rick Smith
30

Mencoba

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

yang akan memberikan apa yang Anda cari.

PJ Davis
sumber
2
tidak yakin mengapa Anda mendapatkan suara negatif, ini sangat cocok untuk saya
semanticart
Ini terus berfungsi sejak activerecord 3.2.11. Jawaban yang diposting oleh Jeff Dean pada 2 Oktober 2009 tidak lagi berfungsi.
jkndrkn
tidak berfungsi, sepertinya hanya berfungsi karena memanggil p.save, yang mungkin menampilkan false. akan mendapatkan ActiveRecord :: RecordNotFound jika Anda menjalankan p.save!
Alan
29

Anda juga bisa menggunakan sesuatu seperti ini:

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

Meskipun seperti yang dinyatakan dalam dokumen , ini akan melewati keamanan penugasan massal.

Samuel Heaney
sumber
Temuan bagus, @Samuel Heaney, saya dapat memverifikasi bahwa ini berfungsi sempurna dengan activerecord 3.2.13.
mkralla11
Bekerja untuk saya juga; tidak perlu menyertakan bidang terpisah.
shalott
2
Sayangnya, ini tidak lagi berfungsi di Rails 4, mereka menghapus hash opsi
Jorge Sampayo
@JorgeSampayo - di Rails 4 Anda tidak perlu ini karena atribut yang dilindungi dapat dihapus untuk mendukung StrongParams.
PinnyM
21

Untuk Rails 4:

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

Jawaban Rails 4 lainnya tidak berhasil untuk saya. Banyak dari mereka tampak berubah saat memeriksa menggunakan Rails Console, tetapi ketika saya memeriksa nilai di database MySQL, mereka tetap tidak berubah. Jawaban lain hanya kadang-kadang berhasil.

Untuk MySQL setidaknya, menetapkan di idbawah nomor id penambahan otomatis tidak berfungsi kecuali Anda menggunakan update_column. Sebagai contoh,

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

Jika Anda mengubah createuntuk menggunakan new+, saveAnda masih akan mengalami masalah ini. Mengubah idsuka secara manual p.id = 10juga menghasilkan masalah ini.

Secara umum, saya akan menggunakan update_columnuntuk mengubah idmeskipun itu memerlukan biaya query database tambahan karena akan bekerja sepanjang waktu. Ini adalah kesalahan yang mungkin tidak muncul di lingkungan pengembangan Anda, tetapi diam-diam dapat merusak database produksi Anda sambil mengatakan bahwa itu berfungsi.

Rick Smith
sumber
3
Ini juga satu-satunya cara saya membuatnya bekerja dalam situasi rel 3.2.22 di konsol. Jawaban yang menggunakan variasi 'simpan' tidak berpengaruh.
JosephK
2
Untuk rel 4+ saya menggunakan:Post.new.update(id: 10, title: 'Test')
Spencer
6

Sebenarnya, melakukan hal berikut ternyata berhasil:

p = Post.new(:id => 10, :title => 'Test')
p.save(false)
Codebeef
sumber
Meskipun mungkin berhasil, itu juga mematikan semua validasi, yang mungkin tidak sesuai dengan yang diminta.
Jordan Moncharmont
Inilah yang saya butuhkan untuk data benih di mana ID penting. Terima kasih.
JD.
Saya awalnya memberi suara positif, mengira ini akan berfungsi untuk data seed, seperti yang ditunjukkan @JD, tetapi kemudian saya mencobanya dengan activerecord 3.2.13 dan saya masih mendapatkan kesalahan "Tidak dapat menetapkan atribut yang dilindungi". Jadi,
suara negatif
1
Sayangnya, ini tidak berfungsi di rel 4, Anda mendapatkan NoMethodError: metode tidak ditentukan `[] 'untuk false: FalseClass
Jorge Sampayo
@JorgeSampayo Ini masih berfungsi jika Anda lulus validate: false, bukan hanya false. Namun, Anda masih mengalami masalah atribut yang dilindungi - ada cara terpisah yang telah saya uraikan dalam jawaban saya.
PinnyM
6

Seperti yang Jeff tunjukkan, id berperilaku seolah-olah dilindungi_tarik. Untuk mencegahnya, Anda perlu mengganti daftar atribut default yang dilindungi. Berhati-hatilah saat melakukan ini di mana pun sehingga informasi atribut bisa berasal dari luar. Bidang id secara default dilindungi karena suatu alasan.

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(Diuji dengan ActiveRecord 2.3.5)

Nic Benders
sumber
6

kita dapat mengganti atribut_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)
pengguna510319
sumber
5
Post.create!(:title => "Test") { |t| t.id = 10 }

Ini tidak menurut saya sebagai hal yang biasanya ingin Anda lakukan, tetapi berfungsi dengan baik jika Anda perlu mengisi tabel dengan satu set id tetap (misalnya saat membuat default menggunakan tugas rake) dan Anda ingin mengganti penambahan otomatis (sehingga setiap kali Anda menjalankan tugas, tabel diisi dengan id yang sama):

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

Letakkan fungsi create_with_id ini di bagian atas seed.rb Anda dan kemudian gunakan untuk melakukan pembuatan objek di mana id eksplisit diinginkan.

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

dan gunakan seperti ini

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

alih-alih menggunakan

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

Darren Hicks
sumber
2

Kasus ini adalah masalah serupa yang perlu ditimpa iddengan jenis tanggal khusus:

# 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

Lalu :

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

Callback berfungsi dengan baik.

Semoga berhasil!.

CristianOrellanaBak
sumber
Saya perlu membuat idberdasarkan cap waktu Unix. Saya telah melakukan itu di dalam before_create. Bekerja dengan baik.
WM
0

Untuk Rails 3, cara paling sederhana untuk melakukan ini adalah dengan menggunakan newdengan without_protectionperbaikan, dan kemudian save:

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

Untuk data seed, mungkin masuk akal untuk melewati validasi yang dapat Anda lakukan seperti ini:

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

Kami sebenarnya telah menambahkan metode helper ke ActiveRecord :: Base yang dideklarasikan segera sebelum mengeksekusi file seed:

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

Dan sekarang:

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

Untuk Rails 4, Anda harus menggunakan StrongParams alih-alih atribut yang dilindungi. Jika demikian, Anda dapat menetapkan dan menyimpan tanpa mengirimkan tanda apa pun ke new:

Post.new(id: 10, title: 'Test').save      # optionally pass `{validate: false}`
PinnyM
sumber
Tidak berhasil untuk saya tanpa memasukkan atribut {}seperti jawaban Samuel di atas (Rails3).
Christopher Oezbek
@PinnyM Jawaban Rails 4 Anda tidak bekerja untuk saya. idmasih 10.
Rick Smith
@RickSmith dalam contoh yang diberikan, idditeruskan sebagai 10 - jadi memang seharusnya begitu. Jika bukan itu yang Anda harapkan, dapatkah Anda menjelaskan dengan sebuah contoh?
PinnyM
Maaf, maksud saya masih belum 10. Lihat jawaban saya untuk penjelasan.
Rick Smith
@RickSmith - menarik, apakah masalah ini unik di MySQL? Bagaimanapun, penggunaan umum untuk menetapkan kunci utama secara langsung adalah untuk data benih. Jika demikian, Anda sebaiknya TIDAK mencoba memasukkan nilai yang berada di bawah ambang batas otomatis, atau Anda harus menonaktifkan penambahan otomatis untuk rangkaian perintah tersebut.
PinnyM
0

Di Rails 4.2.1 dengan Postgresql 9.5.3, Post.create(:id => 10, :title => 'Test')berfungsi selama belum ada baris dengan id = 10.

Nikola
sumber
0

Anda dapat memasukkan id dengan 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
wxianfeng.dll
sumber