438

ActiveRecordでデフォルト値を設定するにはどうすればよいですか?

見苦しく複雑なコードのチャンクについて説明している Pratik の投稿を見ました: http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-model

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 モデルのフィールドのデフォルト値を設定する正規の方法はありますか?

4

29 に答える 29

578

利用可能な各メソッドにはいくつかの問題がありafter_initializeますが、次の理由から、コールバックを定義することが最善の方法であると私は信じています。

  1. default_scope新しいモデルの値を初期化しますが、それがモデルを見つけるスコープになります。一部の数値を 0 に初期化するだけの場合、これは望ましくありません。
  2. 移行でデフォルトを定義することも部分的に機能します... すでに述べたように、Model.new を呼び出すだけでは機能しません
  3. オーバーライドinitializeは機能しますが、 を呼び出すことを忘れないでくださいsuper!
  4. Phusion のようなプラグインを使用するのは少しばかげています。これは ruby​​ です。いくつかのデフォルト値を初期化するためだけにプラグインが本当に必要ですか?
  5. オーバーライドはRails 3after_initialize で非推奨after_initializeになりました。Rails 3.0.3 でオーバーライドすると、コンソールに次の警告が表示されます。

廃止の警告: 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    

これで、モデルの初期化を探す場所が1 つだけになりました。誰かがより良い方法を思い付くまで、私はこの方法を使用しています。

警告:

  1. ブール値フィールドの場合:

    self.bool_field = true if self.bool_field.nil?

    詳細については、この回答に関するポール ラッセルのコメントを参照してください。

  2. モデルの列のサブセットのみを選択している場合 (つまり、selectのようなクエリで使用している場合)、句に含まれていない列にメソッドがアクセスするPerson.select(:firstname, :lastname).allと、 が返されます。次のように、このケースを防ぐことができます。MissingAttributeErrorinitselect

    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 のコメントを参照してください)。

于 2011-02-26T15:14:11.000 に答える
50

移行を通じて(:default各列定義でオプションを指定することにより)データベースにデフォルト値を設定し、ActiveRecordがこれらの値を使用して各属性のデフォルトを設定できるようにします。

IMHO、このアプローチはARの原則に沿っています:設定より規約、DRY、テーブル定義がモデルを駆動しますが、その逆ではありません。

モデルではなく移行ではありますが、デフォルトはまだアプリケーション(Ruby)コードにあることに注意してください。

于 2008-11-30T13:45:52.493 に答える
41

いくつかの単純なケースは、データベース スキーマでデフォルトを定義することで処理できますが、計算された値や他のモデルのキーを含む多くのトリッキーなケースは処理できません。これらの場合、私はこれを行います:

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
于 2012-07-18T14:36:30.567 に答える
18

after_initializeコールバックパターンは、次のようにするだけで改善できます。

after_initialize :some_method_goes_here, :if => :new_record?

initコードが関連付けを処理する必要がある場合、これには重要な利点があります。次のコードは、関連付けを含めずに最初のレコードを読み取ると、微妙なn+1をトリガーするためです。

class Account

  has_one :config
  after_initialize :init_config

  def init_config
    self.config ||= build_config
  end

end
于 2012-09-14T05:37:50.120 に答える
16

Phusion の連中は、このための優れたプラグインをいくつか持っています。

于 2008-11-30T09:42:56.740 に答える
8

宝石を使っていますattribute-defaults

ドキュメントから: アプリを実行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"
于 2013-01-11T04:00:59.257 に答える
8

同様の質問ですが、すべてコンテキストが少し異なります: - Railsのアクティブレコードのモデルで属性のデフォルト値を作成するにはどうすればよいですか?

ベストアンサー:ご希望によります!

すべてのオブジェクトを値で開始したい場合: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 

すべてのオブジェクトにユーザー入力から計算された値を持たせたい場合:before_save :default_values You want user to enter使用X、次にY = X+'foo'?使用する:

class Task < ActiveRecord::Base
  before_save :default_values
  def default_values
    self.status ||= 'P'
  end
end
于 2016-12-22T22:05:59.183 に答える
8

提案された回答よりもさらに優れた/よりクリーンな潜在的な方法は、次のようにアクセサーを上書きすることです。

def status
  self['status'] || ACTIVE
end

ActiveRecord::Base ドキュメントの「Overwriting default accessors」と、self の使用に関する StackOverflow の詳細を参照してください

于 2014-08-11T18:55:54.763 に答える
4

これがコンストラクターの目的です。モデルのinitializeメソッドをオーバーライドします。

メソッドを使用しafter_initializeます。

于 2008-11-30T10:07:56.567 に答える
4

Supみんな、私は最終的に次のことをしました:

def after_initialize 
 self.extras||={}
 self.other_stuff||="This stuff"
end

魔法のように動作します!

于 2010-07-27T06:36:21.427 に答える
3

これは長い間回答されてきましたが、デフォルト値が頻繁に必要になり、データベースに入れたくないのです。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
于 2016-02-14T21:35:41.097 に答える
2

after_initialize複雑な検索を行うActiveModel::MissingAttributeErrorときにエラーが発生するという問題に遭遇しました。

例えば:

@bottles = Bottle.includes(:supplier, :substance).where(search).order("suppliers.name ASC").paginate(:page => page_no)

.where条件の is ハッシュの「検索」

だから私はこの方法で初期化をオーバーライドすることでそれをやった:

def initialize
  super
  default_values
end

private
 def default_values
     self.date_received ||= Date.current
 end

この呼び出しは、カスタマイズ コードを実行する前にsuper、オブジェクトが正しく初期化されていることを確認するために必要です。つまり、default_valuesActiveRecord::Base

于 2011-02-09T10:34:01.957 に答える
1
class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
end
于 2008-11-30T13:43:00.313 に答える
1

after_initialize ソリューションの問題は、この属性にアクセスするかどうかに関係なく、DB から検索するすべてのオブジェクトに after_initialize を追加する必要があることです。遅延ロードのアプローチをお勧めします。

属性メソッド (getter) はもちろんメソッド自体であるため、それらをオーバーライドしてデフォルトを提供できます。何かのようなもの:

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 がサポートしている場合は、データベースの制約にデフォルトを設定する必要があると思います。

于 2012-09-08T00:43:17.027 に答える
1

after_initialize メソッドは非推奨です。代わりにコールバックを使用してください。

after_initialize :defaults

def defaults
  self.extras||={}
  self.other_stuff||="This stuff"
end

ただし、移行で:defaultを使用するのが最もクリーンな方法です。

于 2010-10-17T01:59:13.850 に答える
1

「default_value_for」ジェムを使用することを強くお勧めします: https://github.com/FooBarWidget/default_value_for

その宝石が行う初期化メソッドをオーバーライドする必要があるいくつかのトリッキーなシナリオがあります。

例:

あなたのデータベースのデフォルトはNULLで、モデル/ルビー定義のデフォルトは「何らかの文字列」ですが、実際には何らかの理由で値をnilに設定したい:MyModel.new(my_attr: nil)

ここでのほとんどのソリューションでは、値を nil に設定できず、代わりにデフォルトに設定されます。

では、アプローチを取る代わりに...||=に切り替えます。my_attr_changed?

しかし、あなたのデータベースのデフォルトが「何らかの文字列」であり、モデル/ルビー定義のデフォルトが「他の文字列」であると想像してください。しかし、特定のシナリオでは値を「何らかの文字列」(データベースのデフォルト)に設定したいとします:MyModel.new(my_attr: 'some_string')

値がdbのデフォルトと一致するため、これはmy_attr_changed?falseなります。これにより、Rubyで定義されたデフォルトコードが起動され、値が「他の文字列」に設定されます-これも、希望したものではありません.


これらの理由から、after_initialize フックだけでこれを適切に達成できるとは思いません。

繰り返しますが、「default_value_for」ジェムは正しいアプローチを取っていると思います: https://github.com/FooBarWidget/default_value_for

于 2015-09-18T05:02:02.637 に答える
0

ほとんどの場合、デフォルト値を設定するためにこれを行うことは混乱を招き、厄介ですが、使用することもできます:default_scopeここでsquilのコメントをチェックしてください。

于 2010-07-21T12:57:32.653 に答える
0

列がたまたま「ステータス」タイプの列であり、モデルがステート マシンの使用に適している場合は、aasm gemの使用を検討してください。その後、単純に実行できます。

  aasm column: "status" do
    state :available, initial: true
    state :used
    # transitions
  end

保存されていないレコードの値はまだ初期化されませんが、独自のものをロールバックするよりも少しクリーンでinitあり、すべてのステータスのスコープなど、aasm の他の利点を享受できます。

于 2013-11-11T22:30:44.057 に答える
0

検証方法を使用すると、デフォルトの設定を大幅に制御できることがわかりました。更新のデフォルトを設定する (または検証に失敗する) こともできます。本当に必要な場合は、挿入と更新に異なるデフォルト値を設定することもできます。デフォルトは #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 によって返される各オブジェクトによっても呼び出されるため、パフォーマンスの問題が発生する可能性があります。

于 2011-03-30T21:24:23.197 に答える
0

しばらくこれを使用しています。

# post.rb
class Post < ApplicationRecord
  attribute :country, :string, default: 'ID'
end
于 2020-10-22T22:26:17.353 に答える
0

Rails 6 アプリケーションに取り組んでいるときにも、同様の課題がありました。

これが私がそれを解決した方法です

私はUsersテーブルとテーブルを持っていRolesます。テーブルはUsersテーブルに属しRolesます。テーブルから継承するモデルもありAdminます。StudentUsers

次に、ユーザーが作成されるたびにロールのデフォルト値を設定する必要がありました。たとえば、id =を持つロールまたはadminid =を持つロールです。1student2

class User::Admin < User
  before_save :default_values

  def default_values
    # set role_id to '1' except if role_id is not empty
    return self.role_id = '1' unless role_id.nil?
  end
end

これは、adminユーザーがデータベースに作成/保存される前に、空でない場合はrole_idデフォルトに設定されることを意味します。1

return self.role_id = '1' unless role_id.nil? 

以下と同じです:

return self.role_id = '1' unless self.role_id.nil?

と同じ:

self.role_id = '1' if role_id.nil?

しかし、最初のものはよりクリーンで正確です。

それで全部です。

これが役立つことを願っています

于 2020-06-23T15:12:32.880 に答える
-2

Rails 3 で default_scope を使用する

API ドキュメント

ActiveRecord は、データベースで定義されたデフォルト設定 (スキーマ) とアプリケーションで行われたデフォルト設定 (モデル) の違いをあいまいにします。初期化中に、データベース スキーマを解析し、そこに指定されているデフォルト値を記録します。後でオブジェクトを作成するときに、データベースに触れることなく、スキーマで指定されたデフォルト値を割り当てます。

討論

于 2011-02-03T21:50:12.790 に答える
-2

API ドキュメントhttp://api.rubyonrails.org/classes/ActiveRecord/Callbacks.htmlから モデルでメソッドを 使用するbefore_validationと、作成および更新呼び出しの特定の初期化を作成するオプションが提供されます。 APIドキュメントの例から)番号フィールドはクレジットカード用に初期化されています。これを簡単に調整して、必要な値を設定できます

class CreditCard < ActiveRecord::Base
  # Strip everything but digits, so the user can specify "555 234 34" or
  # "5552-3434" or both will mean "55523434"
  before_validation(:on => :create) do
    self.number = number.gsub(%r[^0-9]/, "") if attribute_present?("number")
  end
end

class Subscription < ActiveRecord::Base
  before_create :record_signup

  private
    def record_signup
      self.signed_up_on = Date.today
    end
end

class Firm < ActiveRecord::Base
  # Destroys the associated clients and people when the firm is destroyed
  before_destroy { |record| Person.destroy_all "firm_id = #{record.id}"   }
  before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
end

彼がここで提案されていないことに驚いた

于 2012-05-31T18:43:21.200 に答える