web-dev-qa-db-ja.com

ActiveRecordでデフォルト値を設定する方法

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モデルのフィールドにデフォルト値を設定するための標準的な方法はありますか?

402
ryw

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

  1. default_scopeは新しいモデルの値を初期化しますが、それがモデルを見つけるスコープになります。いくつかの数字を0に初期化するだけの場合、これはnotです。
  2. マイグレーションでデフォルトを定義することも、一部の場合は機能します...すでに述べたように、Model.newを呼び出すだけで、これはnot動作します。
  3. initializeのオーバーライドは機能しますが、superを呼び出すことを忘れないでください!
  4. Phusionのようなプラグインを使用するのは少しばかげています。これはRubyです、いくつかのデフォルト値を初期化するために本当にプラグインが必要ですか?
  5. 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モデルの初期化を探す場所ができました。誰かがより良い方法を思いつくまで、私はこの方法を使用しています。

警告:

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

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

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

  2. モデルの列のサブセットのみを選択している場合(つまり、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のコメントを参照)

545
Jeff Perrin

マイグレーションによって(各列定義に:defaultオプションを指定することによって)データベースにデフォルト値を入れ、Active Recordにこれらの値を使用して各属性のデフォルト値を設定させます。

私見、このアプローチはARの原則と一致しています:設定上の規約、DRY、テーブル定義はモデルを動かしますが、その逆ではありません。

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

46
Laurent Farcy

レール5+

あなたのモデルの中で attribute メソッドを使うことができます。例えば:

class Account < ApplicationRecord
  attribute :locale, :string, default: 'en'
end

ラムダをdefaultパラメータに渡すこともできます。例:

attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }
42
Lucas Caton

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

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
39
Joseph Lord

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
17
Brad Murray

Phusionの人たちはこれに関していくつかのNice plugin を持っています。

16
Milan Novota

提案された答えよりももっと良い/よりきれいな可能な方法はこのようにアクセサを上書きすることです:

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

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

8
peterhurford

私は 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"
8
aidan

似たような質問ですが、状況は少し異なります。 - 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
7
Blair Anderson

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


ここでの問題は、このレコードのデフォルトがビジネスロジックかどうかです。もしそうなら、私はそれをORMモデルに入れるように注意するでしょう。 rywが述べているフィールドはアクティブなので、これはビジネスロジックのように思えます。例えば。ユーザーはアクティブです。

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

  1. 壊れる SRP 。 ActiveRecord :: Baseを継承するクラスは、すでに多くのさまざまな処理を行っています。その中で最も重要なのは、データの整合性(検証)と永続性(保存)です。ビジネスロジックを入れても小さくてもAR :: BaseでSRPを破る。

  2. テストは遅くなります。私のORMモデルで起こっている何らかの形のロジックをテストしたいのなら、私のテストは実行するためにRailsを初期化しなければなりません。これはアプリケーションの最初の段階ではあまり問題にはなりませんが、単体テストの実行に長い時間がかかるまで続きます。

  3. それはSRPをさらに詳細に、そして具体的な方法で破壊するでしょう。アイテムがアクティブになったときに、ビジネスでユーザーに電子メールを送信する必要があるとします。現在、私たちは電子メールロジックをItem ORMモデルに追加しています。その主な役割はItemのモデリングです。電子メールのロジックを気にするべきではありません。これはビジネス副作用の場合です。これらは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

それで、これをきちんとして、新しいItemを作成する方法は、

SellableItemFactory.new

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

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

4
Houen

みんな、私は次のことをやってしまいました:

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

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

4
Tony

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

after_initializeメソッドを使用してください。

4
John Topley

これは長い間回答されてきましたが、私はデフォルト値を頻繁に必要とし、データベースに入れないことを好みます。 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
3
clem

私はまた人々が彼らの移行にそれを入れているのを見ました、しかし私はむしろそれがモデルコードの中で定義されるのを見たいです。

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
3
Magne

after_initializeActiveModel::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

1
Sean

"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

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

  before_save{ self.status ||= ACTIVE }
end
1
Mike Breen

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

1
Jeff Gran

after_initializeメソッドは推奨されていません。代わりにコールバックを使用してください。

after_initialize :defaults

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

ただし、マイグレーションで:defaultを使用するのが、まだ最もクリーンな方法です。

0
Greg

これは私がまだ追加されていない少し驚いたという私が使用した解決策です。

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

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

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

 validates :new_team_signature, presence: true

これが何をするかはあなたのためにデフォルト値を設定することです。 (私には "Welcome to the Team"があります)そしてそれはさらに一歩進んで、そのオブジェクトには常に価値があることを確認します。

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

0
kdweber89

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

0
skalee

私は、検証方法を使用することでデフォルト設定を制御することができます。更新のデフォルトを設定する(または検証に失敗する)こともできます。本当にしたいのであれば、挿入と更新に異なるデフォルト値を設定することもできます。 #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

0
Kelvin

その列がたまたま「ステータス」タイプの列で、モデルがステートマシンの使用に適している場合は、 aasm gem を使用することを検討します。

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

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

0
Bad Request

https://github.com/keithrowell/Rails_default_value

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