2009-11-22 8 views
5

Mam ten model, który działa dobrze:validates_presence_of powoduje after_initialize nazywać siebie z dziwnym

class Weight < ActiveRecord::Base 
    belongs_to :user 
    validates_presence_of :weight, :measured_on 
    attr_accessible :weight, :measured_on 

    def after_initialize 
    self.measured_on ||= Date.today 
    end 
end 

dodałem mu tę linię

validates_uniqueness_of :measured_on, :scope => :user_id 

i zaczęło rzucać błąd na uprawomocnienie. Nie jest to błąd, ale błąd walidacji Ruby:

>> w.valid? 
ActiveRecord::MissingAttributeError: missing attribute: measured_on 
    from /Users/pupeno/Projects/sano/app/models/weight.rb:8:in `after_initialize' 

Włożyłam oświadczenie debuggera w after_initialize i zauważyłem coś nieoczekiwanego. Kiedy tworzę nową wagę to działa zgodnie z oczekiwaniami, a obiekt na siebie after_initialize jest spodziewana waga:

>> w = Weight.new 
/Users/pupeno/Projects/sano/app/models/weight.rb:9 
self.measured_on ||= Date.today 
(rdb:1) p self 
#<Weight id: nil, user_id: nil, weight: nil, measured_on: nil, created_at: nil, updated_at: nil> 
(rdb:1) c 
=> #<Weight id: nil, user_id: nil, weight: nil, measured_on: "2009-11-22", created_at: nil, updated_at: nil> 

kiedy biegnę w.valid? robi się dziwnie. after_initialize nazywa się znowu, nie jestem pewien, dlaczego, a obiekt ma nic samo się spodziewałem:

>> w.valid? 
/Users/pupeno/Projects/sano/app/models/weight.rb:9 
self.measured_on ||= Date.today 
(rdb:1) p self 
#<Weight id: 1> 
(rdb:1) p self.inspect 
"#<Weight id: 1>" 
(rdb:1) p self.class 
Weight(id: integer, user_id: integer, weight: float, measured_on: date, created_at: datetime, updated_at: datetime) 
(rdb:1) p self.measured_on 
ActiveRecord::MissingAttributeError Exception: missing attribute: measured_on 
(rdb:1) 

Wydaje się, że kolejnym celem Waga został stworzony bez żadnych atrybutów, ale Set ID. Jakieś pomysły, dlaczego? Czy to błąd, czy oczekiwane zachowanie? Czy robię coś nie tak, ustawiając measure_on on after_initialize?

Moje bieżące obejście, w przypadku, ktoś jest ten sam problem, to

class Weight < ActiveRecord::Base 
    belongs_to :user 
    validates_presence_of :weight, :measured_on 
    validates_uniqueness_of :measured_on, :scope => :user_id 
    attr_accessible :weight, :measured_on 

    def after_initialize 
    if self.has_attribute? :measured_on 
     self.measured_on ||= Date.today 
    end 
    end 
end 

ale chciałbym mieć odpowiednie rozwiązanie.

Odpowiedz

6

Myślę, że uderzenie błąd szyny Niedawno walczył z. Zobacz link This blog entry do powiązanego błędu latarni.

Rozumiem, że to, co się dzieje, to że jakiś wcześniejszy kawałek kodu szyny wykonuje "wybierz identyfikator z nazwy", aby sprawdzić, czy wpis istnieje lub odpowiada. Obiekt następnie buforuje, że jedynym polem, które istnieje dla tabeli, jest "id". Twój kod następnie działa, a wartość "attributes" jest wtedy niepoprawna, odzwierciedlając tylko pole id.

Z tego, co udało mi się znaleźć, wynikało to tylko z trafienia tej konkretnej ścieżki kodowej i generalnie nie denerwowało, z wyjątkiem przeprowadzania walidacji.

To, co zrobiłem, aby obejść to, to zawinięcie kodu after_initialise w bloku begin/rescue ActiveRecord :: MissingAttributeError. Następnie napisałem dużą uwagę w aplikacji i powyżej każdego elementu wskazującego, kiedy nowa wersja szyn jest wydana, możemy to usunąć.

Tak, jestem pewien, że istnieją bardziej eleganckie rozwiązania.

def after_initialize 
    begin 
    # ... updates here 
    # self.unique_reference = UUIDTools::UUID.random_create.to_s 
    rescue ActiveRecord::MissingAttributeError 
    end 
end 

Albo można też zrobić:

def after_initialize 
    if self.has_attribute? :measured_on 
    self.measured_on ||= Date.today 
    end 
end 
+0

OP zredagował moją odpowiedź i utworzył sekcję "has_attribute?: Measured_on" - Nie jestem w 100% pewien, że zgadzam się z nią po spojrzeniu na źródło rails - podejrzewam, że działa z powodu efektu ubocznego, a nie przez -projekt). Nie cofam tej części odpowiedzi, bo kto wie, może komuś pomóc. – oskarpearson

+0

Ugryziony przez to dzisiaj. Ten post i twoja odpowiedź uratowały mi bekon. Zapomniałem, że jedną z wad języków dynamicznych jest to, że nie zawsze mają te same atrybuty w obiekcie ... co może być dobre, ale także złe :) –

+0

Dostałem dzisiaj ukąszenie. Wow, to zostało opublikowane tutaj w 2009 roku? Jestem pod wrażeniem, że od tak dawna się nie odblokował. – Trejkaz

0

validates_uniqueness_of wymaga skanowania bazy danych w poszukiwaniu wpisów. ActiveRecord ładuje wszystkie inne wpisy jako instancje twojego modelu. Ale aby zmniejszyć wykorzystanie procesora/pamięci, nie dodaje on metod dla atrybutów, ponieważ nie powinien ich potrzebować do szybkiego sprawdzania istnienia. Jednak nadal tworzy instancje wszystkich istniejących rekordów jako instancje modelu, tak aby wywoływać after_initialize.

Prace wokół jest modyfikacja @attributes hash bezpośrednio zamiast opierania się na akcesorów:

class Weight < ActiveRecord::Base 
    belongs_to :user 
    validates_presence_of :weight, :measured_on 
    validates_uniqueness_of :measured_on, :scope => :user_id 
    attr_accessible :weight, :measured_on 

    def after_initialize 
    @attributes["measured_on"] ||= Date.today 
    end 
end 
+0

Jestem pewien, że validates_uniqueness generuje po prostu instrukcję sql, aby sprawdzić istnienie. Nie powoduje to utworzenia wszystkich wpisów w tabeli. – Mike

+0

Tak to wygląda, ale istnieje? na końcu końca bloku validates \ _each w źródle validates \ _uniqueness \ _of tworzy wciąż aktywną instancję rekordu. – EmFi

2

Ten problem jest związany z biletami szyny # 3165. Przeczytaj tam, jeśli interesuje Cię, jak i dlaczego to się dzieje, spędziłem pół dnia na tym, zanim w końcu znalazłem ten bilet. Gdy jestem smutny, że to było prawie dokładnie rok odkąd ten został zgłoszony, a nie została jeszcze ustalona, ​​oto moja prosta praca całego mojego scenariusza:

validates_uniqueness_of :email, :scope => :library_id 

def after_initialize 
    self.status ||= "Invited" 
end 

To spowoduje „MissingAttributeError” do być wyrzucony, jeśli istnieją zwracane rekordy przez validates_uniqueness_of zapytania. Moje proste rozwiązanie to:

def after_initialize 
    self.status ||= "Invited" if new_record? 
end 

Podczas gdy inni ludzie mają bardziej złożone problemy, powinno to rozwiązać prosty przypadek, aż rzeczywiste rozwiązanie jest zobowiązana do szyn.

+0

Należy pamiętać, że jeśli jest to również potrzebne w przypadku rekordów, które nie są nowe, to obejście nie jest dobre (w moim przypadku spowodowało to przerwanie testów). – Trejkaz

Powiązane problemy