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

108

Я пытаюсь найти лучший способ установить значения по умолчанию для объектов в Rails.

Лучшее, что я могу придумать, - это установить значение по умолчанию в newметоде контроллера.

Есть ли у кого-нибудь какие-либо комментарии, если это приемлемо или есть лучший способ сделать это?

biagidp
источник
1
Что это за объекты; как они потребляются / используются? Используются ли они при рендеринге представлений или для логики контроллера?
Gishu
3
Если вы говорите об объекте ActiveRecord, я должен сказать вам, что нет разумного решения проблемы «значений по умолчанию». Только безумные хаки, и авторы rails, похоже, не думают, что эта функция того стоит (удивительно, как только сообщество rails ..)
Маурисио
Поскольку принятые и большинство ответов сосредоточены на ActiveRecords, мы предполагаем, что исходный вопрос был об AcitveRecords. Следовательно, возможный дубликат stackoverflow.com/questions/328525/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Ответы:

98

«Правильный» - опасное слово в Ruby. Обычно есть несколько способов сделать что-либо. Если вы знаете, что вам всегда нужно это значение по умолчанию для этого столбца в этой таблице, установить их в файле миграции БД - самый простой способ:

class SetDefault < ActiveRecord::Migration
  def self.up
    change_column :people, :last_name, :type, :default => "Doe"
  end

  def self.down
    # You can't currently remove default values in Rails
    raise ActiveRecord::IrreversibleMigration, "Can't remove the default"
  end
end

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

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

class GenericPerson < Person
  def initialize(attributes=nil)
    attr_with_defaults = {:last_name => "Doe"}.merge(attributes)
    super(attr_with_defaults)
  end
end

Затем, когда вы выполняете a GenericPerson.new(), атрибут "Doe" всегда будет повышаться до, Person.new()если вы не замените его чем-то другим.

SFEley
источник
2
Попытайся. Он будет работать с новыми объектами модели, вызываемыми .newметодом класса. В этом сообщении блога обсуждение прямого вызова ActiveRecord касалось .allocateобъектов модели, загруженных с существующими данными из базы данных. ( И это ужасная идея для ActiveRecord работать таким образом, IMO. Но это не относится к
делу
2
Если вы сделаете шаг назад, чтобы еще раз прочитать вопрос автора, Никита, а затем мои комментарии по порядку, это может иметь для вас больше смысла. Если нет ... Что ж, вопрос получен. Хорошего дня.
SFEley
8
Лучше для миграции вниз: change_column_default :people, :last_name, nil stackoverflow.com/a/1746246/483520
Нолан Эми
9
Обратите внимание, что для более новых версий рельсов вам потребуется дополнительный параметр типа перед параметром по умолчанию. Искать: введите на этой странице.
Ben Wheeler
3
@JoelBrewer Я столкнулся с этим сегодня - для Rails 4 вам также нужно указать тип столбца:change_column :people, :last_name, :string, default: "Doe"
GoBusto
56

Основываясь на ответе SFEley, вот обновленный / исправленный для новых версий Rails:

class SetDefault < ActiveRecord::Migration
  def change
    change_column :table_name, :column_name, :type, default: "Your value"
  end
end
J -_- L
источник
2
Помните, что это НЕ работает в Rails 4.2.x (не тестировалось в более поздних версиях). поскольку это change_columnможет быть вполне «структурирование», обратная операция не может быть выведена. Если вы не уверены, просто протестируйте его, запустив db:migrateи db:rollbackсразу после этого. Принятый ответ - тот же результат, но по крайней мере предполагаемый!
gfd
1
Это правильный ответ для меня .. Он работает с rails 5.1, но вам нужно будет сделать upи downкак описано выше от @GoBusto
Фабрицио Бертольо
22

Во-первых, нельзя перегружать, так initialize(*args)как он вызывается не во всех случаях.

Лучше всего использовать настройки по умолчанию при миграции:

add_column :accounts, :max_users, :integer, :default => 10

Второй вариант - поместить в модель значения по умолчанию, но это будет работать только с атрибутами, которые изначально равны нулю. У вас могут возникнуть проблемы, как у меня со booleanстолбцами:

def after_initialize
  if new_record?
    max_users ||= 10
  end
end

Вам нужно, new_record?чтобы значения по умолчанию не переопределяли значения, загруженные из базы данных.

Вам нужно ||=запретить Rails переопределять параметры, переданные в метод инициализации.

Ян Пёртон
источник
12
небольшое примечание - вы хотите сделать две вещи: .... 1) не вызывайте свой метод after_initialize. Вы хотите after_initiation :your_method_name.... 2 использованияself.max_users ||= 10
Джесси Уолгамотт
5
Для логических значений просто сделайте следующее: prop = true if prop.nil?
Фрэнсис Поттер
2
или after_initialize doвместоdef after_initialize
fotanus 07
self.max_users на всякий случай.
Кен Ратаначай С.
15

Вы также можете попробовать change_column_defaultсвои миграции (проверено в Rails 3.2.8):

class SetDefault < ActiveRecord::Migration
  def up
    # Set default value
    change_column_default :people, :last_name, "Smith"
  end

  def down
    # Remove default
    change_column_default :people, :last_name, nil
  end
end

change_column_default Документы по API Rails

Себастьян Пуйе
источник
1
Принятый ответ более полный, но мне нравится этот чистый и задокументированный ... Спасибо!
gfd
7

Если вы имеете в виду объекты ActiveRecord, у вас есть (более) два способа сделать это:

1. Используйте параметр: default в БД.

НАПРИМЕР

class AddSsl < ActiveRecord::Migration
  def self.up
    add_column :accounts, :ssl_enabled, :boolean, :default => true
  end

  def self.down
    remove_column :accounts, :ssl_enabled
  end
end

Подробнее здесь: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html

2. Используйте обратный вызов

НАПРИМЕР before_validation_on_create

Подробнее здесь: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#M002147

Влад Злотяну
источник
2
Разве проблема с использованием значений по умолчанию в базе данных не заключается в том, что они не устанавливаются, пока объект не будет сохранен? Если я хочу создать новый объект с заполненными значениями по умолчанию, мне нужно установить их в инициализаторе.
Rafe,
Логические значения по умолчанию не могут быть 1 или 0 - они должны иметь значение true или false (см. Ответ Силаса).
Jamon Holmgren
@JamonHolmgren Спасибо за замечание, исправил ответ :)
Влад Злотяну 03
Нет проблем @VladZloteanu
Холмгрен
7

В Ruby on Rails v3.2.8, используя after_initializeобратный вызов ActiveRecord, вы можете вызвать метод в своей модели, который назначит значения по умолчанию для нового объекта.

Обратный вызов after_initialize запускается для каждого объекта, который найден и создан средством поиска, при этом after_initialize также запускается после создания экземпляров новых объектов ( см. Обратные вызовы ActiveRecord ).

Итак, ИМО это должно выглядеть примерно так:

class Foo < ActiveRecord::Base
  after_initialize :assign_defaults_on_new_Foo
  ...
  attr_accessible :bar
  ...
  private
  def assign_defaults_on_new_Foo
    # required to check an attribute for existence to weed out existing records
    self.bar = default_value unless self.attribute_whose_presence_has_been_validated
  end
end

Foo.bar = default_valueдля этого экземпляра, если он не содержит attribute_whose_presence_has_been_validatedранее при сохранении / обновлении. default_valueБудет использоваться в сочетании с вашей точки зрения , чтобы сделать форму , используя default_valueдля barатрибута.

В лучшем случае это хакерство ...

ИЗМЕНИТЬ - использовать 'new_record?' чтобы проверить, создается ли экземпляр из нового вызова

Вместо проверки значения атрибута используйте new_record?встроенный метод с рельсами. Итак, приведенный выше пример должен выглядеть так:

class Foo < ActiveRecord::Base
  after_initialize :assign_defaults_on_new_Foo, if: 'new_record?'
  ...
  attr_accessible :bar
  ...
  private
  def assign_defaults_on_new_Foo
    self.bar = default_value
  end
end

Это намного чище. Ах, магия Rails - она ​​умнее меня.

ошибочный
источник
Это работает не так, как я ожидал - предполагалось, что значение по умолчанию будет только для нового, но оно также устанавливает значение по умолчанию для атрибута для каждого найденного и созданного объекта (т.е. записей, загруженных из базы данных). Вы можете проверить атрибут объекта на предмет значений перед назначением значения по умолчанию, но это не очень элегантное или надежное решение.
ошибочно
1
Почему вы рекомендуете after_initializeобратный звонок здесь? В документации Rails по обратным вызовам есть пример установки значения по умолчанию before_createбез дополнительных проверок условий
Dfr
@Dfr - я, должно быть, пропустил это, не могли бы вы дать мне ссылку на обзор, и я
обновлю
Да, вот api.rubyonrails.org/classes/ActiveRecord/Callbacks.html первый пример, классSubscription
Dfr
5
@Dfr - Причина, по которой мы используем after_initializeвместо before_createобратного вызова, заключается в том, что мы хотим установить значение по умолчанию для пользователя (для использования в представлении), когда они создают новые объекты. before_createОбратный вызов вызывается после того, как пользователь был отслужен новый объект, при условии их ввода, и представил объект для создания контроллера. Затем контроллер проверяет наличие before_createобратных вызовов. Это кажется нелогичным, но это предмет номенклатуры - before_createотносится к createдействию. Создание экземпляра нового объекта не createявляется объектом.
erroric
5

По крайней мере, для логических полей в Rails 3.2.6 это будет работать при вашей миграции.

def change
  add_column :users, :eula_accepted, :boolean, default: false
end

Установка 1или 0для значения по умолчанию здесь не сработает, поскольку это логическое поле. Это должно быть значение trueили false.

Силаш
источник
4

Если вы имеете дело с моделью, вы можете использовать API атрибутов в Rails 5+ http://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute

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

class StoreListing < ActiveRecord::Base
  attribute :country, :string, default: 'PT'
end
Пауло Фидальго
источник
2

Сгенерируйте миграцию и используйте change_column_defaultкратко и обратимо:

class SetDefaultAgeInPeople < ActiveRecord::Migration[5.2]
  def change
    change_column_default :people, :age, { from: nil, to: 0 }
  end
end
Пере Джоан Марторелл
источник
1

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

Есть несколько подходов к этому, этот плагин выглядит интересным вариантом.

Paulthenerd
источник
1
Я думаю, вам не следует полагаться на базу данных, чтобы иметь дело со значениями по умолчанию и ограничениями, все это должно быть отсортировано на уровне модели. Этот плагин работает только с моделями ActiveRecord, это не общий способ установки значений по умолчанию для объектов.
Lukas Stejskal
Я бы сказал, что это зависит от типов значений по умолчанию, которые вы пытаетесь использовать, это не то, что мне нужно было делать очень часто, но я бы сказал, что ограничения на самом деле намного лучше, если они будут установлены как в базе данных, так и модель - чтобы ваши данные не стали недействительными в базе данных.
paulthenerd
1

Предложение переопределить новый / инициализировать, вероятно, неполное. Rails (часто) вызывает allocate для объектов ActiveRecord, и вызовы allocate не приводят к вызовам инициализации.

Если вы говорите об объектах ActiveRecord, обратите внимание на переопределение after_initialize.

Эти сообщения в блоге (не мои) полезны:

Значения по умолчанию Конструкторы по умолчанию не вызываются

[Edit: SFEley указывает, что Rails действительно смотрит на значение по умолчанию в базе данных, когда создает экземпляр нового объекта в памяти - я этого не осознавал.]

Джеймс Мур
источник
1

Мне нужно было установить значение по умолчанию, как если бы оно было указано в качестве значения столбца по умолчанию в БД. Так он ведет себя вот так

a = Item.new
a.published_at # => my default value

a = Item.new(:published_at => nil)
a.published_at # => nil

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

class Item < ActiveRecord::Base
  def self.column_defaults
    super.merge('published_at' => Time.now)
  end
end

У меня отлично работает. (Рельсы 3.2.x)

Петр 'Бубак' Шедивы
источник
1

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

def status
  self['name_of_var'] || 'desired_default_value'
end

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

Питерфорд
источник
0

Я ответил на аналогичный вопрос здесь ... чистый способ сделать это - использовать Rails attr_accessor_with_default

class SOF
  attr_accessor_with_default :is_awesome,true
end

sof = SOF.new
sof.is_awesome

=> true

ОБНОВИТЬ

attr_accessor_with_default устарел в Rails 3.2 .. вы можете сделать это вместо этого с чистым Ruby

class SOF
  attr_writer :is_awesome

  def is_awesome
    @is_awesome ||= true
  end
end

sof = SOF.new
sof.is_awesome

#=> true
Орландо
источник
attr_accessor_with_defaultустарело с rails> 3.1.0
flynfish
В вашем примере is_awesome всегда будет истинным, даже если @is_awesome == false.
Ritchie
-3

Вы можете переопределить конструктор для модели ActiveRecord.

Как это:

def initialize(*args)
  super(*args)
  self.attribute_that_needs_default_value ||= default_value
  self.attribute_that_needs_another_default_value ||= another_default_value
  #ad nauseum
end
Терри
источник
2
Это ужасное место для изменения функциональности основных рельсов. Существуют и другие, более стабильные, менее склонные к поломке других вещей, способы сделать это, упомянутые в других ответах. Исправление обезьяны должно быть только крайней мерой для вещей, которые невозможно сделать другим способом.
Will