Як встановити значення за замовчуванням у Rails?


108

Я намагаюся знайти найкращий спосіб встановити значення за замовчуванням для об'єктів у Rails.

Найкраще, що я можу придумати, - це встановити значення за замовчуванням у newметоді в контролері.

Хтось має якісь дані, якщо це прийнятно або якщо є кращий спосіб зробити це?


1
Що це за об'єкти; як їх вживають / вживають? Чи використовуються вони під час надання подань або для логіки контролера?
Гішу

3
Якщо ви говорите про об'єкт ActiveRecord, я мушу сказати вам, що немає проблеми з розумним вирішенням проблеми "значення за замовчуванням". Тільки божевільні хаки, і автори рейок, здається, не вважають, що ця функція того варта (дивовижно, оскільки це лише спільнота рейок ..)
Mauricio

Оскільки прийняті та більшість відповідей зосереджені на ActiveRecords, ми припускаємо, що початкове питання стосувалося AcitveRecords. Тому можливо дублікат stackoverflow.com/questions/328525 / ...
Чіро Сантіллі郝海东冠状病六四事件法轮功

Відповіді:


98

"Правильно" - небезпечне слово в Рубі. Зазвичай існує більше ніж один спосіб зробити що-небудь. Якщо ви знаєте, що ви завжди хочете, що це значення за замовчуванням для цього стовпця в цій таблиці, встановлення їх у файлі міграції БД є найпростішим способом:

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

Тоді, коли ви робите це GenericPerson.new(), він завжди буде обробляти атрибут "Doe", Person.new()якщо ви не перекриєте його чимось іншим.


2
Спробуй це. Він буде працювати над новими об'єктами моделі, названими .newметодом class. Дискусія в блозі про прямий виклик ActiveRecord стосувалася .allocateмодельних об'єктів, завантажених наявними даними з бази даних. ( І для ActiveRecord це страшна ідея працювати таким чином, IMO. Але це вже не в цьому.)
SFEley

2
Якщо ви відступите, щоб знову прочитати запитання оригінального афіші, Микита, а потім мої коментарі для того, це може мати для вас більше сенсу. Якщо ні ... Ну, на питання відповіли. Гарного дня.
SFEley

8
Краще для міграції вниз: change_column_default :people, :last_name, nil stackoverflow.com/a/1746246/483520
Нолан Емі

9
Зауважте, що для новіших версій рейкової системи вам потрібен додатковий параметр типу перед параметром за замовчуванням. Шукати: введіть на цій сторінці.
Бен Вілер

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

2
Остерігайтеся, що це НЕ працює на Rails 4.2.x (не перевірено на пізніших версіях). як це change_columnможе бути досить "структуруванням", зворотну операцію неможливо зробити. Якщо ви не впевнені, просто протестуйте це, запустивши db:migrateі db:rollbackвідразу після. Відповідь прийнято як такий самий результат, але принаймні передбачається!
gfd

1
Це правильна відповідь для мене .. Він працює з рейками 5.1 , але вам потрібно буде робити upі downяк описано вище , з @GoBusto
Фабріціо Bertoglio

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, якщо prop.nil?
Френсіс Поттер

2
або after_initialize doзамістьdef after_initialize
fotanus

self.max_users для безпеки.
Ken Ratanachai S.

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

Документи API Rails change_column_default Rails


1
Прийнята відповідь є більш повною, але мені подобається ця чиста і документально підтверджена ... Дякую!
gfd

7

Якщо ви посилаєтесь на об’єкти ActiveRecord, у вас є (більше) два способи цього:

1. Використовуйте параметр: за замовчуванням у БД

EG

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. Використовуйте зворотний дзвінок

EG before_validation_on_create

Більше інформації тут: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#M002147


2
Чи не проблема з використанням за замовчуванням у базі даних, якщо вони не встановлюються, поки об’єкт не буде збережено? Якщо я хочу побудувати новий об'єкт із заповненими за замовчуванням, мені потрібно встановити їх у ініціалізаторі.
Rafe

Булеві значення за замовчуванням не можуть бути 1 або 0 - вони повинні бути встановлені на істинні чи помилкові (див. Відповідь Силаса).
Джеймон Холмгрен

@JamonHolmgren Дякую за зауваження, я виправив відповідь :)
Влад Злотеану

Без проблем @VladZloteanu
Jamon Holmgren

7

У Ruby on Rails v3.2.8 за допомогою after_initializeзворотного дзвінка ActiveRecord ви можете викликати метод у вашій моделі, який призначить новим об'єктам значення за замовчуванням.

after_initialize зворотний виклик спрацьовує для кожного об'єкта, який знайдений та інстанціюється шукачем, при цьому після_initialize буде ініційовано після ініціалізації нових об'єктів ( див. зворотні виклики ActiveRecord ).

Отже, IMO має виглядати приблизно так:

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атрибута.

У кращому випадку це хакі ...

EDIT - використовувати "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

Це набагато чистіше. Ах, магія Рейлів - вона розумніша за мене.


Це не працює, як я очікував - він повинен був встановити лише за замовчуванням новий, але він також встановлює за замовчуванням атрибут для кожного знайденого та інстанційного об'єкта (тобто записів, завантажених з db). Ви можете перевірити атрибут об'єкта, який шукає значення, перш ніж призначити типовий, але це не дуже елегантне або надійне рішення.
помилковий

1
Чому ви рекомендуєте after_initializeзворотний дзвінок тут? Документація про before_create
рейкові

@Dfr - Я, мабуть, пропустив це, чи можете ви кинути мені посилання на огляд, і я
оновлю

Так, ось api.rubyonrails.org/classes/ActiveRecord/Callbacks.html перший приклад, класSubscription
Дфр

5
@Dfr - Причиною того, що ми використовуємо after_initializeзамість before_createзворотного дзвінка, ми хочемо встановити для користувача значення за замовчуванням (для використання в режимі перегляду), коли він створює нові об'єкти. Зворотний before_createвиклик викликається після того, як користувачеві подається новий об'єкт, надається його введення та подається об'єкт для створення в контролер. Потім контролер перевіряє наявність before_createзворотних дзвінків. Це здається контрінтуїтивним, але це номенклатурна річ - before_createстосується createдії. Ігнорування нового об'єкта не createє об'єктом.
помилка

5

Щонайменше для булевих полів в Rails 3.2.6, це буде працювати при вашій міграції.

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

Поставити a 1або 0за замовчуванням тут не вийде, оскільки це булеве поле. Це повинно бути значення trueабо falseзначення.


4

Якщо ви маєте справу з моделлю, ви можете використовувати API Attriutes у 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 за замовчуванням - чи можете ви уточнити, які типи за замовчуванням ви використовуєте?

Існує цілий ряд підходів, щоб впоратися з цим, цей плагін виглядає як цікавий варіант.


1
Я думаю, ви не повинні залежати від бази даних, щоб мати справу з типовими умовами та обмеженнями, все це має бути відсортовано за шаром моделі. Цей плагін працює лише для моделей ActiveRecord, це не загальний спосіб встановлення за замовчуванням об’єктів.
Лукас Штейскал

Я б стверджував, що це залежить від типів за замовчуванням, які ви намагаєтеся використовувати, це не те, що мені потрібно робити дуже часто, але я б сказав, що обмеження насправді набагато краще встановлювати як в базі даних, так і в модель - щоб запобігти недійсності ваших даних у базі даних.
paulthenerd

1

Пропозиція про заміщення нового / ініціалізація, ймовірно, неповна. Rails буде (часто) виділяти виклики для об'єктів ActiveRecord, а виклики для розподілу не призведуть до ініціалізації викликів.

Якщо ви говорите про об'єкти ActiveRecord, погляньте на переоцінку after_initialize.

Ці повідомлення в блозі (не мої) корисні:

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

[Редагувати: 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застаріло на рейки> 3.1.0
мухомор

У вашому прикладі is_awesome завжди буде істинним, навіть коли @is_awesome == false.
Річі



-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
Це жахливе місце для зміни функціональності основних рейок. Є й інші набагато стійкіші, менш шанси порушити інші речі, способи зробити це згадані в інших відповідях. Виправлення мавп має бути лише в крайньому випадку для речей, які неможливо зробити іншим способом.
Буде
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.