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

Как я могу установить значение по умолчанию в 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?


person ryw    schedule 30.11.2008    source источник
comment
Похоже, вы сами ответили на вопрос, в двух разных вариантах :)   -  person Adam Byrtek    schedule 30.11.2008
comment
Обратите внимание, что стандартная идиома Ruby для self.status = ACTIVE, если self.status не является self.status || = ACTIVE.   -  person Mike Woodhouse    schedule 30.11.2008
comment
Ответ Джеффа Перрина намного лучше, чем тот, который в настоящее время отмечен как принятый. default_scope - неприемлемое решение для установки значений по умолчанию, потому что оно имеет ОГРОМНЫЙ ПОБОЧНЫЙ ЭФФЕКТ, заключающийся в изменении поведения запросов.   -  person lawrence    schedule 12.03.2011
comment
см. также stackoverflow.com/questions/3975161/   -  person Viktor Trón    schedule 14.06.2012
comment
учитывая все положительные отзывы по этому вопросу, я бы сказал, что Ruby нужен метод setDefaultValue для ActiveRecord   -  person spartikus    schedule 17.12.2014
comment
лучший ответ: stackoverflow.com/a/41292328/1536309   -  person Blair Anderson    schedule 22.06.2017


Ответы (27)


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

  1. default_scope инициализирует значения для новых моделей, но затем это станет областью, в которой вы найдете модель. Если вы просто хотите инициализировать некоторые числа до 0, тогда это не то, что вам нужно.
  2. Определение значений по умолчанию в вашей миграции также работает некоторое время ... Как уже упоминалось, это не сработает, когда вы просто вызовете Model.new.
  3. Переопределение initialize может работать, но не забудьте позвонить super!
  4. Использование такого плагина, как phusion, становится немного смешным. Это рубин, действительно ли нам нужен плагин для инициализации некоторых значений по умолчанию?
  5. Переопределение after_initialize устарело в Rails 3. Когда я переопределяю after_initialize в rails 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 (см. Комментарий Клиффа Дарлинга ниже)

person Jeff Perrin    schedule 26.02.2011
comment
Это определенно кажется лучшим способом добиться этого. Что действительно странно и прискорбно. Разумный предпочтительный метод установки значений атрибутов модели по умолчанию при создании кажется чем-то, что Rails уже должен был встроить. Единственный другой (надежный) способ, переопределение initialize, кажется действительно запутанным для чего-то, что должно быть ясным и хорошо определенным. Я провел часы, просматривая документацию, прежде чем искать здесь, потому что я предполагал, что эта функция уже где-то там, и я просто не знал об этом. - person seaneshbaugh; 23.07.2011
comment
Одно замечание по этому поводу - если у вас есть логическое поле, которое вы хотите использовать по умолчанию, не делайте self.bool_field ||= true, так как это заставит поле иметь значение true, даже если вы явно инициализируете его значением false. Вместо этого сделайте self.bool_field = true if self.bool_field.nil?. - person Paul Russell; 21.08.2011
comment
Что касается пункта 2, Model.new действительно работает (только для меня?) Вместе со значениями по умолчанию, определенными в миграциях, или, точнее, со значениями по умолчанию для столбцов таблицы. Но я понимаю, что метод Джеффа, основанный на обратном вызове after_initialize, вероятно, лучший способ сделать это. Просто вопрос: работает ли он с грязными, но несохраненными объектами? В вашем примере будет ли Person.new.number_was возвращать 0,0? - person Laurent Farcy; 03.04.2012
comment
Одно дело, чтобы продолжить. Нужно ли нам добавлять before_save обратный вызов на случай, если кто-то испортил инициализированные данные и ввел некорректные данные, или, скорее, использовать проверку и позволить ей выйти из строя на save? - person O.O; 02.08.2012
comment
Если кто-то вручную обновляет поле до недопустимого значения, вы, вероятно, должны дать сбой проверки при сохранении. - person Jeff Perrin; 03.08.2012
comment
Будьте осторожны при использовании этого подхода в сочетании с выбором определенных столбцов с активной записью. В этом случае в объекте будут найдены только атрибуты, указанные в запросе, и код инициализации выдаст MissingAttributeError. Вы можете добавить дополнительную проверку, как показано: 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+ - раньше используйте self.attributes.has_key?, и вам нужна строка вместо символа. - person Cliff Darling; 22.10.2012
comment
Логический пример сэкономил мне много времени. Спасибо. - person shadowhorst; 19.12.2012
comment
Выполнение этого с ассоциациями будет стремиться загрузить эти ассоциации при поиске. Начните initialize с return if !new_record?, чтобы избежать проблем с производительностью. - person Kyle Macey; 02.04.2013
comment
@KyleMacey Хороший замечание, но возвращение на !new_record? может быть неразумным, если вы переходите с устаревшей базы данных, в которой могут быть некоторые строки со значениями nil вместо их значений по умолчанию из-за неправильных предыдущих миграций. В конце концов, было бы лучше обернуть значения ассоциаций по умолчанию в блок _2 _... - person hurikhan77; 30.07.2013
comment
Примечание: этот метод нельзя использовать вместе с default: в схеме, иначе схема по умолчанию будет использоваться всегда. - person Ciro Santilli 新疆再教育营六四事件ۍ 15.01.2014
comment
Почему бы просто не использовать after_create фильтр? В вашем решении метод init будет вызываться при каждом доступе к модели. - person Wojciech Bednarski; 21.09.2015
comment
@WojciechBednarski AFAIK, after_create вызывается после create, где ActiveRecord обращается к базе данных, поэтому, если вы обновляете что-либо в after_create, либо оно не сохраняется (может быть, это то, что вы хотите?), Либо вам нужен другой запрос к базе данных. Я бы предпочел before_create для этого. - person Franklin Yu; 23.09.2016
comment
NB: это решение может вызвать странное поведение, т. Е. выполнение Person.where('number IS NULL').count может вернуть количество записей, а Person.where('number IS NULL') вернет эти записи, но с их количеством, фактически установленным на 0.0, что довольно нелогично, поскольку вы ожидаете, что их будет nil. - person Magne; 22.05.2017
comment
Пункт 2 больше не применяется. Думаю, ребята из Rails реализовали это. При определении :default в миграциях thenModel.new теперь фактически создаст объект со значением по умолчанию. Проверена работа в Rails 4.1.16. - person Magne; 22.05.2017

Рельсы 5+

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

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

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

attribute :uuid, :string, default: -> { SecureRandom.uuid }

Второй аргумент - это тип, и он также может быть экземпляром класса настраиваемого типа, например:

attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }
person Lucas Caton    schedule 19.04.2017
comment
ааааааааааааааааааааааааааааааааа жемчужина, которую я искал! по умолчанию тоже может пройти процесс, например по умолчанию: - ›{Time.current.to_date} - person schpet; 08.09.2017
comment
Обязательно укажите тип в качестве второго аргумента, в противном случае тип будет Value и приведение типов выполняться не будет. - person null; 20.11.2017
comment
к моему удовольствию, это также работает с store_accessor, например, учитывая store_accessor :my_jsonb_column, :locale, вы можете определить attribute :locale, :string, default: 'en' - person Ryan Romanchuk; 31.08.2019
comment
О, это фантастика, мне нужно было показать в форме значения по умолчанию, и это отлично работает. Спасибо, Лукас. - person Paul Watson; 27.02.2020
comment
Их все еще можно установить на nil. Если они не могут быть nil DB not null + DB default + github.com/sshaw/keep_defaults - это способ исходя из моего опыта - person sshaw; 03.03.2020
comment
При использовании этого метода атрибутов необходимо ли еще добавить значения по умолчанию к миграции? - person Aboozar Rajabi; 09.07.2020
comment
Принимая во внимание, что это предположение, если у вас есть существующие объекты в базе данных, которые были созданы до того, как этот код был добавлен (например, если вы добавляете новый столбец, который вы хотите использовать по умолчанию), они НЕ получат это значение, когда вы загрузите их в . Но если вы воспользуетесь методом after_initialize, они будут - person phil; 20.10.2020

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

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

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

person Laurent Farcy    schedule 30.11.2008
comment
Другая проблема - когда вам нужно значение по умолчанию для внешнего ключа. Вы не можете жестко закодировать значение идентификатора в поле внешнего ключа, потому что в разных БД идентификатор может отличаться. - person shmichael; 30.08.2010
comment
еще одна проблема заключается в том, что таким образом вы не можете инициализировать непостоянные средства доступа (атрибуты, не являющиеся столбцами db). - person Viktor Trón; 13.05.2011
comment
Другая проблема заключается в том, что вы не всегда можете увидеть все значения по умолчанию в одном месте. они могут быть разбросаны по разным миграциям. - person declan; 23.06.2011
comment
declan, есть db / schema.rb - person Benjamin Atkin; 31.10.2011
comment
Хочу отметить для будущих читателей: по крайней мере, из того, что я прочитал, это противоречит принципам AR. Логика моделей должна лежать в классах моделей, а базы данных должны быть как можно более неосведомленными. Для меня значения по умолчанию представляют собой конкретную логику модели. - person darethas; 07.12.2013
comment
другая проблема - это время, когда вы хотите добавить новое поле со значениями по умолчанию в таблицу, в которой уже работает более 1 миллиона записей, т.е. обновление существующих записей займет некоторое время ... - person wik; 23.08.2017
comment
@wik Я согласен, что это займет некоторое время, но как насчет непредвиденных последствий неправильной инициализации ваших новых столбцов для всех существующих записей? Обычно я управляю записями в базе данных с помощью некоторых простых SQL-запросов, особенно в режиме чтения (SELECT). Для отладки или отчетов. Если значение по умолчанию останется на уровне модели Ruby, я не получу правильного результата. Мне кажется, какой бы метод инициализации вы ни использовали, вы должны позаботиться о правильной инициализации существующих записей в базе данных, когда вы вводите новый столбец. - person Laurent Farcy; 04.09.2017
comment
Случай, на который указывает wik, - это всего лишь теоретический сценарий, если вы играете точно и строго по правилам в тупой манере. В реальном мире вы запускаете миграцию в dev, быстро тестируете env, так как у них не должно быть миллионов записей. А в stag / prod вы запускаете сценарий SQL со знанием плана без блокировок и с окном обслуживания. Тем не менее, такое обновление не должно стоить так много времени. - person Andre Figueiredo; 12.07.2019
comment
Для статических значений по умолчанию это работает, но одна проблема возникает, когда вы хотите использовать что-то вроде now(). AR автоматически извлекает статические значения по умолчанию в качестве значений в новых записях, но не в вызовы функций db. Check.new.number # => 0. Check.new.timestamp # => nil. - person lobati; 11.02.2021

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

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
person Joseph Lord    schedule 18.07.2012
comment
Это действительно важный момент. Я должен представить, что в большинстве случаев установка значения по умолчанию для записи должна выполняться только перед сохранением новой записи, а не при загрузке постоянной записи. - person Russell Silva; 17.12.2013
comment
Спасибо, чувак, ты спас мне день. - person stephanfriedrich; 22.12.2014
comment
Как насчет :before_create? - person Franklin Yu; 23.09.2016
comment
Как: before_create обрабатывает отдельные вызовы new и save? Я хотел бы проверить это и действительно понять, прежде чем переходить на это. - person Joseph Lord; 05.10.2016

Шаблон обратного вызова 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
person Brad Murray    schedule 14.09.2012

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

person Milan Novota    schedule 30.11.2008
comment
Обратите внимание, что этот плагин позволяет значениям :default в миграциях схемы «просто работать» с Model.new. - person jchook; 02.02.2014
comment
Я могу заставить значения :default в миграциях «просто работать» с Model.new, вопреки тому, что Джефф сказал в своем сообщении. Проверена работа в Rails 4.1.16. - person Magne; 22.05.2017

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

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

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

person peterhurford    schedule 11.08.2014
comment
В хэше, возвращенном от attributes, статус по-прежнему будет нулевым. Тестировал в рельсах 5.2.0. - person spyle; 20.06.2018

Я использую 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"
person aidan    schedule 11.01.2013

Похожие вопросы, но все имеют немного другой контекст: - Как мне создать значение по умолчанию для атрибутов в модели 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
person Blair Anderson    schedule 22.12.2016

Я также видел, как люди добавляли его в свою миграцию, но я бы предпочел, чтобы он был определен в коде модели.

Есть ли канонический способ установить значение по умолчанию для полей в модели ActiveRecord?

Канонический способ Rails до Rails 5 состоял в том, чтобы установить его при миграции и просто смотреть в db/schema.rb всякий раз, когда вы хотите увидеть, какие значения по умолчанию устанавливаются БД для любой модели.

Вопреки тому, что указано в ответе @Jeff Perrin (что немного устарело), ​​подход к миграции даже применит значение по умолчанию при использовании Model.new из-за некоторой магии Rails. Проверена работа в Rails 4.1.16.

Самое простое часто бывает лучшим. Меньше долга знаний и потенциальных точек путаницы в кодовой базе. И это «просто работает».

class AddStatusToItem < ActiveRecord::Migration
  def change
    add_column :items, :scheduler_type, :string, { null: false, default: "hotseat" }
  end
end

Или, для изменения столбца без создания нового, выполните одно из следующих действий:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column_default :items, :scheduler_type, "hotseat"
  end
end

Или, может быть, даже лучше:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column :items, :scheduler_type, :string, default: "hotseat"
  end
end

Обратитесь к официальному руководству RoR, чтобы узнать о вариантах методов изменения столбцов.

null: false запрещает значения NULL в БД, и, в качестве дополнительного преимущества, он также обновляется, так что для всех ранее существовавших записей БД, которые ранее были нулевыми, также устанавливается значение по умолчанию для этого поля. Вы можете исключить этот параметр из миграции, если хотите, но мне это очень удобно!

Канонический способ в Rails 5+, как сказал @Lucas Caton:

class Item < ActiveRecord::Base
  attribute :scheduler_type, :string, default: 'hotseat'
end
person Magne    schedule 22.05.2017

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

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

person John Topley    schedule 30.11.2008
comment
Обычно вы правы, но никогда не следует переопределять инициализацию в модели ActiveRecord, поскольку она не всегда может вызываться. Вместо этого вы должны использовать метод after_initialize. - person Luke Redpath; 13.11.2009
comment
Использование default_scope только для установки значения по умолчанию ОЧЕНЬ неправильно. after_initialize - правильный ответ. - person joaomilho; 11.03.2011

Привет, ребята, в итоге я сделал следующее:

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

Работает как шарм!

person Tony    schedule 27.07.2010

На этот вопрос уже давно ответили, но мне часто нужны значения по умолчанию, и я предпочитаю не помещать их в базу данных. Я создаю 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
person clem    schedule 14.02.2016

Я столкнулся с проблемами, когда after_initialize выдавал ActiveModel::MissingAttributeError ошибки при выполнении сложных поисков:

eg:

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

"поиск" в .where - это хеш условий

В итоге я сделал это, переопределив инициализацию следующим образом:

def initialize
  super
  default_values
end

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

Вызов super необходим, чтобы убедиться, что объект правильно инициализируется из ActiveRecord::Base перед выполнением моего кода настройки, то есть: default_values

person Sean    schedule 09.02.2011
comment
Мне это нравится. Мне нужно было сделать def initialize(*); super; default_values; end в Rails 5.2.0. Кроме того, значение по умолчанию доступно даже в хэше .attributes. - person spyle; 20.06.2018

after_initialize устарел, используйте вместо него обратный вызов.

after_initialize :defaults

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

однако использование : default при переносе по-прежнему является самым чистым способом.

person Greg    schedule 17.10.2010
comment
В Rails 3: after_initialize метод НЕ устарел. Фактически, обратный вызов в стиле макроса вы даете пример того, что устарел. Подробности: guides.rubyonrails.org/ - person Zabba; 28.12.2010

Проблема с решениями after_initialize заключается в том, что вам нужно добавить 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'). В этом случае я думаю, вам действительно нужно установить ограничения по умолчанию в вашей базе данных, если БД поддерживает это.

person Jeff Gran    schedule 08.09.2012
comment
Это решение и предлагаемая альтернатива не работают в моем случае: у меня есть иерархия классов STI, в которой только один класс имеет этот атрибут и соответствующий столбец, который будет использоваться в условиях запроса БД. - person cmoran92; 20.03.2019

Я настоятельно рекомендую использовать драгоценный камень default_value_for: https://github.com/FooBarWidget/default_value_for

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

Примеры:

По умолчанию для вашей базы данных установлено значение NULL, по умолчанию для вашей модели / рубина является "некоторая строка", но на самом деле вы хотите установить значение nil по любой причине: MyModel.new(my_attr: nil)

В большинстве решений здесь не будет установлено значение nil, вместо этого будет установлено значение по умолчанию.

Хорошо, поэтому вместо подхода ||= вы переключаетесь на _3 _...

НО теперь представьте, что ваша база данных по умолчанию - это "некоторая строка", по умолчанию ваша модель / рубин - "некоторая другая строка", но при определенном сценарии вы хотите установить значение "некоторая строка" (по умолчанию db): MyModel.new(my_attr: 'some_string')

Это приведет к тому, что my_attr_changed? будет ложным, потому что значение соответствует значению по умолчанию в базе данных, что, в свою очередь, запустит ваш определяемый рубином код по умолчанию и установит значение "какая-то другая строка" - опять же, не то, что вы желанный.


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

Опять же, я думаю, что гем "default_value_for" использует правильный подход: https://github.com/FooBarWidget/default_value_for < / а>

person etipton    schedule 18.09.2015

Хотя это для установки значений по умолчанию в большинстве случаев сбивает с толку и неудобно, вы также можете использовать :default_scope. Ознакомьтесь с комментарием squil здесь.

person skalee    schedule 21.07.2010

Я обнаружил, что использование метода проверки дает большой контроль над установкой значений по умолчанию. Вы даже можете установить значения по умолчанию (или не пройти проверку) для обновлений. Вы даже можете установить другое значение по умолчанию для вставок и обновлений, если действительно хотите. Обратите внимание, что значение по умолчанию не будет установлено до #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

person Kelvin    schedule 30.03.2011
comment
не выполняется ли проверка только перед сохранением? Что, если вы хотите показать настройки по умолчанию перед сохранением? - person nurettin; 18.02.2013
comment
@nurettin Это хороший момент, и я понимаю, почему вы иногда этого хотите, но OP не упомянул об этом как о требовании. Вы должны решить для себя, хотите ли вы накладных расходов на установку значений по умолчанию для каждого экземпляра, даже если он не сохранен. Альтернативой является сохранение фиктивного объекта для повторного использования действия new. - person Kelvin; 19.02.2013

Если столбец является столбцом типа «статус», а ваша модель допускает использование конечных автоматов, рассмотрите возможность использования aasm gem, после чего вы можете просто сделать

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

Он по-прежнему не инициализирует значение для несохраненных записей, но он немного чище, чем использование собственного с init или чем-то еще, и вы получаете другие преимущества aasm, такие как области для всех ваших статусов.

person Bad Request    schedule 11.11.2013

https://github.com/keithrowell/rails_default_value

class Task < ActiveRecord::Base
  default :status => 'active'
end
person Keith Rowell    schedule 24.03.2016

Вот решение, которое я использовал и которое, как я был немного удивлен, еще не было добавлено.

Он состоит из двух частей. Первая часть устанавливает значение по умолчанию в фактической миграции, а вторая часть добавляет проверку в модель, гарантирующую, что присутствие истинно.

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

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

 validates :new_team_signature, presence: true

Это позволит установить для вас значение по умолчанию. (для меня у меня есть «Добро пожаловать в команду»), а затем он сделает еще один шаг, чтобы гарантировать, что для этого объекта всегда присутствует значение.

Надеюсь, это поможет!

person kdweber89    schedule 08.02.2019

У меня была аналогичная проблема, когда я работал над приложением Rails 6.

Вот как я это решил:

У меня есть таблица Users и таблица Roles. Таблица Users принадлежит таблице Roles. У меня также есть модели Admin и Student, которые наследуются от таблицы Users.

Затем потребовалось, чтобы я устанавливал значение по умолчанию для роли всякий раз, когда создается пользователь, скажем, admin роль, которая имеет роль id = 1 или student, которая имеет id = 2.

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?

но первый чище и точнее.

Это все.

Надеюсь, это поможет

person Promise Preston    schedule 23.06.2020

Пользуюсь этим какое-то время.

# post.rb
class Post < ApplicationRecord
  attribute :country, :string, default: 'ID'
end
person Yana Agun Siswanto    schedule 22.10.2020

используйте default_scope в рельсах 3

api doc

ActiveRecord скрывает разницу между значением по умолчанию, определенным в базе данных (схеме), и значением по умолчанию, выполненным в приложении (модели). Во время инициализации он анализирует схему базы данных и отмечает все указанные в ней значения по умолчанию. Позже, при создании объектов, он назначает эти значения по умолчанию, указанные в схеме, не касаясь базы данных.

обсуждение

person Viktor Trón    schedule 03.02.2011
comment
если вы используете meta_where, default_scope может не работать для назначения значений по умолчанию новым объектам AR из-за ошибки. - person Viktor Trón; 01.03.2011
comment
эта проблема с meta_where теперь исправлена ​​[metautonomous.lighthouseapp .com / projects / 53011 / билеты / - person Viktor Trón; 03.03.2011
comment
НЕ используйте default_scope. Это заставит все ваши запросы добавлять это условие в установленное вами поле. Это почти НИКОГДА не то, что вы хотите. - person brad; 14.06.2012
comment
@brad, забавно упомянуть, я полностью согласен, это зло :). см. мой комментарий в stackoverflow.com / questions / 10680845 /. - person Viktor Trón; 14.06.2012

Из документации api http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html Используйте метод before_validation в своей модели, он дает вам возможность создать конкретную инициализацию для вызовов create и update, например в этом примере (снова код, взятый из примера 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

Удивлен, что его здесь не предложили

person jamesc    schedule 31.05.2012
comment
before_validation не установит значения по умолчанию, пока объект не будет готов к сохранению. Если процессу необходимо прочитать значения по умолчанию перед сохранением, тогда значения не будут готовы. - person mmell; 31.08.2012
comment
В любом случае вы никогда не устанавливаете значения по умолчанию во время проверки. Это даже не взлом. Сделайте это во время инициализации - person Sachin; 07.02.2017

person    schedule
comment
Ммм ... сначала кажется гениальным, но, немного подумав, я вижу несколько проблем. Во-первых, все значения по умолчанию не находятся в одной точке, а разбросаны по классу (представьте, что вы их ищете или меняете). Во-вторых, что хуже всего, вы не можете впоследствии установить нулевое значение (или даже ложное!). - person paradoja; 30.11.2008
comment
зачем вам устанавливать по умолчанию нулевое значение? вы получаете это из коробки с AR, вообще ничего не делая. Что касается false при использовании логического столбца, то вы правы, это не лучший подход. - person Mike Breen; 30.11.2008
comment
Я не могу говорить о других привычках кодирования. У меня не было проблем, потому что я не разбрасываю свои геттеры / сеттеры по файлу класса. Кроме того, любой современный текстовый редактор должен упростить переход к методу (shift-cmd-t в textmate). - person Mike Breen; 30.11.2008
comment
@paradoja - я беру это обратно, теперь я вижу, где он ломается с использованием null. Необязательно использовать null по умолчанию, но если вы действительно хотите изменить значение на null в какой-то момент. Хороший улов @paradoja, спасибо. - person Mike Breen; 30.11.2008
comment
Я использую этот метод, так как он хорошо работает с динамически генерируемыми атрибутами. - person Dale Campbell; 02.11.2010