Ghi đè id khi tạo trong ActiveRecord


104

Có cách nào để ghi đè giá trị id của mô hình khi tạo không? Cái gì đó như:

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

sẽ là lý tưởng, nhưng rõ ràng sẽ không hoạt động.


1
Nhiều câu trả lời trong số này sẽ không liên tục được với Rails 4 và nói rằng chúng đang hoạt động. Xem câu trả lời của tôi để giải thích.
Rick Smith

Câu trả lời:


116

id chỉ là attr_protected, đó là lý do tại sao bạn không thể sử dụng tính năng gán hàng loạt để đặt nó. Tuy nhiên, khi thiết lập thủ công, nó chỉ hoạt động:

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

Tôi không chắc động cơ ban đầu là gì, nhưng tôi thực hiện điều này khi chuyển đổi các mô hình ActiveHash thành ActiveRecord. ActiveHash cho phép bạn sử dụng ngữ nghĩa tương tự thuộc về thuộc tính trong ActiveRecord, nhưng thay vì phải di chuyển và tạo bảng, đồng thời phát sinh chi phí cơ sở dữ liệu trong mỗi lần gọi, bạn chỉ cần lưu trữ dữ liệu của mình trong các tệp yml. Các khóa ngoại trong cơ sở dữ liệu tham chiếu đến id trong bộ nhớ trong yml.

ActiveHash rất phù hợp cho các danh sách chọn lọc và các bảng nhỏ thay đổi không thường xuyên và chỉ thay đổi bởi các nhà phát triển. Vì vậy, khi chuyển từ ActiveHash sang ActiveRecord, đơn giản nhất là chỉ cần giữ nguyên tất cả các tham chiếu khóa ngoại.


@jkndrkn - Tôi không biết ý bạn là gì. Tôi đã có ActiveRecord::VERSION::STRING == "3.2.11"ở đây (với bộ điều hợp sqlite3) và những điều trên phù hợp với tôi.
Felix Rabe

Có lẽ những gì tôi trải nghiệm chỉ thể hiện với trình điều khiển mysql.
jkndrkn

@jkndrkn trình cho tôi sử dụng trình điều khiển MySQL cho Rails 3.2.18
lulalala

đã làm cho tôi. cứu cuộc đời tôi. được sử dụng trên đường ray 4.0.0 / postgres 9.3.5
allenwlee

Xem câu trả lời của tôi để biết cách thực hiện việc này trên Rails 4.
Rick Smith

30

Thử

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

điều đó sẽ cung cấp cho bạn những gì bạn đang tìm kiếm.


2
không chắc chắn lý do tại sao bạn đang nhận downvoted, điều này hoạt động tuyệt vời đối với tôi
semanticart

Điều này tiếp tục hoạt động như activerecord 3.2.11. Câu trả lời được đăng bởi Jeff Dean vào ngày 2 tháng 10 năm 2009 không còn hoạt động.
jkndrkn

không hoạt động, dường như chỉ hoạt động vì gọi p.save, có thể trả về false. sẽ nhận được ActiveRecord :: RecordNotFound nếu bạn chạy p.save!
Alan

29

Bạn cũng có thể sử dụng một cái gì đó như thế này:

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

Mặc dù như đã nêu trong tài liệu , điều này sẽ bỏ qua bảo mật chuyển nhượng hàng loạt.


Rất vui, @Samuel Heaney, tôi có thể xác minh điều này hoạt động hoàn hảo với activerecord 3.2.13.
mkralla

Làm việc cho tôi là tốt; không cần thiết để bao gồm một trường riêng biệt.
shalott 23/03

2
Than ôi, điều này không còn hoạt động trên Rails 4 nữa, họ đã loại bỏ băm tùy chọn
Jorge Sampayo

@JorgeSampayo - trong Rails 4, bạn không cần điều này vì các thuộc tính được bảo vệ có thể bị xóa theo hướng có lợi cho StrongParams.
PinnyM

21

Đối với Rails 4:

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

Câu trả lời Rails 4 khác không phù hợp với tôi. Nhiều người trong số họ dường như thay đổi khi kiểm tra bằng Rails Console, nhưng khi tôi kiểm tra các giá trị trong cơ sở dữ liệu MySQL, chúng vẫn không thay đổi. Các câu trả lời khác đôi khi chỉ hoạt động.

Đối với MySQL ít nhất, việc gán idsố id tự động bên dưới không hoạt động trừ khi bạn sử dụng update_column. Ví dụ,

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

Nếu bạn đổi createsang sử dụng new+ savebạn sẽ vẫn gặp sự cố này. Thay đổi thủ công idtương tự p.id = 10cũng tạo ra vấn đề này.

Nói chung, tôi sẽ sử dụng update_columnđể thay đổi idmặc dù nó tốn thêm một truy vấn cơ sở dữ liệu vì nó sẽ hoạt động mọi lúc. Đây là một lỗi có thể không hiển thị trong môi trường phát triển của bạn, nhưng có thể âm thầm làm hỏng cơ sở dữ liệu sản xuất của bạn trong khi nói rằng nó đang hoạt động.


3
Đây cũng là cách duy nhất tôi làm cho nó hoạt động trong tình huống đường ray 3.2.22 trong bảng điều khiển. Các câu trả lời sử dụng các biến thể trên 'save' không có tác dụng.
JosephK

2
Đối với đường ray 4+ tôi sử dụng:Post.new.update(id: 10, title: 'Test')
Spencer

6

Trên thực tế, nó chỉ ra rằng làm những việc sau đây:

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

Mặc dù nó có thể hoạt động, nó cũng tắt tất cả các xác nhận, điều này có thể không giống như những gì được yêu cầu.
Jordan Moncharmont

Đây chính xác là những gì tôi cần cho dữ liệu hạt giống trong đó ID quan trọng. Cảm ơn bạn.
JD.

Ban đầu tôi đã ủng hộ, nghĩ rằng điều này sẽ hoạt động đối với dữ liệu hạt giống, như @JD đã chỉ ra, nhưng sau đó tôi đã thử nó với activerecord 3.2.13 và tôi vẫn gặp lỗi "Không thể gán thuộc tính được bảo vệ". Vì vậy, đã phản đối :(
mkralla11

1
Thật không may, điều này không hoạt động trong rails 4, bạn nhận được NoMethodError: phương thức không xác định `[] 'cho sai: FalseClass
Jorge

@JorgeSampayo Điều này vẫn hoạt động nếu bạn vượt qua validate: false, thay vì đơn giản false. Tuy nhiên, bạn vẫn gặp phải vấn đề thuộc tính được bảo vệ - có một cách riêng để giải quyết vấn đề mà tôi đã nêu trong câu trả lời của mình.
PinnyM

6

Như Jeff đã chỉ ra, id hoạt động như thể được bảo vệ bởi attr_protected. Để ngăn chặn điều đó, bạn cần ghi đè danh sách các thuộc tính được bảo vệ mặc định. Hãy cẩn thận làm điều này ở bất kỳ nơi nào mà thông tin thuộc tính có thể đến từ bên ngoài. Trường id được bảo vệ mặc định vì một lý do.

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(Đã kiểm tra với ActiveRecord 2.3.5)


6

chúng ta có thể ghi đè thuộc tính_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 }

Đây không phải là điều mà bạn thường muốn làm, nhưng nó hoạt động khá tốt nếu bạn cần điền vào một bảng với một tập hợp id cố định (ví dụ: khi tạo giá trị mặc định bằng tác vụ cào) và bạn muốn ghi đè tính năng tự động tăng dần (để mỗi khi bạn chạy tác vụ, bảng sẽ được điền với các id giống nhau):

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

2

Đặt hàm create_with_id này ở trên cùng của seed.rb và sau đó sử dụng nó để tạo đối tượng ở những nơi bạn muốn có id rõ ràng.

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

và sử dụng nó như thế này

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

thay vì sử dụng

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


2

Trường hợp này là một vấn đề tương tự cần thiết phải ghi đè lên idbằng một loại ngày tùy chỉnh:

# 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

Và sau đó :

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

Gọi lại hoạt động tốt.

Chúc may mắn!.


Tôi cần tạo một nhãn idthời gian dựa trên Unix. Tôi đã làm điều đó bên trong before_create. Hoạt động tốt.
WM

0

Đối với Rails 3, cách đơn giản nhất để làm điều này là sử dụng newvới sự without_protectiontinh chỉnh, sau đó save:

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

Đối với dữ liệu hạt giống, bạn có thể bỏ qua xác thực mà bạn có thể làm như sau:

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

Chúng tôi thực sự đã thêm một phương thức trợ giúp vào ActiveRecord :: Base được khai báo ngay trước khi thực thi các tệp hạt giống:

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

Và bây giờ:

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

Đối với Rails 4, bạn nên sử dụng StrongParams thay vì các thuộc tính được bảo vệ. Nếu đúng như vậy, bạn chỉ cần gán và lưu mà không cần chuyển bất kỳ cờ nào cho new:

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

Không hiệu quả với tôi nếu không đưa các thuộc tính vào {}như câu trả lời của Samuel ở trên (Rails3).
Christopher Oezbek

@PinnyM Câu trả lời Rails 4 của bạn không phù hợp với tôi. idvẫn là 10 tuổi
Rick Smith

@RickSmith trong ví dụ đã cho, idđược chuyển là 10 - vì vậy đó chính xác là những gì nó phải như vậy. Nếu đó không phải là điều bạn mong đợi, bạn có thể làm rõ bằng một ví dụ không?
PinnyM

Xin lỗi, tôi muốn nói vẫn chưa phải là 10. Xem câu trả lời của tôi để được giải thích.
Rick Smith

@RickSmith - thật thú vị, vấn đề này có phải là duy nhất đối với MySQL không? Trong mọi trường hợp, cách sử dụng chung để chỉ định trực tiếp khóa chính là cho dữ liệu gốc. Nếu vậy, nói chung, bạn KHÔNG nên cố gắng nhập các giá trị dưới ngưỡng tự động tăng hoặc bạn nên tắt tính năng tự động tăng cho tập hợp lệnh đó.
PinnyM

0

Trong Rails 4.2.1 với Postgresql 9.5.3, Post.create(:id => 10, :title => 'Test')hoạt động miễn là chưa có hàng có id = 10.


0

bạn có thể chèn id bằng 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.