Jak ustawić domyślne wartości w Railsach?


108

Próbuję znaleźć najlepszy sposób na ustawienie wartości domyślnych dla obiektów w Railsach.

Najlepsze, co przychodzi mi do głowy, to ustawienie wartości domyślnej w newmetodzie w kontrolerze.

Czy ktoś ma jakiś wkład, jeśli jest to dopuszczalne lub czy istnieje lepszy sposób na zrobienie tego?


1
Co to za przedmioty; jak są one konsumowane / używane? Czy są używane podczas renderowania widoków, czy do logiki kontrolera?
— Gishu

3
Jeśli mówisz o obiekcie ActiveRecord, muszę powiedzieć, że nie ma rozsądnego rozwiązania problemu „wartości domyślnych”. Tylko szalone hacki, a autorzy railsów nie wydają się uważać, że ta funkcja jest tego warta (niesamowite, ponieważ tylko społeczność railsów jest ...)
— Mauricio

Ponieważ zaakceptowane i większość odpowiedzi skupia się na ActiveRecords, zakładamy, że pierwotne pytanie dotyczyło AcitveRecords. Dlatego możliwy duplikat stackoverflow.com/questions/328525/…
— Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Odpowiedzi:


98

„Prawidłowo” to niebezpieczne słowo w języku Ruby. Zwykle jest więcej niż jeden sposób na zrobienie czegokolwiek. Jeśli wiesz, że zawsze będziesz potrzebować tej domyślnej wartości dla tej kolumny w tej tabeli, ustawienie ich w pliku migracji bazy danych jest najłatwiejszym sposobem:

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

Ponieważ ActiveRecord automatycznie wykrywa właściwości tabeli i kolumny, spowoduje to ustawienie tego samego domyślnego w każdym modelu używającym go w dowolnej standardowej aplikacji Railsowej.

Jeśli jednak chcesz ustawić wartości domyślne tylko w określonych przypadkach - powiedzmy, że jest to model dziedziczony, który dzieli tabelę z innymi - to innym eleganckim sposobem jest zrobienie tego bezpośrednio w kodzie Railsów, gdy tworzony jest obiekt modelu:

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

Następnie, gdy zrobisz a GenericPerson.new(), zawsze przepłynie atrybut „Doe” do, Person.new()chyba że zastąpisz go czymś innym.


2
Spróbuj. Będzie działać na nowych obiektach modelu wywoływanych .newmetodą klasy. Omówienie tego posta na blogu na temat bezpośredniego wywoływania ActiveRecord .allocatedotyczyło obiektów modelu ładowanych istniejącymi danymi z bazy danych. ( I to okropny pomysł, aby ActiveRecord działał w ten sposób, IMO. Ale to nie ma znaczenia).
— SFEley,

2
Jeśli cofniesz się i ponownie przeczytasz pytanie pierwotnego autora, Nikita, a potem moje komentarze w kolejności, może to mieć dla ciebie więcej sensu. Jeśli nie ... Cóż, odpowiedź na pytanie. Miłego dnia.
— SFEley,

8
Lepsze w przypadku migracji w dół: change_column_default :people, :last_name, nil stackoverflow.com/a/1746246/483520
— Nolan Amy,

9
Zwróć uwagę, że dla nowszych wersji szyn potrzebujesz dodatkowego parametru typu przed parametrem domyślnym. Szukaj: wpisz na tej stronie.
— Ben Wheeler,

3
@JoelBrewer Natknąłem się na to dzisiaj - w przypadku Rails 4 musisz również określić typ kolumny:change_column :people, :last_name, :string, default: "Doe"
— GoBusto

56

W oparciu o odpowiedź SFEley, tutaj jest zaktualizowany / poprawiony dla nowszych wersji Railsów:

class SetDefault < ActiveRecord::Migration
  def change
    change_column :table_name, :column_name, :type, default: "Your value"
  end
end

2
Uważaj, to NIE działa na Railsach 4.2.x (nie testowane na późniejszych wersjach). co change_columnmoże być dość „strukturalne”, nie można wywnioskować operacji odwrotnej. Jeśli nie jesteś pewien, po prostu przetestuj go, uruchamiając db:migratei db:rollbackzaraz potem. Zaakceptowano odpowiedź jako ten sam wynik, ale przynajmniej przyjęto!
— gfd

1
To jest dla mnie poprawna odpowiedź. Działa z szynami 5.1, ale musisz to zrobić upi downjak opisano powyżej z @GoBusto
— Fabrizio Bertoglio

22

Przede wszystkim nie możesz przeciążać, initialize(*args)ponieważ nie jest to wywoływane we wszystkich przypadkach.

Najlepszą opcją jest dodanie ustawień domyślnych do migracji:

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

Drugą najlepszą metodą jest umieszczenie w modelu wartości domyślnych, ale zadziała to tylko z atrybutami, które początkowo są zerowe. Możesz mieć problemy, tak jak ja z booleankolumnami:

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

Potrzebujesz, new_record?aby wartości domyślne nie zastępowały wartości załadowanych z bazy danych.

Musisz ||=powstrzymać Railsy przed nadpisywaniem parametrów przekazanych do metody initialize.


12
drobna uwaga - chcesz zrobić dwie rzeczy: .... 1) nie wywołuj swojej metody after_initialize. Chcesz after_initiation :your_method_name... 2 użyćself.max_users ||= 10
— Jesse Wolgamott

5
Dla logicznych, po prostu zrób to: prop = true if prop.nil?
— Francis Potter,

2
lub after_initialize dozamiastdef after_initialize
— fotanus

self.max_users, aby być bezpiecznym.
— Ken Ratanachai S.,

15

Możesz także spróbować change_column_defaultswoich migracji (testowane w Railsach 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 Dokumentacja Rails API


1
Zaakceptowana odpowiedź jest bardziej kompletna, ale podoba mi się ta przejrzysta i udokumentowana… Dzięki!
— gfd

7

Jeśli odnosisz się do obiektów ActiveRecord, masz na to (więcej niż) dwa sposoby:

1. Użyj: default parametru w DB

NA PRZYKŁAD

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

Więcej informacji tutaj: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html

2. Użyj oddzwaniania

NA PRZYKŁAD before_validation_on_create

Więcej informacji tutaj: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#M002147


2
Czy nie jest problem z używaniem wartości domyślnych w bazie danych, które nie są ustawiane, dopóki obiekt nie zostanie zapisany? Jeśli chcę skonstruować nowy obiekt z wypełnionymi wartościami domyślnymi, muszę ustawić je w inicjatorze.
— Rafe

Wartości logiczne nie mogą mieć wartości domyślnej 1 ani 0 - muszą być ustawione na wartość true lub false (patrz odpowiedź Silasj).
— Jamon Holmgren,

@JamonHolmgren Dzięki za uwagę, poprawiłem odpowiedź :)
— Vlad Zloteanu,

Żaden problem @VladZloteanu
— Jamon Holmgren

7

W Ruby on Rails v3.2.8, używając after_initializewywołania zwrotnego ActiveRecord, możesz wywołać metodę w swoim modelu, która przypisze domyślne wartości dla nowego obiektu.

Funkcja zwrotna after_initialize jest wyzwalana dla każdego obiektu, który został znaleziony i utworzony przez wyszukiwarkę, przy czym funkcja after_initialize jest wyzwalana również po utworzeniu instancji nowych obiektów ( zobacz Wywołania zwrotne ActiveRecord ).

Więc IMO powinno wyglądać mniej więcej tak:

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_valuew tym przypadku, chyba że instancja zawiera attribute_whose_presence_has_been_validatedpoprzednio zapisaną / aktualizację. default_valueZostaną następnie wykorzystane w połączeniu z widoku do renderowania formularz używając default_valuedo baratrybutu.

W najlepszym razie jest to hacky ...

EDYCJA - użyj „new_record?” aby sprawdzić, czy tworzona jest instancja z nowego połączenia

Zamiast sprawdzać wartość atrybutu, użyj new_record?wbudowanej metody z railsami. Tak więc powyższy przykład powinien wyglądać następująco:

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

To jest dużo czystsze. Ach, magia Railsów - jest mądrzejsza ode mnie.


To nie działa tak, jak się spodziewałem - miało ustawić tylko wartość domyślną na nowy, ale ustawia również wartość domyślną dla każdego obiektu, który został znaleziony i utworzony (tj. Rekordy ładowane z bazy danych). Możesz sprawdzić atrybut obiektu, szukając wartości przed przypisaniem wartości domyślnej, ale nie jest to bardzo eleganckie ani niezawodne rozwiązanie.
— błędny

1
Dlaczego after_initializetutaj polecasz callback? Dokumentacja Railsów dotycząca wywołań zwrotnych zawiera przykład ustawienia wartości domyślnej before_createbez dodatkowego sprawdzania warunków
— Dfr

@Dfr - Musiałem to przeoczyć, czy możesz wrzucić link do recenzji, a zaktualizuję odpowiedź ...
— błąd

Tak, tutaj api.rubyonrails.org/classes/ActiveRecord/Callbacks.html pierwszy przykład, klasaSubscription
— Dfr

5
@Dfr - Powodem, dla którego używamy after_initializezamiast before_createwywołania zwrotnego, jest to, że chcemy ustawić domyślną wartość dla użytkownika (do użycia w widoku) podczas tworzenia nowych obiektów. before_createZwrotna jest wywoływana po użytkownik został doręczony nowego obiektu, pod warunkiem ich wejścia i złożony obiekt, dla stworzenia do sterownika. Następnie kontroler sprawdza wszelkie before_createwywołania zwrotne. Wydaje się to sprzeczne z intuicją, ale to rzecz nomenklaturowa - before_createodnosi się do createakcji. Utworzenie wystąpienia nowego obiektu nie powoduje jego createpowstania.
— błąd

5

Przynajmniej dla pól boolowskich w Railsach 3.2.6 będzie to działać podczas migracji.

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

Wstawienie 1lub 0jako wartości domyślnej nie zadziała tutaj, ponieważ jest to pole logiczne. Musi to być wartość a truelub false.



2

Wygeneruj migrację i użyj change_column_default, jest zwięzły i odwracalny:

class SetDefaultAgeInPeople < ActiveRecord::Migration[5.2]
  def change
    change_column_default :people, :age, { from: nil, to: 0 }
  end
end

1

Jeśli ustawiasz tylko wartości domyślne dla niektórych atrybutów modelu opartego na bazie danych, rozważyłbym użycie domyślnych wartości kolumn sql - czy możesz wyjaśnić, jakich typów domyślnych używasz?

Istnieje wiele podejść do tego, ta wtyczka wygląda na interesującą opcję.


1
Myślę, że nie powinieneś polegać na bazie danych, aby radzić sobie z ustawieniami domyślnymi i ograniczeniami, wszystko to powinno być uporządkowane w warstwie modelu. Ta wtyczka działa tylko dla modeli ActiveRecord, nie jest to ogólny sposób ustawiania wartości domyślnych dla obiektów.
— Lukas Stejskal

Twierdzę, że zależy to od typów domyślnych, których próbujesz użyć, nie jest to coś, co musiałem robić bardzo często, ale powiedziałbym, że w rzeczywistości ograniczenia są znacznie lepsze, gdy są ustawione zarówno w bazie danych, jak i model - aby zapobiec utracie danych w bazie.
— paulthenerd

1

Propozycja zastąpienia nowego / inicjalizacji jest prawdopodobnie niekompletna. Railsy będą (często) wywoływać alokację dla obiektów ActiveRecord, a wywołania alokacji nie spowodują wywołań inicjalizacji.

Jeśli mówisz o obiektach ActiveRecord, przyjrzyj się zastępowaniu after_initialize.

Te posty na blogu (nie moje) są przydatne:

Wartości domyślne Nie wywołano konstruktorów domyślnych

[Edycja: SFEley wskazuje, że Railsy faktycznie patrzą na domyślne w bazie danych, gdy tworzy instancję nowego obiektu w pamięci - nie zdawałem sobie z tego sprawy.]


1

Musiałem ustawić wartość domyślną, tak jakby została określona jako domyślna wartość kolumny w DB. Więc zachowuje się tak

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

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

Ponieważ wywołanie zwrotne after_initialize jest wywoływane po ustawieniu atrybutów z argumentów, nie było możliwości sprawdzenia, czy atrybut jest zerowy, ponieważ nigdy nie został ustawiony lub został celowo ustawiony na zero. Musiałem więc trochę zagrzebać się w środku i przyszedłem z tym prostym rozwiązaniem.

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

U mnie działa świetnie. (Rails 3.2.x)



0

I odpowiedział na podobne pytanie tutaj .. czysty sposób, aby to zrobić przy użyciu Rails attr_accessor_with_default

class SOF
  attr_accessor_with_default :is_awesome,true
end

sof = SOF.new
sof.is_awesome

=> true

AKTUALIZACJA

attr_accessor_with_default został uznany za przestarzały w Railsach 3.2 .. zamiast tego możesz to zrobić za pomocą czystego Rubiego

class SOF
  attr_writer :is_awesome

  def is_awesome
    @is_awesome ||= true
  end
end

sof = SOF.new
sof.is_awesome

#=> true

attr_accessor_with_defaultjest przestarzałe od rails> 3.1.0
— flynfish

W twoim przykładzie is_awesome zawsze będzie true, nawet jeśli @is_awesome == false.
— Ritchie,



-3

Możesz nadpisać konstruktora dla modelu ActiveRecord.

Lubię to:

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
To okropne miejsce, w którym można zmieniać podstawowe funkcje szyn. Istnieją inne, znacznie stabilniejsze, mniej skłonne do zepsucia innych rzeczy, sposoby na to wymienione w innych odpowiedziach. Łaty małpy powinny być tylko ostatecznością w przypadku rzeczy, których nie można zrobić w inny sposób.
— Będzie
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.