Як встановити значення за замовчуванням у 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.