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

417

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?

ryw
źródło
Wygląda na to, że sam odpowiedziałeś na pytanie w dwóch różnych wariantach :)
Adam Byrtek
19
Zauważ, że „standardowy” Rubinowy idiom dla „self.status = AKTYWNY, chyba że self.status” to „self.status || = AKTYWNY”
Mike Woodhouse
1
Odpowiedź Jeffa Perrina jest znacznie lepsza niż ta obecnie oznaczona jako zaakceptowana. default_scope jest niedopuszczalnym rozwiązaniem do ustawiania wartości domyślnych, ponieważ ma OGROMNY EFEKT BOCZNY polegający również na zmianie zachowania zapytań.
Lawrence
patrz także stackoverflow.com/questions/3975161/…
Viktor Trón
2
biorąc pod uwagę wszystkie głosy poparcia dla tego pytania, powiedziałbym, że Ruby potrzebuje metody setDefaultValue dla ActiveRecord
spartikus

Odpowiedzi:

557

Istnieje kilka problemów z każdą z dostępnych metod, ale uważam, że zdefiniowanie after_initializewywołania zwrotnego jest dobrym rozwiązaniem z następujących powodów:

  1. default_scopezainicjuje 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.
  2. Definiowanie wartości domyślnych w migracji również działa przez pewien czas ... Jak już wspomniano, nie zadziała to po prostu wywołując Model.new.
  3. Zastąpienie initializemoże działać, ale nie zapomnij zadzwonić super!
  4. Używanie wtyczek takich jak phusion staje się trochę śmieszne. To jest ruby, czy naprawdę potrzebujemy wtyczki, aby zainicjować niektóre wartości domyślne?
  5. Zastąpienie after_initialize jest przestarzałe od wersji Rails 3. Gdy nadpisuję after_initializew wersji 3.0.3, w konsoli pojawia się następujące ostrzeżenie:

OSTRZEŻENIE DEPRECATION: Baza # po_initialize została uznana za przestarzałą, zamiast tego użyj metody Base.after_initialize:. (wywoływany z / Users / me / myapp / app / models / my_model: 15)

Dlatego powiedziałbym, że napisz after_initializeoddzwonienie, które pozwoli ci na domyślne atrybuty oprócz ustawienia domyślnych powiązań:

  class Person < ActiveRecord::Base
    has_one :address
    after_initialize :init

    def init
      self.number  ||= 0.0           #will set the default value only if it's nil
      self.address ||= build_address #let's you set a default association
    end
  end    

Teraz masz tylko jedno miejsce, aby poszukać inicjalizacji swoich modeli. Używam tej metody, dopóki ktoś nie wymyśli lepszej.

Ostrzeżenia:

  1. 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

  2. Jeśli wybierasz tylko podzbiór kolumn dla modelu (np. Używając selectw zapytaniu takim jak Person.select(:firstname, :lastname).all), otrzymasz, MissingAttributeErrorjeśli twoja initmetoda uzyskuje dostęp do kolumny, która nie została uwzględniona w selectklauzuli. 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)

Jeff Perrin
źródło
7
Zdecydowanie wydaje się to najlepszym sposobem na osiągnięcie tego. Co jest naprawdę dziwne i niefortunne. Rozsądna preferowana metoda ustalania domyślnych atrybutów modelu podczas tworzenia wydaje się być czymś, co Rails powinien już mieć wbudowany. Jedyny inny (niezawodny) sposób, zastępujący 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.
seaneshbaugh
106
Jedna uwaga na ten temat - jeśli masz pole boolowskie, które chcesz ustawić domyślnie, nie rób tego 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?.
Paul Russell,
2
Jeśli chodzi o punkt 2, Model.new faktycznie działa (tylko dla mnie?) Razem z wartościami domyślnymi zdefiniowanymi w migracji, a dokładniej z wartościami domyślnymi dla kolumn tabeli. Ale zdaję sobie sprawę, że metoda Jeffa oparta na wywołaniu zwrotnym after_initialize jest prawdopodobnie najlepszym sposobem. Tylko pytanie: czy działa z brudnymi, ale niezapisanymi przedmiotami? Czy w twoim przykładzie Person.new.number_was zwróci 0.0?
Laurent Farcy,
21
Należy zachować ostrożność podczas korzystania z tego podejścia w połączeniu z wybieraniem określonych kolumn z aktywnym rekordem. W takim przypadku w obiekcie zostaną znalezione tylko atrybuty określone w zapytaniu, a kod inicjujący wyrzuci a 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żywaj self.attributes.has_key?i potrzebujesz łańcucha zamiast symbolu.
Cliff Darling
6
Wykonanie tego z powiązaniami chętnie załaduje te skojarzenia podczas wyszukiwania. Zacznij initializeod, return if !new_record?aby uniknąć problemów z wydajnością.
Kyle Macey,
68

Szyny 5+

Możesz zastosować metodę atrybutu w swoich modelach, np .:

class Account < ApplicationRecord
  attribute :locale, :string, default: 'en'
end

Możesz również przekazać lambda do defaultparametru. Przykład:

attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }
Lucas Caton
źródło
3
ahhhhh to klejnot, którego szukałem! default może również zająć proc, np. default: -> {Time.current.to_date}
schpet
1
Upewnij się, że podałeś typ jako drugi argument, w przeciwnym razie typ będzie Valuei nie będzie przeprowadzane rzutowanie.
null
ku mojej radości, działa to również w przypadku store_accessor, na przykład biorąc pod uwagę, store_accessor :my_jsonb_column, :locależe można wtedy zdefiniowaćattribute :locale, :string, default: 'en'
Ryan Romanchuk
Och, to fantastyczne, potrzebowałem ustawień domyślnych, aby pokazać się w formie i to działa świetnie. Dzięki Lucas.
Paul Watson
Nadal można je ustawić na nil. Jeśli nie mogą to być nilDB not null+ DB domyślnie + github.com/sshaw/keep_defaults to droga do mojego doświadczenia
sshaw
47

Umieszczamy wartości domyślne w bazie danych poprzez migracje (poprzez określenie :defaultopcji 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).

Laurent Farcy
źródło
3
Innym problemem jest to, że chcesz mieć wartość domyślną dla klucza obcego. Nie można zakodować na stałe wartości identyfikatora w polu klucza obcego, ponieważ w różnych bazach danych identyfikator może być inny.
shmichael
2
jeszcze innym problemem jest to, że w ten sposób nie można zainicjować nietrwałych akcesorów (atrybutów, które nie są kolumnami db).
Viktor Trón
2
innym problemem jest to, że niekoniecznie widać wszystkie wartości domyślne w jednym miejscu. mogą być rozproszone przez różne migracje.
declan
8
deklan, jest db / schema.rb
Benjamin Atkin
6
Chciałbym wspomnieć o przyszłych czytelnikach: przynajmniej z tego, co przeczytałem, jest to sprzeczne z zasadami AR. Logika modeli powinna opierać się na klasach modeli, a bazy danych powinny być tak ignoranckie, jak to tylko możliwe. Domyślne wartości stanowią dla mnie określoną logikę dotyczącą modelu.
darethas
40

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:

after_initialize :defaults

def defaults
   unless persisted?
    self.extras||={}
    self.other_stuff||="This stuff"
    self.assoc = [OtherModel.find_by_name('special')]
  end
end

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:

after_initialize :defaults, unless: :persisted?
              # ":if => :new_record?" is equivalent in this context

def defaults
  self.extras||={}
  self.other_stuff||="This stuff"
  self.assoc = [OtherModel.find_by_name('special')]
end
Joseph Lord
źródło
4
To jest naprawdę ważny punkt. Muszę sobie wyobrazić, że w większości przypadków ustawienie domyślne rekordu można wykonać tylko przed utrwaleniem nowego rekordu, a nie przy ładowaniu utrwalonego rekordu.
Russell Silva,
Dzięki stary, uratowałeś mi Dzień.
stephanfriedrich
2
Jak o :before_create?
Franklin Yu,
W jaki sposób: before_create obsługuje oddzielne nowe i zapisuje połączenia? Chciałbym to sprawdzić i naprawdę zrozumieć przed przejściem do tego.
Joseph Lord,
17

Wzorzec wywołania zwrotnego after_initialize można poprawić, wykonując następujące czynności

after_initialize :some_method_goes_here, :if => :new_record?

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.

class Account

  has_one :config
  after_initialize :init_config

  def init_config
    self.config ||= build_config
  end

end
Brad Murray
źródło
16

Faceci z Phusion mają do tego fajną wtyczkę .

Milan Novota
źródło
Uwaga: ta wtyczka pozwala :default„po prostu pracować” wartościom w migracji schematów Model.new.
jchook
Mogę uzyskać :defaultwartoś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.
Magne
8

Używam attribute-defaultsklejnotu

Z dokumentacji: uruchom sudo gem install attribute-defaultsi dodaj require 'attribute_defaults'do swojej aplikacji.

class Foo < ActiveRecord::Base
  attr_default :age, 18
  attr_default :last_seen do
    Time.now
  end
end

Foo.new()           # => age: 18, last_seen => "2014-10-17 09:44:27"
Foo.new(:age => 25) # => age: 25, last_seen => "2014-10-17 09:44:28"
aidan
źródło
7

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żyjafter_initialize :init

Chcesz, aby new.htmlformularz miał domyślną wartość po otwarciu strony? użyj https://stackoverflow.com/a/5127684/1536309

class Person < ActiveRecord::Base
  has_one :address
  after_initialize :init

  def init
    self.number  ||= 0.0           #will set the default value only if it's nil
    self.address ||= build_address #let's you set a default association
  end
  ...
end 

Jeśli chcesz, aby każdy obiekt miał wartość obliczaną na podstawie danych wprowadzonych przez użytkownika: użyjbefore_save :default_values Chcesz, aby użytkownik wprowadził,Xa następnieY = X+'foo'? posługiwać się:

class Task < ActiveRecord::Base
  before_save :default_values
  def default_values
    self.status ||= 'P'
  end
end
Blair Anderson
źródło
4

Po to są konstruktory! Zastąp initializemetodę modelu .

Skorzystaj z after_initializemetody

John Topley
źródło
2
Zwykle miałbyś rację, ale nigdy nie powinieneś nadpisywać inicjalizacji w modelu ActiveRecord, ponieważ nie zawsze może być ona wywoływana. Zamiast tego powinieneś użyć after_initializemetody.
Luke Redpath,
Używanie parametru default_scope tylko w celu ustawienia wartości domyślnej jest PEWNO błędne. poprawna odpowiedź po_inicjalizacji.
joaomilho
4

Sup chłopaki, skończyło się na tym, że:

def after_initialize 
 self.extras||={}
 self.other_stuff||="This stuff"
end

Działa jak marzenie!

Tony
źródło
3

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ę DefaultValuesproblem:

module DefaultValues
  extend ActiveSupport::Concern

  class_methods do
    def defaults(attr, to: nil, on: :initialize)
      method_name = "set_default_#{attr}"
      send "after_#{on}", method_name.to_sym

      define_method(method_name) do
        if send(attr)
          send(attr)
        else
          value = to.is_a?(Proc) ? to.call : to
          send("#{attr}=", value)
        end
      end

      private method_name
    end
  end
end

A następnie użyj go w moich modelach tak:

class Widget < ApplicationRecord
  include DefaultValues

  defaults :category, to: 'uncategorized'
  defaults :token, to: -> { SecureRandom.uuid }
end
Clem
źródło
3

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?

Kanonicznym sposobem Railsów, przed Railsami 5, było tak naprawdę ustawienie go w migracji, i po prostu szukanie, db/schema.rbkiedy 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.

class AddStatusToItem < ActiveRecord::Migration
  def change
    add_column :items, :scheduler_type, :string, { null: false, default: "hotseat" }
  end
end

Lub, aby zmienić kolumnę bez tworzenia nowej, wykonaj następujące czynności:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column_default :items, :scheduler_type, "hotseat"
  end
end

A może nawet lepiej:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column :items, :scheduler_type, :string, default: "hotseat"
  end
end

Sprawdź oficjalny przewodnik RoR, aby uzyskać informacje na temat metod zmiany kolumn.

Nie null: falsezezwala 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:

class Item < ActiveRecord::Base
  attribute :scheduler_type, :string, default: 'hotseat'
end
Magne
źródło
1

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:

Class Foo < ActiveRecord::Base
  # has a DB column/field atttribute called 'status'
  def status
    (val = read_attribute(:status)).nil? ? 'ACTIVE' : val
  end
end

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.

Jeff Gran
źródło
To rozwiązanie i proponowana alternatywa nie działają w moim przypadku: mam hierarchię klas STI, w której tylko jedna klasa ma ten atrybut, i odpowiednią kolumnę, która będzie używana w warunkach zapytania DB.
cmoran92,
1

Podczas wykonywania złożonych znalezisk napotkałem problemy z after_initializepodawaniem ActiveModel::MissingAttributeErrorbłędów:

na przykład:

@bottles = Bottle.includes(:supplier, :substance).where(search).order("suppliers.name ASC").paginate(:page => page_no)

„szukaj” w .where haszu warunków

Skończyło się na tym, że nadpisałem inicjalizację w następujący sposób:

def initialize
  super
  default_values
end

private
 def default_values
     self.date_received ||= Date.current
 end

superPołączenie jest konieczne, aby upewnić się, że obiekt inicjalizacji prawidłowo z ActiveRecord::Baseprzed robi mój kod Dostosuj, tj: default_values

Sean
źródło
Lubię to. Musiałem to zrobić def initialize(*); super; default_values; endw Railsach 5.2.0. Ponadto wartość domyślna jest dostępna, nawet w .attributeshaszu.
spyle
1
class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
end
Mike Breen
źródło
2
Mmmhh ... początkowo wydaje się genialny, ale po krótkiej refleksji widzę kilka problemów. Po pierwsze, wszystkie wartości domyślne nie są w jednym punkcie, ale rozproszone po klasie (wyobraź sobie, że je szukasz lub je zmieniasz). Po drugie i najgorsze, nie możesz później podać wartości zerowej (a nawet fałszywej!).
paradoja
dlaczego miałbyś ustawić wartość zerową jako domyślną? dostajesz to po wyjęciu z pudełka z AR bez robienia czegokolwiek. Jeśli chodzi o fałsz przy użyciu kolumny logicznej, masz rację, nie jest to najlepsze podejście.
Mike Breen,
Nie mogę mówić za innymi nawykami kodowania. Nie miałem problemu, ponieważ nie rozrzucam moich programów pobierających / ustawiających wokół pliku klasy. Ponadto każdy nowoczesny edytor tekstu powinien ułatwić nawigację do metody (shift-cmd-t w textmate).
Mike Breen,
@paradoja - Cofam to, teraz widzę, gdzie się psuje, używając również null. Niekoniecznie jest to ustawienie domyślne, ale jeśli w pewnym momencie chcesz zmienić wartość na null. Dobry połów @ paradoja, dzięki.
Mike Breen
Używam tej metody, ponieważ działa ona dobrze z dynamicznie generowanymi atrybutami.
Dale Campbell,
0

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 .

Skalee
źródło
0

metoda after_initialize jest przestarzała, zamiast niej użyj wywołania zwrotnego.

after_initialize :defaults

def defaults
  self.extras||={}
  self.other_stuff||="This stuff"
end

jednak użycie : default w migracji jest nadal najczystszym sposobem.

Greg
źródło
4
W Rails 3: after_initializemetoda NIE jest przestarzała . W rzeczywistości wywołanie zwrotne w stylu makro podaje się jako przestarzałe . Szczegóły: guides.rubyonrails.org/...
Zabba
0

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ę.

class MyModel
  validate :init_defaults

  private
  def init_defaults
    if new_record?
      self.some_int ||= 1
    elsif some_int.nil?
      errors.add(:some_int, "can't be blank on update")
    end
  end
end

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

kelwin
źródło
czy sprawdzanie poprawności nie następuje tylko przed zapisaniem? Co jeśli chcesz wyświetlić ustawienia domyślne przed zapisaniem?
nurettin
@nurettin To dobry punkt i rozumiem, dlaczego czasami tego chcesz, ale OP nie wspomniał o tym jako wymogu. Musisz sam zdecydować, czy chcesz narzucić domyślne ustawienia każdej instancji, nawet jeśli nie zostaną zapisane. Alternatywą jest pozostawienie fikcyjnego obiektu w newcelu ponownego użycia akcji.
Kelvin
0

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ć

  aasm column: "status" do
    state :available, initial: true
    state :used
    # transitions
  end

Nadal nie inicjuje wartości niezapisanych rekordów, ale jest nieco bardziej przejrzysty niż tworzenie własnych initlub cokolwiek, i czerpiesz inne korzyści z aasm, takie jak zakresy dla wszystkich twoich statusów.

Zła prośba
źródło
0

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ę na my_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

etipton
źródło
0

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.

add_column :teams, :new_team_signature, :string, default: 'Welcome to the Team'

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

 validates :new_team_signature, presence: true

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!

kdweber89
źródło
0
# db/schema.rb
create_table :store_listings, force: true do |t|
  t.string :my_string, default: "original default"
end

StoreListing.new.my_string # => "original default"

# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
  attribute :my_string, :string, default: "new default"
end

StoreListing.new.my_string # => "new default"

class Product < ActiveRecord::Base
  attribute :my_default_proc, :datetime, default: -> { Time.now }
end

Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
sleep 1
Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
Shilovk
źródło
-2

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

Viktor Trón
źródło
jeśli użyjesz meta_where, default_scope może nie działać w przypadku przypisywania wartości domyślnych do nowych obiektów AR z powodu błędu.
Viktor Trón
ten problem meta_where został już naprawiony [ metautonomous.lighthouseapp.com/projects/53011/tickets/…
Viktor Trón
3
NIE używaj default_scope. Spowoduje to, że wszystkie zapytania dodadzą ten warunek do ustawionego pola. To prawie NIGDY nie chcesz.
Brad
@brad, zabawne, że wspomniałeś, całkowicie się zgadzam, to jest złe :). zobacz mój komentarz w stackoverflow.com/questions/10680845/… .
Viktor Trón
-3

Z dokumentacji interfejsu API http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html Użyj before_validationmetody 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ści

class CreditCard < ActiveRecord::Base
  # Strip everything but digits, so the user can specify "555 234 34" or
  # "5552-3434" or both will mean "55523434"
  before_validation(:on => :create) do
    self.number = number.gsub(%r[^0-9]/, "") if attribute_present?("number")
  end
end

class Subscription < ActiveRecord::Base
  before_create :record_signup

  private
    def record_signup
      self.signed_up_on = Date.today
    end
end

class Firm < ActiveRecord::Base
  # Destroys the associated clients and people when the firm is destroyed
  before_destroy { |record| Person.destroy_all "firm_id = #{record.id}"   }
  before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
end

Zaskoczony, że jego tu nie zaproponowano

jamesc
źródło
before_validation nie ustawi wartości domyślnych, dopóki obiekt nie będzie gotowy do utrwalenia. Jeśli proces musi przeczytać wartości domyślne przed utrwaleniem, wartości nie będą gotowe.
mmell
Zresztą nigdy nie ustawiasz wartości domyślnych podczas sprawdzania poprawności. To nie jest żaden hack. Zrób to podczas inicjalizacji
Sachin