Qual é a maneira correta de substituir um método setter no Ruby on Rails?

184

Estou usando Ruby on Rails 3.2.2 e gostaria de saber se o seguinte é uma maneira "adequada" / "correta" / "certa" de substituir um método setter para um atributo my class.

attr_accessible :attribute_name

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

  self[:attribute_name] = value
end

O código acima parece funcionar como esperado. No entanto, gostaria de saber se, usando o código acima, no futuro terei problemas ou, pelo menos, quais problemas "devo esperar" / "podem acontecer" com o Ruby on Rails . Se esse não é o caminho certo para substituir um método setter, qual é o caminho certo?


Nota : Se eu usar o código

attr_accessible :attribute_name

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

  self.attribute_name = value
end

Estou tendo o erro a seguir:

SystemStackError (stack level too deep):
  actionpack (3.2.2) lib/action_dispatch/middleware/reloader.rb:70
Backo
fonte
4
Eu amo a terminologia aplicada '"adequada" / "correta" / "certa"'. Quando você dá três maneiras, ele realmente garante que não haja má interpretação. Bom trabalho!
Jay
5
@ Jay - "Italianismos finos"; -)
Backo 8/05/12
2
Só para esclarecer, o "nível de pilha muito profundo" está se referindo ao fato de que é uma chamada recursiva ... é a própria chamada.
Nippysaurus

Respostas:

295

==================================================== ========================= Atualização: 19 de julho de 2017

Agora a documentação do Rails também está sugerindo o uso superassim:

class Model < ActiveRecord::Base

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

end

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

Resposta original

Se você deseja substituir os métodos setter para colunas de uma tabela enquanto acessa através de modelos, esta é a maneira de fazê-lo.

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

Consulte Substituindo acessadores padrão na documentação do Rails.

Portanto, seu primeiro método é a maneira correta de substituir os configuradores de coluna em Models of Ruby on Rails. Esses acessadores já são fornecidos pelo Rails para acessar as colunas da tabela como atributos do modelo. Isso é o que chamamos de mapeamento ActiveRecord ORM.

Lembre-se também de que a attr_accessibleparte superior do modelo não tem nada a ver com os acessadores. Possui uma funcionalidade completamente diferente (consulte esta pergunta )

Porém, no Ruby puro, se você definiu acessadores para uma classe e deseja substituir o setter, deve usar a variável de instância como esta:

class Person
  attr_accessor :name
end

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

Isso será mais fácil de entender quando você souber o que attr_accessorfaz. O código attr_accessor :nameé equivalente a esses dois métodos (getter e setter)

def name # getter
  @name
end

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

Além disso, seu segundo método falha porque causa um loop infinito quando você está chamando o mesmo método attribute_name=dentro desse método.

rubyprince
fonte
9
Para Rails 4 simplesmente ignorar attr_accessibleuma vez que não está mais lá, e ele deve funcionar
zigomir
11
Por que não ligar super?
22714 Nathan Lilienthal
1
Fiquei com a impressão de que, como os acessadores e escritores são criados dinamicamente, supertalvez não funcionem. Mas, parece que não é o caso. Acabei de verificar e funciona para mim. Além disso, esta pergunta é a mesma
rubyprince
4
Há uma enorme pegadinha com write_attribute. As conversões serão ignoradas. Esteja ciente de que write_attributeignorará as conversões de fuso horário com datas, que quase sempre serão indesejadas.
Tim Scott
2
super funcionará bem, no entanto, há algum motivo para você não querer isso. Por exemplo, na gema mongóide, existe um erro no qual você não pode enviar para a matriz se você usar o método getter. É um bug por causa de lá a maneira de gerenciar a matriz na memória. Além disso, o @name também retornará o valor definido em vez de chamar o método que você substituiu. No entanto, na solução acima, ambos funcionarão perfeitamente.
Newdark-it
44

Use a superpalavra-chave:

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

Por outro lado, para substituir o leitor:

def attribute_name
  super.some_custom_decode
end
Robert Kajic
fonte
1
Resposta melhor que a IMO aceita, pois mantém a chamada do método limitada ao mesmo nome. Este comportamento preserva herdadas substituído para attribute_name =
Andrew Schwartz
A substituição do método getter se tornou perigosa no Rails 4.2 devido a essa alteração: github.com/rails/rails/commit/… Anteriormente, os auxiliares de formulário chamariam o valor não-previsto do campo e não chamariam seu getter personalizado. Agora eles chamam seu método e, portanto, produzirão resultados confusos em seus formulários, dependendo de como você está substituindo o valor.
Brendon Muir
16

Nos trilhos 4

digamos que você tenha o atributo idade na sua tabela

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

Nota: Para novos usuários no Rails 4, você não precisa especificar attr_accessible no modelo. Em vez disso, você deve listar seus atributos no nível do controlador usando o método de permissão .

Taimoor Changaiz
fonte
3

Eu descobri que (pelo menos para coleções de relacionamento ActiveRecord) o seguinte padrão funciona:

has_many :specialties

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

(Isso pega as 3 primeiras entradas não duplicadas na matriz passada).

Robin Daugherty
fonte
0

Usando attr_writerpara substituir o configurador attr_writer: attribute_name

  def attribute_name=(value)
    # manipulate value
    # then send result to the default setter
    super(result)
  end
bananaappletw
fonte