Jak ustawić wartość domyślną w ActiveRecord?
Widzę post od Pratika, który opisuje brzydki, skomplikowany fragment kodu: 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
Widziałem następujące przykłady googlujące się w Internecie:
def initialize
super
self.status = ACTIVE unless self.status
end
i
def after_initialize
return unless new_record?
self.status = ACTIVE
end
Widziałem także ludzi, którzy włączali to do migracji, ale wolałbym, żeby było to zdefiniowane w kodzie modelu.
Czy istnieje kanoniczny sposób ustawienia wartości domyślnej dla pól w modelu ActiveRecord?
Odpowiedzi:
Istnieje kilka problemów z każdą z dostępnych metod, ale uważam, że zdefiniowanie
after_initialize
wywołania zwrotnego jest dobrym rozwiązaniem z następujących powodów:default_scope
zainicjuje wartości dla nowych modeli, ale wtedy stanie się to zakresem, w którym znajdziesz model. Jeśli chcesz tylko zainicjować niektóre liczby na 0, to nie jest to, czego chcesz.initialize
może działać, ale nie zapomnij zadzwonićsuper
!after_initialize
jest przestarzałe od wersji Rails 3. Gdy nadpisujęafter_initialize
w wersji 3.0.3, w konsoli pojawia się następujące ostrzeżenie:Dlatego powiedziałbym, że napisz
after_initialize
oddzwonienie, które pozwoli ci na domyślne atrybuty oprócz ustawienia domyślnych powiązań:Teraz masz tylko jedno miejsce, aby poszukać inicjalizacji swoich modeli. Używam tej metody, dopóki ktoś nie wymyśli lepszej.
Ostrzeżenia:
W przypadku pól boolowskich wykonaj:
self.bool_field = true if self.bool_field.nil?
Więcej informacji można znaleźć w komentarzu Paula Russella do tej odpowiedzi
Jeśli wybierasz tylko podzbiór kolumn dla modelu (np. Używając
select
w zapytaniu takim jakPerson.select(:firstname, :lastname).all
), otrzymasz,MissingAttributeError
jeśli twojainit
metoda uzyskuje dostęp do kolumny, która nie została uwzględniona wselect
klauzuli. Możesz zabezpieczyć się przed tą sprawą w następujący sposób:self.number ||= 0.0 if self.has_attribute? :number
i dla kolumny boolowskiej ...
self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
Zauważ też, że składnia jest inna przed Rails 3.2 (patrz komentarz Cliffa Darlinga poniżej)
źródło
initialize
, wydaje się po prostu bardzo skomplikowany dla czegoś, co powinno być jasne i dobrze zdefiniowane. Spędziłem godziny przeszukując dokumentację, zanim zacząłem szukać tutaj, ponieważ założyłem, że ta funkcjonalność już gdzieś tam była i po prostu nie byłem jej świadomy.self.bool_field ||= true
, ponieważ spowoduje to wymuszenie tego pola, nawet jeśli jawnie zainicjujesz go na fałsz. Zamiast zrobićself.bool_field = true if self.bool_field.nil?
.MissingAttributeError
. Możesz dodać dodatkową kontrolę w następujący sposób:self.number ||= 0.0 if self.has_attribute? :number
Dla wartości logicznych:self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
. To jest Rails 3.2+ - wcześniej używajself.attributes.has_key?
i potrzebujesz łańcucha zamiast symbolu.initialize
od,return if !new_record?
aby uniknąć problemów z wydajnością.Szyny 5+
Możesz zastosować metodę atrybutu w swoich modelach, np .:
Możesz również przekazać lambda do
default
parametru. Przykład:źródło
Value
i nie będzie przeprowadzane rzutowanie.store_accessor :my_jsonb_column, :locale
że można wtedy zdefiniowaćattribute :locale, :string, default: 'en'
nil
. Jeśli nie mogą to byćnil
DBnot null
+ DB domyślnie + github.com/sshaw/keep_defaults to droga do mojego doświadczeniaUmieszczamy wartości domyślne w bazie danych poprzez migracje (poprzez określenie
:default
opcji w definicji każdej kolumny) i zezwalamy, aby Active Record używał tych wartości do ustawienia wartości domyślnych dla każdego atrybutu.IMHO, to podejście jest zgodne z zasadami AR: konwencja nad konfiguracją, OSUSZANIE, definicja tabeli napędza model, a nie na odwrót.
Zauważ, że wartości domyślne są nadal w kodzie aplikacji (Ruby), choć nie w modelu, ale w migracji (s).
źródło
Niektóre proste przypadki można rozwiązać, definiując wartość domyślną w schemacie bazy danych, ale nie obsługuje to wielu trudniejszych przypadków, w tym obliczonych wartości i kluczy innych modeli. W tych przypadkach robię to:
Zdecydowałem się użyć funkcji after_initialize, ale nie chcę, aby była stosowana do obiektów, które zostały znalezione tylko te nowe lub utworzone. Myślę, że to prawie szokujące, że wywołanie zwrotne After_new nie jest zapewnione dla tego oczywistego przypadku użycia, ale zrobiłem to, potwierdzając, czy obiekt jest już utrwalony, wskazując, że nie jest nowy.
Po zobaczeniu odpowiedzi Brada Murraya jest to jeszcze czystsze, jeśli warunek zostanie przeniesiony do żądania oddzwonienia:
źródło
:before_create
?Wzorzec wywołania zwrotnego after_initialize można poprawić, wykonując następujące czynności
Jest to nietrywialna korzyść, jeśli Twój kod init musi radzić sobie ze skojarzeniami, ponieważ poniższy kod wyzwala subtelne n + 1, jeśli czytasz początkowy rekord bez uwzględnienia powiązanego.
źródło
Faceci z Phusion mają do tego fajną wtyczkę .
źródło
:default
„po prostu pracować” wartościom w migracji schematówModel.new
.:default
wartości w migracji, aby „po prostu pracować”Model.new
, w przeciwieństwie do tego, co Jeff powiedział w swoim poście. Sprawdzona praca w szynach 4.1.16.Jeszcze lepszym / czystszym potencjalnym sposobem niż proponowane odpowiedzi jest zastąpienie akcesorium, jak poniżej:
Zobacz „Nadpisywanie domyślnych akcesoriów” w dokumentacji ActiveRecord :: Base i więcej z StackOverflow na temat używania self .
źródło
attributes
. Testowane w szynach 5.2.0.Używam
attribute-defaults
klejnotuZ dokumentacji: uruchom
sudo gem install attribute-defaults
i dodajrequire 'attribute_defaults'
do swojej aplikacji.źródło
Podobne pytania, ale wszystkie mają nieco inny kontekst: - Jak utworzyć wartość domyślną dla atrybutów w modelu activerecord Rails?
Najlepsza odpowiedź: zależy od tego, czego chcesz!
Jeśli chcesz, aby każdy obiekt zaczynał się od wartości: użyj
after_initialize :init
Chcesz, aby
new.html
formularz miał domyślną wartość po otwarciu strony? użyj https://stackoverflow.com/a/5127684/1536309Jeśli chcesz, aby każdy obiekt miał wartość obliczaną na podstawie danych wprowadzonych przez użytkownika: użyj
before_save :default_values
Chcesz, aby użytkownik wprowadził,X
a następnieY = X+'foo'
? posługiwać się:źródło
Po to są konstruktory! Zastąpinitialize
metodę modelu .Skorzystaj z
after_initialize
metodyźródło
after_initialize
metody.Sup chłopaki, skończyło się na tym, że:
Działa jak marzenie!
źródło
Odpowiedź była udzielana przez długi czas, ale często potrzebuję wartości domyślnych i wolę nie umieszczać ich w bazie danych. Tworzę
DefaultValues
problem:A następnie użyj go w moich modelach tak:
źródło
Kanonicznym sposobem Railsów, przed Railsami 5, było tak naprawdę ustawienie go w migracji, i po prostu szukanie,
db/schema.rb
kiedy tylko chcesz zobaczyć, jakie wartości domyślne są ustawiane przez DB dla dowolnego modelu.W przeciwieństwie do tego, co stwierdza @Jeff Perrin (który jest nieco stary), metoda migracji zastosuje domyślną nawet podczas używania
Model.new
, z powodu pewnej magii Railsów. Sprawdzona praca w szynach 4.1.16.Najprostsza rzecz jest często najlepsza. Mniejszy dług wiedzy i potencjalne zamieszanie w bazie kodu. I to po prostu działa.
Lub, aby zmienić kolumnę bez tworzenia nowej, wykonaj następujące czynności:
A może nawet lepiej:
Sprawdź oficjalny przewodnik RoR, aby uzyskać informacje na temat metod zmiany kolumn.
Nie
null: false
zezwala na wartości NULL w bazie danych i, jako dodatkową korzyść, aktualizuje się tak, że wszystkie wcześniej istniejące rekordy bazy danych, które wcześniej były zerowe, mają również wartość domyślną dla tego pola. Jeśli chcesz, możesz wykluczyć ten parametr z migracji, ale uważam, że jest to bardzo przydatne!Kanoniczny sposób w Rails 5+ jest, jak powiedział @Lucas Caton:
źródło
Problem z rozwiązaniami after_initialize polega na tym, że musisz dodać after_initialize do każdego obiektu, który spoglądasz poza bazę danych, niezależnie od tego, czy masz dostęp do tego atrybutu, czy nie. Sugeruję leniwe podejście.
Metody atrybutów (metody pobierające) są oczywiście metodami, więc możesz je zastąpić i podać wartość domyślną. Coś jak:
Chyba że, jak ktoś zauważył, musisz wykonać Foo.find_by_status („AKTYWNY”). W takim przypadku myślę, że naprawdę musisz ustawić domyślne ograniczenia bazy danych, jeśli DB je obsługuje.
źródło
Podczas wykonywania złożonych znalezisk napotkałem problemy z
after_initialize
podawaniemActiveModel::MissingAttributeError
błędów:na przykład:
„szukaj” w
.where
haszu warunkówSkończyło się na tym, że nadpisałem inicjalizację w następujący sposób:
super
Połączenie jest konieczne, aby upewnić się, że obiekt inicjalizacji prawidłowo zActiveRecord::Base
przed robi mój kod Dostosuj, tj: default_valuesźródło
def initialize(*); super; default_values; end
w Railsach 5.2.0. Ponadto wartość domyślna jest dostępna, nawet w.attributes
haszu.źródło
Mimo że robienie tego w celu ustawienia wartości domyślnych jest mylące i niewygodne w większości przypadków, możesz również użyć
:default_scope
. Sprawdź komentarz Squila tutaj .źródło
metoda after_initialize jest przestarzała, zamiast niej użyj wywołania zwrotnego.
jednak użycie : default w migracji jest nadal najczystszym sposobem.
źródło
after_initialize
metoda NIE jest przestarzała . W rzeczywistości wywołanie zwrotne w stylu makro podaje się jako przestarzałe . Szczegóły: guides.rubyonrails.org/...Przekonałem się, że użycie metody sprawdzania poprawności zapewnia dużą kontrolę nad ustawianiem wartości domyślnych. Możesz nawet ustawić wartości domyślne (lub nie sprawdzania poprawności) aktualizacji. Możesz nawet ustawić inną wartość domyślną dla wstawek niż aktualizacji, jeśli naprawdę tego chcesz. Pamiętaj, że ustawienie domyślne nie zostanie ustawione, dopóki #valid? nazywa się.
Jeśli chodzi o zdefiniowanie metody after_initialize, mogą wystąpić problemy z wydajnością, ponieważ after_initialize jest również wywoływany przez każdy obiekt zwracany przez: find: http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and-after_find
źródło
new
celu ponownego użycia akcji.Jeśli kolumna okazuje się być kolumną typu „status”, a Twój model nadaje się do korzystania z automatów stanów, rozważ użycie klejnotu aasm , po czym możesz po prostu zrobić
Nadal nie inicjuje wartości niezapisanych rekordów, ale jest nieco bardziej przejrzysty niż tworzenie własnych
init
lub cokolwiek, i czerpiesz inne korzyści z aasm, takie jak zakresy dla wszystkich twoich statusów.źródło
https://github.com/keithrowell/rails_default_value
źródło
Zdecydowanie zalecamy użycie klejnotu „default_value_for”: https://github.com/FooBarWidget/default_value_for
Istnieje kilka trudnych scenariuszy, które prawie wymagają zastąpienia metody inicjalizacji, co robi ten klejnot.
Przykłady:
Domyślna wartość db to NULL, domyślnie zdefiniowany model / ruby to „jakiś ciąg znaków”, ale tak naprawdę chcesz ustawić wartość zero z jakiegokolwiek powodu:
MyModel.new(my_attr: nil)
Większość rozwiązań tutaj nie ustawi wartości na zero i zamiast tego ustawi ją na wartość domyślną.
OK, więc zamiast
||=
podejść, przełączasz się namy_attr_changed?
...ALE wyobraź sobie, że domyślną wartością db jest „jakiś ciąg”, domyślnie zdefiniowany przez model / ruby to „jakiś inny ciąg”, ale w pewnym scenariuszu chcesz ustawić wartość na „jakiś ciąg” (domyślna db):
MyModel.new(my_attr: 'some_string')
Spowoduje to,
my_attr_changed?
że będzie fałszem, ponieważ wartość pasuje do wartości domyślnej db, co z kolei spowoduje uruchomienie domyślnego kodu zdefiniowanego w ruby i ustawienie wartości na „jakiś inny ciąg” - znowu nie to, co chciałeś.Z tych powodów nie sądzę, że można to właściwie osiągnąć za pomocą haka after_initialize.
Ponownie myślę, że klejnot „default_value_for” przyjmuje właściwe podejście: https://github.com/FooBarWidget/default_value_for
źródło
Oto rozwiązanie, którego użyłem, ale byłem trochę zaskoczony, nie zostało jeszcze dodane.
Składa się z dwóch części. Pierwsza część to ustawienie domyślne w rzeczywistej migracji, a druga część to sprawdzenie poprawności w modelu zapewniające, że obecność jest prawdziwa.
Zobaczysz tutaj, że wartość domyślna jest już ustawiona. Teraz podczas sprawdzania poprawności chcesz upewnić się, że ciąg ma zawsze wartość, więc po prostu zrób to
Spowoduje to ustawienie dla Ciebie wartości domyślnej. (dla mnie mam „Welcome to the Team”), a następnie pójdzie o krok dalej i zapewni, że dla tego obiektu zawsze będzie obecna wartość.
Mam nadzieję, że to pomaga!
źródło
źródło
użyj default_scope w szynach 3
api doc
ActiveRecord przesłania różnicę między domyślnymi ustawieniami zdefiniowanymi w bazie danych (schemat) a domyślnymi wykonanymi w aplikacji (modelu). Podczas inicjalizacji analizuje schemat bazy danych i odnotowuje określone tam wartości domyślne. Później, podczas tworzenia obiektów, przypisuje te wartości domyślne określone przez schemat bez dotykania bazy danych.
dyskusja
źródło
default_scope
. Spowoduje to, że wszystkie zapytania dodadzą ten warunek do ustawionego pola. To prawie NIGDY nie chcesz.Z dokumentacji interfejsu API http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html Użyj
before_validation
metody w swoim modelu, daje ona opcje tworzenia konkretnej inicjalizacji dla tworzenia i aktualizacji wywołań np. W tym przykładzie (ponownie pobrany kod z przykładu api docs) pole liczbowe jest inicjowane dla karty kredytowej. Możesz łatwo to dostosować, aby ustawić dowolne wartościZaskoczony, że jego tu nie zaproponowano
źródło