Làm cách nào để đặt giá trị mặc định trong Rails?


108

Tôi đang cố gắng tìm cách tốt nhất để đặt giá trị mặc định cho các đối tượng trong Rails.

Điều tốt nhất tôi có thể nghĩ đến là đặt giá trị mặc định trong newphương thức trong bộ điều khiển.

Có ai có bất kỳ đầu vào nếu điều này được chấp nhận hoặc nếu có một cách tốt hơn để làm điều đó?


1
Những đối tượng này là gì; chúng được tiêu thụ / sử dụng như thế nào? Chúng được sử dụng trong khi hiển thị các khung nhìn hay cho logic bộ điều khiển?
Gishu

3
Nếu bạn đang nói về một đối tượng ActiveRecord, tôi phải nói với bạn rằng không có giải pháp lành mạnh nào cho vấn đề 'giá trị mặc định'. Chỉ hacks điên, và các đường ray tác giả dường như không nghĩ rằng tính năng này có giá trị nó (tuyệt vời như chỉ đường ray cộng đồng là ..)
Mauricio

Vì các câu trả lời được chấp nhận và hầu hết đều tập trung vào ActiveRecords, chúng tôi giả định câu hỏi ban đầu là về AcitveRecords. Do đó có thể có bản sao của stackoverflow.com/questions/328525/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Câu trả lời:


98

"Đúng" là một từ nguy hiểm trong Ruby. Thường có nhiều cách để làm bất cứ điều gì. Nếu bạn biết rằng bạn sẽ luôn muốn có giá trị mặc định đó cho cột đó trên bảng đó, thì việc đặt chúng trong tệp di chuyển DB là cách dễ nhất:

class SetDefault < ActiveRecord::Migration
  def self.up
    change_column :people, :last_name, :type, :default => "Doe"
  end

  def self.down
    # You can't currently remove default values in Rails
    raise ActiveRecord::IrreversibleMigration, "Can't remove the default"
  end
end

Bởi vì ActiveRecord tự động phát hiện các thuộc tính bảng và cột của bạn, điều này sẽ làm cho cùng một mặc định được đặt trong bất kỳ mô hình nào sử dụng nó trong bất kỳ ứng dụng Rails tiêu chuẩn nào.

Tuy nhiên, nếu bạn chỉ muốn các giá trị mặc định được đặt trong các trường hợp cụ thể - giả sử, đó là một mô hình kế thừa chia sẻ một bảng với một số người khác - thì một cách thanh lịch khác là thực hiện điều đó trực tiếp trong mã Rails của bạn khi đối tượng mô hình được tạo:

class GenericPerson < Person
  def initialize(attributes=nil)
    attr_with_defaults = {:last_name => "Doe"}.merge(attributes)
    super(attr_with_defaults)
  end
end

Sau đó, khi bạn thực hiện một GenericPerson.new(), nó sẽ luôn nhỏ giọt thuộc tính "Doe" lên đến Person.new()trừ khi bạn ghi đè nó bằng thứ khác.


2
Thử nó. Nó sẽ hoạt động trên các đối tượng mô hình mới được gọi với .newphương thức lớp. Cuộc thảo luận của bài đăng trên blog đó về việc gọi trực tiếp ActiveRecord .allocatelà về các đối tượng mô hình được tải với dữ liệu hiện có từ cơ sở dữ liệu. ( đó là một ý tưởng tồi tệ khi ActiveRecord hoạt động theo cách đó, IMO. Nhưng đó là điều quan trọng.)
SFEley

2
Nếu bạn lùi lại để đọc lại câu hỏi của người đăng ban đầu, Nikita, và sau đó nhận xét của tôi theo thứ tự, nó có thể có ý nghĩa hơn đối với bạn. Nếu không ... Chà, câu hỏi đã được trả lời. Chúc một ngày tốt lành.
SFEley

8
Tốt hơn cho quá trình di chuyển xuống: change_column_default :people, :last_name, nil stackoverflow.com/a/1746246/483520
Nolan Amy

9
Xin lưu ý rằng đối với các phiên bản đường ray mới hơn, bạn cần một tham số loại bổ sung trước tham số mặc định. Tìm kiếm: gõ vào trang này.
Ben Wheeler

3
@JoelBrewer Tôi chạy vào ngày hôm nay - cho Rails 4, bạn cũng cần phải xác định loại cột:change_column :people, :last_name, :string, default: "Doe"
GoBusto

56

Dựa trên câu trả lời của SFEley, đây là một bản cập nhật / sửa lỗi cho các phiên bản Rails mới hơn:

class SetDefault < ActiveRecord::Migration
  def change
    change_column :table_name, :column_name, :type, default: "Your value"
  end
end

2
Lưu ý rằng điều này KHÔNG hoạt động trên Rails 4.2.x (không được thử nghiệm trên các phiên bản mới hơn). vì change_columncó thể khá "cấu trúc", không thể suy luận hoạt động ngược lại. Nếu bạn không chắc chắn, chỉ cần kiểm tra nó bằng cách chạy db:migratedb:rollbackngay sau đó. Câu trả lời được chấp nhận là cùng một kết quả nhưng ít nhất nó được giả định!
gfd 21/02/17

1
Đây là câu trả lời đúng cho tôi .. Nó hoạt động với đường ray 5.1 nhưng bạn sẽ cần phải làm updownnhư mô tả ở trên từ @GoBusto
Fabrizio Bertoglio

22

Trước hết, bạn không thể quá tải initialize(*args)vì nó không được gọi trong mọi trường hợp.

Tùy chọn tốt nhất của bạn là đặt mặc định của bạn vào quá trình di chuyển của bạn:

add_column :accounts, :max_users, :integer, :default => 10

Cách tốt thứ hai là đặt mặc định vào mô hình của bạn nhưng điều này sẽ chỉ hoạt động với các thuộc tính ban đầu là con số không. Bạn có thể gặp sự cố như tôi đã làm với booleancác cột:

def after_initialize
  if new_record?
    max_users ||= 10
  end
end

Bạn cần new_record?để các giá trị mặc định không ghi đè các giá trị được tải từ cơ sở dữ liệu.

Bạn cần ||=ngăn Rails ghi đè các tham số được truyền vào phương thức khởi tạo.


12
lưu ý nhỏ - bạn muốn làm hai điều: .... 1) không gọi phương thức của bạn after_initialize. Bạn muốn after_initiation :your_method_name.... 2 sử dụngself.max_users ||= 10
Jesse Wolgamott

5
Đối với boolean, chỉ cần làm điều này: prop = true nếu prop.nil?
Francis Potter

2
hoặc after_initialize dothay vìdef after_initialize
fotanus

self.max_users để được an toàn.
Ken Ratanachai S.

15

Bạn cũng có thể thử change_column_defaulttrong phần di chuyển của mình (đã thử nghiệm trong Rails 3.2.8):

class SetDefault < ActiveRecord::Migration
  def up
    # Set default value
    change_column_default :people, :last_name, "Smith"
  end

  def down
    # Remove default
    change_column_default :people, :last_name, nil
  end
end

Tài liệu API change_column_default Rails


1
Câu trả lời được chấp nhận là đầy đủ hơn nhưng tôi thích câu trả lời rõ ràng và có tài liệu này ... Cảm ơn!
gfd 21/02/17

7

Nếu bạn đang đề cập đến các đối tượng ActiveRecord, bạn có (nhiều hơn) hai cách để thực hiện việc này:

1. Sử dụng một: tham số mặc định trong DB

VÍ DỤ

class AddSsl < ActiveRecord::Migration
  def self.up
    add_column :accounts, :ssl_enabled, :boolean, :default => true
  end

  def self.down
    remove_column :accounts, :ssl_enabled
  end
end

Thông tin thêm tại đây: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html

2. Sử dụng một cuộc gọi lại

VÍ DỤ before_validation_on_create

Thông tin thêm tại đây: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#M002147


2
Không phải vấn đề với việc sử dụng mặc định trong cơ sở dữ liệu mà chúng không được thiết lập cho đến khi đối tượng được lưu? Nếu tôi muốn tạo một đối tượng mới với các giá trị mặc định được điền, tôi cần đặt chúng trong bộ khởi tạo.
Rafe

Boolean không thể mặc định là 1 hoặc 0 - chúng phải được đặt thành true hoặc false (xem câu trả lời của Silasj).
Jamon Holmgren

@JamonHolmgren Cảm ơn bạn đã nhận xét, tôi đã sửa câu trả lời :)
Vlad Zloteanu

OK @VladZloteanu
Jamon Holmgren

7

Trong Ruby on Rails v3.2.8, sử dụng lệnh after_initializegọi lại ActiveRecord, bạn có thể gọi một phương thức trong mô hình của mình sẽ gán các giá trị mặc định cho một đối tượng mới.

Lệnh gọi lại after_initialize được kích hoạt cho từng đối tượng được tìm thấy và khởi tạo bởi công cụ tìm kiếm, với after_initialize được kích hoạt sau khi các đối tượng mới cũng được khởi tạo ( xem ActiveRecord Callbacks ).

Vì vậy, IMO nó sẽ trông giống như sau:

class Foo < ActiveRecord::Base
  after_initialize :assign_defaults_on_new_Foo
  ...
  attr_accessible :bar
  ...
  private
  def assign_defaults_on_new_Foo
    # required to check an attribute for existence to weed out existing records
    self.bar = default_value unless self.attribute_whose_presence_has_been_validated
  end
end

Foo.bar = default_valuecho trường hợp này trừ khi trường hợp chứa một attribute_whose_presence_has_been_validatedbản lưu / cập nhật trước đó. Sau default_valueđó, sẽ được sử dụng cùng với chế độ xem của bạn để hiển thị biểu mẫu bằng thuộc tính default_valuefor bar.

Tốt nhất đây là hacky ...

CHỈNH SỬA - sử dụng 'new_record?' để kiểm tra xem có khởi tạo từ một cuộc gọi mới hay không

Thay vì kiểm tra một giá trị thuộc tính, hãy sử dụng new_record?phương thức tích hợp sẵn với đường ray. Vì vậy, ví dụ trên sẽ giống như sau:

class Foo < ActiveRecord::Base
  after_initialize :assign_defaults_on_new_Foo, if: 'new_record?'
  ...
  attr_accessible :bar
  ...
  private
  def assign_defaults_on_new_Foo
    self.bar = default_value
  end
end

Cái này sạch hơn nhiều. Ah, điều kỳ diệu của Rails - nó thông minh hơn tôi.


Điều này không hoạt động như tôi mong đợi - nó được cho là chỉ đặt mặc định là mới, nhưng nó cũng đặt mặc định thành thuộc tính cho mỗi đối tượng được tìm thấy và khởi tạo (tức là các bản ghi được tải từ db). Bạn có thể thực hiện kiểm tra một thuộc tính đối tượng để tìm kiếm các giá trị trước khi gán giá trị mặc định, nhưng đó không phải là một giải pháp tốt hoặc mạnh mẽ.
erroric

1
Tại sao bạn khuyên bạn nên after_initializegọi lại ở đây? Tài liệu về lệnh gọi lại của Rails có ví dụ về việc đặt giá trị mặc định before_createmà không cần kiểm tra điều kiện bổ sung
Dfr

@Dfr - Tôi chắc đã bỏ lỡ điều đó, bạn có thể ném cho tôi một liên kết để xem lại và tôi sẽ cập nhật câu trả lời ...
erroric

Có, đây api.rubyonrails.org/classes/ActiveRecord/Callbacks.html ví dụ đầu tiên, lớpSubscription
Dfr

5
@Dfr - Lý do mà chúng tôi đang sử dụng after_initialize, thay vì before_creategọi lại, là chúng tôi muốn đặt giá trị mặc định cho người dùng (để sử dụng trong một khung nhìn) khi họ đang tạo các đối tượng mới. Các before_createcallback được gọi sau khi những người dùng đã được phục vụ một đối tượng mới, với điều kiện đầu vào của họ, và nộp các đối tượng để tạo với bộ điều khiển. Bộ điều khiển sau đó sẽ kiểm tra bất kỳ lệnh before_creategọi lại nào . Nó có vẻ phản trực quan, nhưng nó là một thứ danh pháp - before_createđề cập đến createhành động. Khởi tạo một đối tượng mới không phải createlà đối tượng.
erroric

5

Đối với các trường boolean ít nhất trong Rails 3.2.6, điều này sẽ hoạt động trong quá trình di chuyển của bạn.

def change
  add_column :users, :eula_accepted, :boolean, default: false
end

Đặt một 1hoặc 0cho một mặc định sẽ không hoạt động ở đây, vì nó là một trường boolean. Nó phải là một truehoặc falsegiá trị.



2

Tạo quá trình di chuyển và sử dụng change_column_default, ngắn gọn và có thể hoàn nguyên:

class SetDefaultAgeInPeople < ActiveRecord::Migration[5.2]
  def change
    change_column_default :people, :age, { from: nil, to: 0 }
  end
end

1

Nếu bạn chỉ đặt giá trị mặc định cho các thuộc tính nhất định của một mô hình được cơ sở dữ liệu hỗ trợ, tôi sẽ cân nhắc sử dụng các giá trị cột mặc định sql - bạn có thể làm rõ loại mặc định nào bạn đang sử dụng không?

Có một số cách tiếp cận để xử lý nó, plugin này có vẻ như là một lựa chọn thú vị.


1
Tôi nghĩ rằng bạn không nên phụ thuộc vào cơ sở dữ liệu để đối phó với các mặc định và ràng buộc, tất cả những thứ đó nên được sắp xếp trong lớp mô hình. Plugin đó chỉ hoạt động cho các mô hình ActiveRecord, không phải là cách chung chung để đặt mặc định cho các đối tượng.
Lukas Stejskal,

Tôi lập luận rằng nó phụ thuộc vào loại mặc định mà bạn đang cố gắng sử dụng, đó không phải là điều tôi cần phải làm thường xuyên nhưng tôi muốn nói rằng các ràng buộc trên thực tế tốt hơn nhiều nếu được đặt trong cả cơ sở dữ liệu và mô hình - để ngăn dữ liệu của bạn không trở nên không hợp lệ trong cơ sở dữ liệu.
paulthenerd 27/07/09

1

Đề xuất ghi đè mới / khởi tạo có thể chưa hoàn thành. Rails sẽ (thường xuyên) gọi cấp phát cho các đối tượng ActiveRecord và các lệnh gọi cấp phát sẽ không dẫn đến các cuộc gọi khởi tạo.

Nếu bạn đang nói về các đối tượng ActiveRecord, hãy xem ghi đè after_initialize.

Các bài đăng trên blog này (không phải của tôi) rất hữu ích:

Giá trị mặc định Các hàm tạo mặc định không được gọi

[Chỉnh sửa: SFEley chỉ ra rằng Rails thực sự xem xét mặc định trong cơ sở dữ liệu khi nó khởi tạo một đối tượng mới trong bộ nhớ - tôi đã không nhận ra điều đó.]


1

Tôi cần đặt mặc định giống như thể nó được chỉ định làm giá trị cột mặc định trong DB. Vì vậy, nó hoạt động như thế này

a = Item.new
a.published_at # => my default value

a = Item.new(:published_at => nil)
a.published_at # => nil

Vì lệnh gọi lại after_initialize được gọi sau khi thiết lập các thuộc tính từ các đối số, không có cách nào để biết liệu thuộc tính đó là nil vì nó chưa bao giờ được đặt hay do nó được cố ý đặt là nil. Vì vậy, tôi đã phải chọc vào bên trong một chút và đến với giải pháp đơn giản này.

class Item < ActiveRecord::Base
  def self.column_defaults
    super.merge('published_at' => Time.now)
  end
end

Làm việc tuyệt vời cho tôi. (Đường ray 3.2.x)



0

tôi đã trả lời một câu hỏi tương tự ở đây .. một cách rõ ràng để làm điều này là sử dụng Rails attr_accessor_with_default

class SOF
  attr_accessor_with_default :is_awesome,true
end

sof = SOF.new
sof.is_awesome

=> true

CẬP NHẬT

attr_accessor_with_default đã không được chấp nhận trong Rails 3.2 .. thay vào đó bạn có thể làm điều này bằng Ruby thuần túy

class SOF
  attr_writer :is_awesome

  def is_awesome
    @is_awesome ||= true
  end
end

sof = SOF.new
sof.is_awesome

#=> true

attr_accessor_with_defaultkhông được dùng nữa vì rails> 3.1.0
flynfish

Trong ví dụ của bạn, is_awesome sẽ luôn đúng ngay cả khi @is_awesome == false.
Ritchie



-3

Bạn có thể ghi đè phương thức khởi tạo cho mô hình ActiveRecord.

Như thế này:

def initialize(*args)
  super(*args)
  self.attribute_that_needs_default_value ||= default_value
  self.attribute_that_needs_another_default_value ||= another_default_value
  #ad nauseum
end

2
Đây là một nơi tồi tệ để đi về việc thay đổi chức năng của đường ray lõi. Có nhiều cách ổn định khác ít có khả năng phá vỡ những thứ khác, những cách để làm điều này được đề cập trong các câu trả lời khác. Vá khỉ chỉ nên là biện pháp cuối cùng cho những việc không thể làm theo cách nào khác.
Sẽ
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.