Как я могу установить значения по умолчанию в ActiveRecord? - PullRequest
402 голосов
/ 30 ноября 2008

Как установить значение по умолчанию в ActiveRecord?

Я вижу сообщение от Пратика, которое описывает уродливый, сложный кусок кода: 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?

Ответы [ 26 ]

544 голосов
/ 26 февраля 2011

Есть несколько проблем с каждым из доступных методов, но я считаю, что определение обратного вызова after_initialize - это путь по следующим причинам:

  1. default_scope инициализирует значения для новых моделей, но затем это станет областью, в которой вы найдете модель. Если вы просто хотите инициализировать некоторые числа в 0, тогда это , а не , что вы хотите.
  2. Определение значений по умолчанию в вашей миграции также работает частично ... Как уже упоминалось, это будет не работать, когда вы просто вызываете Model.new.
  3. Переопределение initialize может работать, но не забудьте позвонить super!
  4. Использование плагинов, подобных phusion, становится немного нелепым. Это ruby, нужен ли нам плагин только для инициализации некоторых значений по умолчанию?
  5. Переопределение after_initialize устарело с Rails 3. Когда я переопределяю after_initialize в рельсах 3.0.3, я получаю следующее предупреждение в консоли:

ПРЕДУПРЕЖДЕНИЕ ОБ УСТРОЙСТВЕ: Base # after_initialize устарела, используйте взамен Base.after_initialize: метод. (вызывается из / 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. Для логических полей:

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

    Подробнее см. Комментарий Пола Рассела к этому ответу

  2. Если вы выбираете только подмножество столбцов для модели (то есть, используя select в запросе, подобном Person.select(:firstname, :lastname).all), вы получите 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 был другим (см. Комментарий Клиффа Дарлинга ниже)

46 голосов
/ 30 ноября 2008

Мы помещаем значения по умолчанию в базу данных посредством миграций (указав параметр :default для каждого определения столбца) и позволяем Active Record использовать эти значения для установки значения по умолчанию для каждого атрибута.

ИМХО, этот подход соответствует принципам AR: соглашение по конфигурации, DRY, определение таблицы определяет модель, а не наоборот.

Обратите внимание, что значения по умолчанию все еще находятся в коде приложения (Ruby), но не в модели, а в миграциях.

42 голосов
/ 19 апреля 2017

Рельсы 5 +

Вы можете использовать атрибут атрибут в своих моделях, например ::

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

Вы также можете передать лямбду параметру default. Пример:

attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }
39 голосов
/ 18 июля 2012

Некоторые простые случаи могут быть обработаны путем определения значения по умолчанию в схеме базы данных, но это не обрабатывает ряд более сложных случаев, включая вычисленные значения и ключи других моделей. Для этих случаев я делаю это:

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 не предусмотрен для этого очевидного варианта использования, но я обошелся, подтвердив, сохранился ли объект, указав, что он не новый.

Увидев ответ Брэда Мюррея, становится еще чище, если условие перенесено в запрос обратного вызова:

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
17 голосов
/ 14 сентября 2012

Шаблон обратного вызова after_initialize можно улучшить, просто сделав следующее

after_initialize :some_method_goes_here, :if => :new_record?

Это имеет нетривиальное преимущество, если ваш код инициализации должен иметь дело с ассоциациями, так как следующий код вызывает тонкое n + 1, если вы читаете исходную запись, не включая связанную.

class Account

  has_one :config
  after_initialize :init_config

  def init_config
    self.config ||= build_config
  end

end
16 голосов
/ 30 ноября 2008

У ребят из Phusion есть для этого хороший плагин .

8 голосов
/ 11 января 2013

Я использую 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"
8 голосов
/ 11 августа 2014

Еще лучше / чище потенциальный способ, чем предложенные ответы, - переписать метод доступа, например так:

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

См. «Перезапись средств доступа по умолчанию» в документации ActiveRecord :: Base и больше от StackOverflow при использовании self .

7 голосов
/ 23 декабря 2016

Подобные вопросы, но все они имеют немного другой контекст: - Как создать значение по умолчанию для атрибутов в модели 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
4 голосов
/ 30 ноября 2008

Для этого и нужны конструкторы! Переопределить метод initialize модели.

Используйте метод after_initialize.

...