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

StackOverflow https://stackoverflow.com/questions/328525

  •  11-07-2019
  •  | 
  •  

質問

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

Pratikの投稿で、complicatedい複雑なコードの塊について説明しています: 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

and

  def after_initialize 
    return unless new_record?
    self.status = ACTIVE
  end

また、人々が移行に使用するのを見たことがありますが、モデルコードで定義されていることを望んでいます。

ActiveRecordモデルのフィールドにデフォルト値を設定する標準的な方法はありますか?

役に立ちましたか?

解決

使用可能な各メソッドにはいくつかの問題がありますが、 after_initialize コールバックを定義することは、次の理由で進むべき方法だと思います:

  1. default_scope は新しいモデルの値を初期化しますが、それがモデルを見つけるスコープになります。いくつかの数字を0に初期化するだけの場合、これはあなたが望むものではありません。
  2. 移行のデフォルトを定義することも一部の場合に機能します...既に述べたように、Model.newを呼び出しただけでは、これは機能しません
  3. initialize のオーバーライドは機能しますが、 super を呼び出すことを忘れないでください!
  4. phusionのようなプラグインを使用するのは少しばかげています。これはルビーです、いくつかのデフォルト値を初期化するためだけにプラグインが本当に必要ですか?
  5. after_initialize をオーバーライドすると、廃止されます。Rails3.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    

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

警告:

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

    self.bool_field = self.bool_field.nilの場合はtrue?

    詳細については、この回答に関するPaul Russellのコメントを参照してください

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

    self.number || = 0.0 self.has_attribute?の場合:number

    およびブール列の場合...

    self.bool_field = true if(self.has_attribute?:bool_value)&amp;&amp; self.bool_field.nil?

    また、Rails 3.2より前の構文は異なることに注意してください(以下のCliff Darlingのコメントを参照してください)

他のヒント

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

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

デフォルトはアプリケーション(Ruby)コードにありますが、モデルではなく移行にあります。

レール5 +

属性を使用できますモデル内のメソッド。例:

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コードで関連付けを処理する必要がある場合、これは重要です。関連付けを含めずに初期レコードを読み取ると、次のコードが微妙なn + 1をトリガーします。

class Account

  has_one :config
  after_initialize :init_config

  def init_config
    self.config ||= build_config
  end

end

Phusionのメンバーには、このためのプラグインがあります。

>

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

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

「デフォルトアクセサの上書き」を参照してください。 ActiveRecord :: Baseドキュメントおよびselfの使用に関するStackOverflowの詳細

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 

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

class Task < ActiveRecord::Base
  before_save :default_values
  def default_values
    self.status ||= 'P'
  end
end

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

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

すみません、私は次のことをしました:

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

魅力のように動作します!

まず最初に:ジェフの答えには同意しません。アプリが小さく、ロジックがシンプルな場合に意味があります。ここでは、より大きなアプリケーションを構築および保守する際に、それがどのように問題になるかについての洞察を提供しようとしています。小さなものを構築するときは、最初にこのアプローチを使用することはお勧めしませんが、代替アプローチとしてそれを念頭に置いてください:


ここでの質問は、このレコードのデフォルトがビジネスロジックであるかどうかです。もしそうなら、ORMモデルに入れるのは慎重です。 rywが言及するフィールドはアクティブであるため、これはビジネスロジックのように聞こえます。例えば。ユーザーはアクティブです。

ビジネス上の懸念をORMモデルに入れることに慎重になるのはなぜですか?

  1. SRP に違反します。 ActiveRecord :: Baseを継承するクラスは、すでにさまざまなことを多く行っています。その主なものは、データの一貫性(検証)と永続性(保存)です。 AR :: Baseを使用してビジネスロジックを小さくしても、SRPが壊れます。

  2. テストに時間がかかります。 ORMモデルで発生する何らかのロジックをテストする場合、テストを実行するにはRailsを初期化する必要があります。これはアプリケーションの最初の段階ではあまり問題になりませんが、単体テストの実行に時間がかかるまで蓄積されます。

  3. これは、SRPをさらに具体的に破壊します。アイテムがアクティブになったときにユーザーにメールを送信する必要があるようになったとしますか?次に、アイテムORMモデルにメールロジックを追加します。このモデルの主な役割はアイテムのモデリングです。電子メールロジックを気にする必要はありません。これは、ビジネスの副作用の場合です。これらはORMモデルに属していません。

  4. 多様化することは困難です。初期化ロジックを制御することのみを目的とする、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

次に、これを配置して、新しいアイテムを作成する方法は次のようになります

SellableItemFactory.new

そして、私のテストでは、ItemFactoryに値がない場合、ItemFactoryがItemでアクティブに設定されていることを確認するだけでした。 Railsの初期化は不要で、SRPの破損もありません。アイテムの初期化がより高度になると(たとえば、ステータスフィールド、デフォルトタイプなどを設定する)、ItemFactoryはこれを追加できます。 2種類のデフォルトになった場合、これを行うために新しいBusinesCaseItemFactoryを作成できます。

注:ファクトリが多くのアクティブなものを構築できるように、ここで依存性注入を使用することも有益かもしれませんが、簡単にするために省略しました。ここにあります: self.new(klass =アイテム、属性= {})

これは長い間回答されてきましたが、デフォルト値が頻繁に必要であり、データベースに入れないことを好みます。 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 / schema.rb を見るだけでした。任意のモデルのDB。

@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レコードもこのフィールドのデフォルト値で更新されます。必要に応じて、移行でこのパラメーターを除外できますが、非常に便利です!

Rails 5+の標準的な方法は、@ Lucas Catonが言ったように:

class Item < ActiveRecord::Base
  attribute :scheduler_type, :string, default: 'hotseat'
end

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

複雑な検索を実行すると ActiveModel :: MissingAttributeError エラーが発生する after_initialize で問題が発生しました:

eg:

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

&quot;検索&quot; .where は条件のハッシュです

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

def initialize
  super
  default_values
end

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

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

class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
end

&quot; default_value_for&quot;を使用することを強くお勧めします。 gem: https://github.com/FooBarWidget/default_value_for

初期化メソッドをオーバーライドすることを必要とするトリッキーなシナリオがいくつかありますが、それはgemが行います。

例:

データベースのデフォルトはNULL、モデル/ルビ定義のデフォルトは&quot; some string&quot;ですが、実際には を使用して、何らかの理由で値をnilに設定します: MyModel.new (my_attr:nil)

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

OK、そのため || = アプローチをとる代わりに、 my_attr_changed? ...

に切り替えます。

BUT は、データベースのデフォルトが&quot; some string&quot;であり、モデル/ルビー定義のデフォルトが&quot; some other string&quot;であると想像しますが、特定のシナリオでは want を使用して値を&quot; some string&quot;に設定します(dbのデフォルト): MyModel.new(my_attr: 'some_string')

この結果、 my_attr_changed? false になります。これは、値がdbのデフォルトと一致するためです。これにより、ルビー定義のデフォルトコードが起動され、値が&quotに設定されます;その他の文字列&quot; -繰り返しますが、あなたが望むものではありません。


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

繰り返しますが、&quot; default_value_for&quot; gemは正しいアプローチを取っています: https://github.com/FooBarWidget/default_value_for

デフォルト値を設定するためにこれを行うと、ほとんどの場合、混乱を招き、扱いにくくなりますが、:default_scope も使用できます。 squilのコメントはこちらをご覧ください。

after_initializeメソッドは廃止されました。代わりにコールバックを使用してください。

after_initialize :defaults

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

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

検証メソッドを使用すると、デフォルト設定の多くの制御が提供されることがわかりました。更新のデフォルトを設定する(または検証に失敗する)こともできます。本当に必要な場合は、挿入と更新に異なるデフォルト値を設定します。 デフォルトは#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 などを使用して独自のロールを作成するよりも少しクリーンであり、すべてのステータス。

https://github.com/keithrowell/rails_default_value

class Task < ActiveRecord::Base
  default :status => 'active'
end

ここに私が使用した解決策がありますが、まだ追加されていないことに少し驚いていました。

これには2つの部分があります。最初の部分は実際の移行でデフォルトを設定し、2番目の部分はモデルに検証を追加して、存在が正しいことを確認します。

add_column :teams, :new_team_signature, :string, default: 'Welcome to the Team'

ここで、デフォルトがすでに設定されていることがわかります。検証では、文字列の値が常にあることを確認したいので、

 validates :new_team_signature, presence: true

これにより、デフォルト値が設定されます。 (私にとっては「チームへようこそ」とあります)、さらに一歩進んで、そのオブジェクトに常に値が存在することを確認します。

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

rails 3でdefault_scopeを使用

api doc

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

ディスカッション

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

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

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top