ActiveRecordでデフォルト値を設定する方法
私はPratikからの、見にくく複雑なコードの塊についての記事を見ている。 http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-モデル
class Item < ActiveRecord::Base
def initialize_with_defaults(attrs = nil, &block)
initialize_without_defaults(attrs) do
setter = lambda { |key, value| self.send("#{key.to_s}=", value) unless
!attrs.nil? && attrs.keys.map(&:to_s).include?(key.to_s) }
setter.call('scheduler_type', 'hotseat')
yield self if block_given?
end
end
alias_method_chain :initialize, :defaults
end
私は次の例がググっているのを見ました:
def initialize
super
self.status = ACTIVE unless self.status
end
そして
def after_initialize
return unless new_record?
self.status = ACTIVE
end
私はまた人々が彼らの移行にそれを入れているのを見ました、しかし私はむしろそれがモデルコードの中で定義されるのを見たいです。
ActiveRecordモデルのフィールドにデフォルト値を設定するための標準的な方法はありますか?
使用可能な各メソッドにはいくつかの問題がありますが、after_initialize
コールバックを定義することは、次の理由で進むべき方法だと思います。
default_scope
は新しいモデルの値を初期化しますが、それがモデルを見つけるスコープになります。いくつかの数字を0に初期化するだけの場合、これはnotです。initialize
のオーバーライドは機能しますが、super
を呼び出すことを忘れないでください!after_initialize
のオーバーライド非推奨 Rails現在3. Rails 3.0.3でafter_initialize
をオーバーライドすると、次の警告が表示されますコンソールで:非推奨の警告:Base#after_initializeは非推奨になりました。代わりにBase.after_initialize:methodを使用してください。 (/ Users/me/myapp/app/models/my_model:15から呼び出されます)
したがって、after_initialize
コールバックを記述して、デフォルトの属性に加えてに加えて、関連付けにデフォルトを設定できるようにします。
class Person < ActiveRecord::Base
has_one :address
after_initialize :init
def init
self.number ||= 0.0 #will set the default value only if it's nil
self.address ||= build_address #let's you set a default association
end
end
これで、just oneモデルの初期化を探す場所ができました。誰かがより良い方法を思いつくまで、私はこの方法を使用しています。
警告:
ブールフィールドの場合:
self.bool_field = true if self.bool_field.nil?
詳細については、この回答に関するPaul Russellのコメントを参照してください。
モデルの列のサブセットのみを選択している場合(つまり、Person.select(:firstname, :lastname).all
などのクエリでselect
を使用している場合)、MissingAttributeError
メソッドが列にアクセスすると、init
を取得します'select
句には含まれていません。次のようにして、このケースを防ぐことができます。
self.number ||= 0.0 if self.has_attribute? :number
ブール列の場合...
self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
また、Rails 3.2より前の構文は異なることに注意してください(以下のCliff Darlingのコメントを参照)
マイグレーションによって(各列定義に:default
オプションを指定することによって)データベースにデフォルト値を入れ、Active Recordにこれらの値を使用して各属性のデフォルト値を設定させます。
私見、このアプローチはARの原則と一致しています:設定上の規約、DRY、テーブル定義はモデルを動かしますが、その逆ではありません。
モデル内ではなく移行内ではあるが、デフォルトはまだアプリケーション(Ruby)コード内にあることに注意されたい。
あなたのモデルの中で attribute メソッドを使うことができます。例えば:
class Account < ApplicationRecord
attribute :locale, :string, default: 'en'
end
ラムダをdefault
パラメータに渡すこともできます。例:
attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }
いくつかの単純なケースはデータベーススキーマでデフォルトを定義することで処理できますが、それは計算値や他のモデルのキーを含む多くのトリッキーなケースを処理しません。これらの場合、私はこれをします:
after_initialize :defaults
def defaults
unless persisted?
self.extras||={}
self.other_stuff||="This stuff"
self.assoc = [OtherModel.find_by_name('special')]
end
end
私はafter_initializeを使うことに決めました、しかし、私はそれがそれらが新しいか、または作成されたものだけを見つけられるオブジェクトに適用されることを望みません。 after_newコールバックがこの明白なユースケースのために提供されていないのはほとんど衝撃的だと思いますが、オブジェクトが既に永続化されているかどうかを確認することによって行ったのです。
Brad Murrayの答えを見たことで、条件がコールバックリクエストに移動した場合、これはさらに明確になります。
after_initialize :defaults, unless: :persisted?
# ":if => :new_record?" is equivalent in this context
def defaults
self.extras||={}
self.other_stuff||="This stuff"
self.assoc = [OtherModel.find_by_name('special')]
end
After_initializeコールバックパターンは、単に次のようにすることで改善できます。
after_initialize :some_method_goes_here, :if => :new_record?
あなたのinitコードが関連付けを扱う必要があるなら、これは自明な利点を持ちます。
class Account
has_one :config
after_initialize :init_config
def init_config
self.config ||= build_config
end
end
Phusionの人たちはこれに関していくつかのNice plugin を持っています。
提案された答えよりももっと良い/よりきれいな可能な方法はこのようにアクセサを上書きすることです:
def status
self['status'] || ACTIVE
end
ActiveRecord :: Baseドキュメント および StackOverflowのselfの使用に関する詳細 の「デフォルトのアクセサの上書き」を参照してください。
私は attribute-defaults
gem を使います
ドキュメントから:Sudo gem install attribute-defaults
を実行し、require 'attribute_defaults'
をアプリに追加します。
class Foo < ActiveRecord::Base
attr_default :age, 18
attr_default :last_seen do
Time.now
end
end
Foo.new() # => age: 18, last_seen => "2014-10-17 09:44:27"
Foo.new(:age => 25) # => age: 25, last_seen => "2014-10-17 09:44:28"
似たような質問ですが、状況は少し異なります。 - Rails activerecordのモデルで属性のデフォルト値を作成するにはどうすればよいですか?
ベストアンサー:欲しいものによります!
すべてのオブジェクトを値で始めるには、を使用します。 after_initialize :init
ページを開くときにnew.html
フォームにデフォルト値を設定しますか?使用 https://stackoverflow.com/a/5127684/1536309
class Person < ActiveRecord::Base
has_one :address
after_initialize :init
def init
self.number ||= 0.0 #will set the default value only if it's nil
self.address ||= build_address #let's you set a default association
end
...
end
すべてのオブジェクトににユーザー入力から計算された値を持たせたい場合:use before_save :default_values
ユーザーにX
を入力してからY = X+'foo'
を入力しますか?つかいます:
class Task < ActiveRecord::Base
before_save :default_values
def default_values
self.status ||= 'P'
end
end
まず第一に:私はJeffの答えに同意しません。アプリが小さくてロジックが単純な場合には意味があります。私はここで、より大きなアプリケーションを構築し保守するときにそれがどのように問題になり得るかについての洞察を与えようとしています。小さいものを構築するときに最初にこのアプローチを使用することはお勧めしませんが、代わりのアプローチとして念頭に置くようにしてください。
ここでの問題は、このレコードのデフォルトがビジネスロジックかどうかです。もしそうなら、私はそれをORMモデルに入れるように注意するでしょう。 rywが述べているフィールドはアクティブなので、これはビジネスロジックのように思えます。例えば。ユーザーはアクティブです。
ビジネス上の懸念をORMモデルに入れることに慎重になるのはなぜですか?
壊れる SRP 。 ActiveRecord :: Baseを継承するクラスは、すでに多くのさまざまな処理を行っています。その中で最も重要なのは、データの整合性(検証)と永続性(保存)です。ビジネスロジックを入れても小さくてもAR :: BaseでSRPを破る。
テストは遅くなります。私のORMモデルで起こっている何らかの形のロジックをテストしたいのなら、私のテストは実行するためにRailsを初期化しなければなりません。これはアプリケーションの最初の段階ではあまり問題にはなりませんが、単体テストの実行に長い時間がかかるまで続きます。
それはSRPをさらに詳細に、そして具体的な方法で破壊するでしょう。アイテムがアクティブになったときに、ビジネスでユーザーに電子メールを送信する必要があるとします。現在、私たちは電子メールロジックをItem ORMモデルに追加しています。その主な役割はItemのモデリングです。電子メールのロジックを気にするべきではありません。これはビジネス副作用の場合です。これらはORMモデルに属しません。
多様化するのは難しいです。 init_type:stringフィールドのようなものが初期化ロジックを制御することだけを目的とした成熟したRailsアプリケーションを見たことがあります。これはデータベースを汚染して構造上の問題を解決しています。もっと良い方法があると思います。
POROの方法:これはもう少しコードですが、ORMモデルとビジネスロジックを別々にしておくことができます。ここのコードは単純化されていますが、アイデアを示す必要があります。
class SellableItemFactory
def self.new(attributes = {})
record = Item.new(attributes)
record.active = true if record.active.nil?
record
end
end
それで、これをきちんとして、新しいItemを作成する方法は、
SellableItemFactory.new
そして私のテストでは、ItemFactoryがItemに値を持たないならアクティブになることを単純に検証することができます。 Railsの初期化は不要で、SRPは壊れません。アイテムの初期化がより高度になると(例えば、ステータスフィールド、デフォルトタイプなどを設定すると)、ItemFactoryはこれを追加することができます。 2種類のデフォルトがある場合は、これを行うために新しいBusinesCaseItemFactoryを作成できます。
注:ここで依存性注入を使用して、ファクトリで多数のアクティブなものを構築できるようにすることも有益な場合がありますが、簡単にするために省略しました。 self.new(klass = Item、attributes = {})
みんな、私は次のことをやってしまいました:
def after_initialize
self.extras||={}
self.other_stuff||="This stuff"
end
魅力のように動作します!
これがコンストラクタの目的です。モデルのinitialize
メソッドをオーバーライドします。
after_initialize
メソッドを使用してください。
これは長い間回答されてきましたが、私はデフォルト値を頻繁に必要とし、データベースに入れないことを好みます。 DefaultValues
を作成します。
module DefaultValues
extend ActiveSupport::Concern
class_methods do
def defaults(attr, to: nil, on: :initialize)
method_name = "set_default_#{attr}"
send "after_#{on}", method_name.to_sym
define_method(method_name) do
if send(attr)
send(attr)
else
value = to.is_a?(Proc) ? to.call : to
send("#{attr}=", value)
end
end
private method_name
end
end
end
そして私のモデルでそれを次のように使用します。
class Widget < ApplicationRecord
include DefaultValues
defaults :category, to: 'uncategorized'
defaults :token, to: -> { SecureRandom.uuid }
end
私はまた人々が彼らの移行にそれを入れているのを見ました、しかし私はむしろそれがモデルコードの中で定義されるのを見たいです。
ActiveRecordモデルのフィールドにデフォルト値を設定するための標準的な方法はありますか?
Rails 5より前の標準的なRailsの方法は、実際にはマイグレーションでそれを設定することでした。そしてどのデフォルト値がどのモデルに対してDBによって設定されているかを知りたいときはいつでもdb/schema.rb
を見てください。
@Jeff Perrinが答えたこと(少し古い)とは反対に、Railsの魔法のおかげで、マイグレーションアプローチはModel.new
を使うときにはデフォルトを適用することさえあります。 Rails 4.1.16で動作確認済み。
最も簡単なことがしばしば最善です。コードベースでの知識の借方が少なくなり、混乱が生じる可能性があります。そしてそれは「うまくいく」。
class AddStatusToItem < ActiveRecord::Migration
def change
add_column :items, :scheduler_type, :string, { null: false, default: "hotseat" }
end
end
null: false
はDB内のNULL値を許可しません。また、追加の利点として、既存のすべてのDBレコードもこのフィールドのデフォルト値で設定されて更新されます。ご希望であれば、移行時にこのパラメータを除外してもかまいませんが、非常に便利です。
@Lucas Catonが言ったように、Rails 5以降の標準的な方法は次のとおりです。
class Item < ActiveRecord::Base
attribute :scheduler_type, :string, default: 'hotseat'
end
after_initialize
がActiveModel::MissingAttributeError
エラーを出すという問題に遭遇しました。
例えば:
@bottles = Bottle.includes(:supplier, :substance).where(search).order("suppliers.name ASC").paginate(:page => page_no)
.where
の "search"は条件のハッシュです
だから私はこのように初期化をオーバーライドすることでそれをやってしまった:
def initialize
super
default_values
end
private
def default_values
self.date_received ||= Date.current
end
カスタマイズコードを実行する前に、super
呼び出しを行って、オブジェクトがActiveRecord::Base
から正しく初期化されていることを確認する必要があります。例:default_values
"default_value_for" gemを使うことを強くお勧めします。 https://github.com/FooBarWidget/default_value_for
そのgemが初期化メソッドをオーバーライドすることをほとんど必要とするいくつかのトリッキーなシナリオがあります。
例:
DbのデフォルトはNULL、model/Ruby定義のデフォルトは "some string"ですが、実際には何らかの理由で値をnilに設定する必要があります:MyModel.new(my_attr: nil)
ここでのほとんどの解決策は値をnilに設定することに失敗し、代わりにそれをデフォルトに設定します。
||=
アプローチをとる代わりに、my_attr_changed?
...に切り替えます。
BUT今あなたのdbのデフォルトは "some string"、あなたのモデル/ Rubyで定義されたデフォルトは "some other string"であると想像してみてください。シナリオでは、値を "some string"(データベースのデフォルト)に設定したい:MyModel.new(my_attr: 'some_string')
これはmy_attr_changed?
がfalseになることを意味します。これは、値がdbのデフォルトと一致するためです。他の文字列 " - ここでも、あなたが望むものではありません。
これらの理由から、これはafter_initializeフックだけで適切に達成できるとは思いません。
繰り返しますが、私は "default_value_for" gemが正しいアプローチを取っていると思います: https://github.com/FooBarWidget/default_value_for
class Item < ActiveRecord::Base
def status
self[:status] or ACTIVE
end
before_save{ self.status ||= ACTIVE }
end
After_initializeソリューションの問題は、この属性にアクセスするかどうかにかかわらず、DBから検索するすべてのオブジェクトにafter_initializeを追加する必要があることです。私は怠惰なアプローチを提案します。
属性メソッド(ゲッター)はもちろんメソッドそのものなので、それらをオーバーライドしてデフォルトを指定できます。何かのようなもの:
Class Foo < ActiveRecord::Base
# has a DB column/field atttribute called 'status'
def status
(val = read_attribute(:status)).nil? ? 'ACTIVE' : val
end
end
他の人が指摘したように、Foo.find_by_status( 'ACTIVE')を実行する必要がある場合を除き、その場合、DBがサポートしていれば、データベースの制約にデフォルトを設定する必要があると思います。
after_initializeメソッドは推奨されていません。代わりにコールバックを使用してください。
after_initialize :defaults
def defaults
self.extras||={}
self.other_stuff||="This stuff"
end
ただし、マイグレーションで:defaultを使用するのが、まだ最もクリーンな方法です。
これは私がまだ追加されていない少し驚いたという私が使用した解決策です。
それには2つの部分があります。最初の部分は実際の移行でのデフォルトの設定です。そして2番目の部分はモデルに検証を追加して、存在が正しいことを確認します。
add_column :teams, :new_team_signature, :string, default: 'Welcome to the Team'
そのため、ここではデフォルトがすでに設定されていることがわかります。検証では、文字列に常に値があることを確認したいので、次のようにします。
validates :new_team_signature, presence: true
これが何をするかはあなたのためにデフォルト値を設定することです。 (私には "Welcome to the Team"があります)そしてそれはさらに一歩進んで、そのオブジェクトには常に価値があることを確認します。
それが役立つことを願っています!
デフォルト値を設定するためにそうすることはほとんどの場合混乱して厄介ですが、:default_scope
を使うこともできます。チェックアウト ここにsquilのコメント 。
私は、検証方法を使用することでデフォルト設定を制御することができます。更新のデフォルトを設定する(または検証に失敗する)こともできます。本当にしたいのであれば、挿入と更新に異なるデフォルト値を設定することもできます。 #valid?までデフォルトは設定されません。と呼ばれています。
class MyModel
validate :init_defaults
private
def init_defaults
if new_record?
self.some_int ||= 1
elsif some_int.nil?
errors.add(:some_int, "can't be blank on update")
end
end
end
After_initializeメソッドの定義に関しては、after_initializeは以下によって返される各オブジェクトによっても呼び出されるため、パフォーマンスの問題が生じる可能性があります。find: http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and -after_find
その列がたまたま「ステータス」タイプの列で、モデルがステートマシンの使用に適している場合は、 aasm gem を使用することを検討します。
aasm column: "status" do
state :available, initial: true
state :used
# transitions
end
まだ保存されていないレコードの値は初期化されませんが、init
などを使用して独自にロールするよりも少しきれいです。また、すべてのステータスのスコープなど、aasmの他の利点も享受できます。
https://github.com/keithrowell/Rails_default_value
class Task < ActiveRecord::Base
default :status => 'active'
end