Jaki jest właściwy sposób na przesłonięcie metody ustawiającej w Ruby on Rails?

184

Korzystam z Ruby on Rails 3.2.2 i chciałbym wiedzieć, czy poniższe są „właściwym” / „poprawnym” / „pewnym” sposobem na przesłonięcie metody ustawiającej dla atrybutu mojej klasy.

attr_accessible :attribute_name

def attribute_name=(value)
  ... # Some custom operation.

  self[:attribute_name] = value
end

Powyższy kod wydaje się działać zgodnie z oczekiwaniami. Jednak chciałbym wiedzieć, czy, stosując powyższy kod w przyszłości będę miał problemy, a przynajmniej, jakie problemy „należy się spodziewać” / „może się zdarzyć” z Ruby on Rails . Jeśli to nie jest właściwy sposób na zastąpienie metody setera, jaki jest właściwy sposób?


Uwaga : jeśli użyję kodu

attr_accessible :attribute_name

def attribute_name=(value)
  ... # Some custom operation.

  self.attribute_name = value
end

Otrzymuję następujący błąd:

SystemStackError (stack level too deep):
  actionpack (3.2.2) lib/action_dispatch/middleware/reloader.rb:70
Backo
źródło
4
Uwielbiam terminologię „właściwe” / „poprawne” / „pewne”. Jeśli podasz 3 sposoby, naprawdę zapewni, że nie będzie błędnej interpretacji. Dobra robota!
Jay
5
@Jay - „Delikatność włoska”; -)
Backo,
2
Żeby było jasne, „poziom stosu zbyt głęboki” odnosi się do faktu, że jest to wywołanie rekurencyjne ... to samo wywołanie.
Nippysaurus

Odpowiedzi:

295

================================================== ========================= Aktualizacja: 19 lipca 2017 r

Teraz dokumentacja Railsów również sugeruje użycie w supernastępujący sposób:

class Model < ActiveRecord::Base

  def attribute_name=(value)
    # custom actions
    ###
    super(value)
  end

end

================================================== =========================

Oryginalna odpowiedź

Jeśli chcesz zastąpić metody ustawiające dla kolumn tabeli podczas uzyskiwania dostępu przez modele, jest to odpowiedni sposób.

class Model < ActiveRecord::Base
  attr_accessible :attribute_name

  def attribute_name=(value)
    # custom actions
    ###
    write_attribute(:attribute_name, value)
    # this is same as self[:attribute_name] = value
  end

end

Zobacz Zastępowanie domyślnych akcesoriów w dokumentacji Railsów.

Tak więc twoja pierwsza metoda jest poprawnym sposobem na przesłonięcie ustawiaczy kolumn w modelach Ruby on Rails. Te akcesoria są już dostarczane przez Railsy, ​​aby uzyskać dostęp do kolumn tabeli jako atrybuty modelu. Nazywamy to mapowaniem ORM ActiveRecord.

Należy również pamiętać, że attr_accessiblegórna część modelu nie ma nic wspólnego z akcesoriami. Ma zupełnie inną funkcjonalność (patrz to pytanie )

Ale w czystym Rubim, jeśli zdefiniowałeś akcesory dla klasy i chcesz przesłonić setter, musisz użyć zmiennej instancji takiej jak ta:

class Person
  attr_accessor :name
end

class NewPerson < Person
  def name=(value)
    # do something
    @name = value
  end
end

Łatwiej będzie to zrozumieć, gdy dowiesz się, co attr_accessorrobi. Kod attr_accessor :namejest równoważny z tymi dwiema metodami (getter i setter)

def name # getter
  @name
end

def name=(value) #  setter
  @name = value
end

Również twoja druga metoda zawiedzie, ponieważ spowoduje nieskończoną pętlę, ponieważ wywołujesz tę samą metodę attribute_name=w tej metodzie.

rubyprince
źródło
9
W przypadku Rails 4 po prostu pomiń, attr_accessibleponieważ już go nie ma, i powinien działać
zigomir
11
Dlaczego nie zadzwonić super?
Nathan Lilienthal
1
Miałem wrażenie, że skoro akcesory i pisarze są tworzone dynamicznie, supermogą nie działać. Ale wydaje się, że tak nie jest. Właśnie to sprawdziłem i działa dla mnie. Także to pytanie zadaje to samo
rubyprince
4
Istnieje ogromna gotcha z write_attribute. Konwersje zostaną pominięte. Pamiętaj, że write_attributepominie konwersje w strefie czasowej z datami, co prawie zawsze będzie niepożądane.
Tim Scott
2
super też będzie działać, ale istnieje powód, dla którego możesz tego nie chcieć. Na przykład w klejnotu mongoid występuje błąd, w którym nie można przesuwać do tablicy, jeśli super metoda gettera. Jest to błąd ze względu na sposób zarządzania tablicą w pamięci. Również @ nazwa zwróci również ustawioną wartość, a nie wywoła metodę nadpisywania. Jednak w powyższym rozwiązaniu oba będą działać dobrze.
newdark-it
44

Użyj supersłowa kluczowego:

def attribute_name=(value)
  super(value.some_custom_encode)
end

I odwrotnie, aby zastąpić czytnik:

def attribute_name
  super.some_custom_decode
end
Robert Kajic
źródło
1
Lepsza odpowiedź niż zaakceptowana IMO, ponieważ ogranicza wywołanie metody do tej samej nazwy. Ten przetwory dziedziczone przesłonięte zachowanie do ATTRIBUTE_NAME =
Andrew Schwartz
Przesłonięcie metody gettera stało się niebezpieczne w Rails 4.2 z powodu tej zmiany: github.com/rails/rails/commit/... Poprzednio pomocnicy formularzy wywoływali wartość pola nietypowego i nie wywoływali niestandardowego gettera. Teraz wywołują twoją metodę, a zatem będą generować mylące wyniki w twoich formularzach w zależności od tego, jak przesłonisz wartość.
Brendon Muir,
16

W szynach 4

powiedzmy, że masz atrybut wieku w tabeli

def age=(dob)   
    now = Time.now.utc.to_date
    age = now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
    super(age) #must add this otherwise you need to add this thing and place the value which you want to save. 
  end

Uwaga: W przypadku nowych graczy w szynach 4 nie trzeba określać atrybutu attr_accessible w modelu. Zamiast tego musisz umieścić na białej liście swoje atrybuty na poziomie kontrolera, używając metody zezwolenia .

Taimoor Changaiz
źródło
3

Przekonałem się, że (przynajmniej w przypadku kolekcji relacji ActiveRecord) działa następujący wzorzec:

has_many :specialties

def specialty_ids=(values)
  super values.uniq.first(3)
end

(Spowoduje to pobranie pierwszych 3 niepowielonych wpisów w przekazanej tablicy).

Robin Daugherty
źródło
0

Używanie attr_writerdo nadpisywania setter attr_writer: nazwa_atrybutu

  def attribute_name=(value)
    # manipulate value
    # then send result to the default setter
    super(result)
  end
bananaappletw
źródło