¿Cuál es la forma correcta de anular un método setter en Ruby on Rails?

184

Estoy usando Ruby on Rails 3.2.2 y me gustaría saber si lo siguiente es una forma "correcta" / "correcta" / "segura" de anular un método setter para un atributo my class.

attr_accessible :attribute_name

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

  self[:attribute_name] = value
end

El código anterior parece funcionar como se esperaba. Sin embargo, me gustaría saber si, al usar el código anterior, en el futuro tendré problemas o, al menos, qué problemas "debería esperar" / "podrían ocurrir" con Ruby on Rails . Si esa no es la forma correcta de anular un método setter, ¿cuál es la forma correcta?


Nota : si uso el código

attr_accessible :attribute_name

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

  self.attribute_name = value
end

Obtuve el siguiente error:

SystemStackError (stack level too deep):
  actionpack (3.2.2) lib/action_dispatch/middleware/reloader.rb:70
Backo
fuente
44
Me encanta la terminología aplicada '"apropiado" / "correcto" / "seguro"'. Cuando le das 3 formas, realmente garantiza que no hay malas interpretaciones. ¡Buen trabajo!
Jay
55
@ Jay - "Italianismos finos"; -)
Backo
2
Para ser claros, el "nivel de pila demasiado profundo" se refiere al hecho de que es una llamada recursiva ... se llama a sí misma.
Nippysaurus

Respuestas:

295

================================================== ========================= Actualización: 19 de julio de 2017

Ahora la documentación de Rails también sugiere usar superasí:

class Model < ActiveRecord::Base

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

end

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

Respuesta original

Si desea anular los métodos de establecimiento de columnas de una tabla mientras accede a través de modelos, esta es la forma de hacerlo.

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 Anulación de accesos predeterminados en la documentación de Rails.

Entonces, su primer método es la forma correcta de anular los establecedores de columnas en Modelos de Ruby on Rails. Rails ya proporciona estos accesorios para acceder a las columnas de la tabla como atributos del modelo. Esto es lo que llamamos mapeo ActiveRecord ORM.

También tenga en cuenta que la attr_accessibleparte superior del modelo no tiene nada que ver con los accesorios. Tiene una funcionalidad completamente diferente (ver esta pregunta )

Pero en Ruby puro, si ha definido los accesores para una clase y desea anular el setter, debe utilizar una variable de instancia como esta:

class Person
  attr_accessor :name
end

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

Esto será más fácil de entender una vez que sepa lo que attr_accessorhace. El código attr_accessor :namees equivalente a estos dos métodos (getter y setter)

def name # getter
  @name
end

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

Además, su segundo método falla porque causará un bucle infinito ya que está llamando al mismo método attribute_name=dentro de ese método.

rubyprince
fuente
9
Para Rails 4 simplemente omita attr_accessibleya que ya no está allí, y debería funcionar
zigomir
11
¿Por qué no llamar super?
Nathan Lilienthal
1
Tenía la impresión de que, dado que los accesores y los escritores se crean dinámicamente, superpodría no funcionar. Pero, parece que no es el caso. Lo acabo de comprobar y funciona para mí. Además, esta pregunta es la misma
rubyprince
44
Hay una gran trampa con write_attribute. Se omitirán las conversiones. Tenga en cuenta que write_attributeomitirá las conversiones de zona horaria con fechas, que casi siempre serán indeseadas.
Tim Scott,
2
super funcionará tan bien, sin embargo, hay alguna razón por la que es posible que no nos quiera. Por ejemplo, en la gema mongoide hay un error en el que no puedes empujar a la matriz si superas el método getter. Es un error debido a la forma en que maneja la matriz en la memoria. Además, @name también devolverá el valor establecido en lugar de llamar al método que está sobrescribiendo. Sin embargo, en la solución anterior, ambos funcionarán bien.
newdark-it
44

Use la superpalabra clave:

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

Por el contrario, para anular al lector:

def attribute_name
  super.some_custom_decode
end
Robert Kajic
fuente
1
Mejor respuesta que la IMO aceptada, ya que mantiene la llamada al método limitada al mismo nombre. Esto preserva el comportamiento anulado heredado de attribute_name =
Andrew Schwartz
Anular el método getter se ha vuelto peligroso en Rails 4.2 debido a este cambio: github.com/rails/rails/commit/… Anteriormente los ayudantes de formulario llamarían al valor no tipificado del campo y no llamarían a su getter personalizado. Ahora llaman a su método, por lo que producirán resultados confusos en sus formularios dependiendo de cómo anule el valor.
Brendon Muir
16

En rieles 4

digamos que tiene un atributo de edad en su tabla

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 los recién llegados en los rieles 4 no es necesario especificar attr_accessible en el modelo. En su lugar, debe hacer una lista blanca de sus atributos a nivel de controlador utilizando el método de permiso .

Taimoor Changaiz
fuente
3

He encontrado que (al menos para las colecciones de relaciones ActiveRecord) funciona el siguiente patrón:

has_many :specialties

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

(Esto toma las primeras 3 entradas no duplicadas en la matriz aprobada).

Robin Daugherty
fuente
0

Utilizando attr_writerpara sobrescribir setter attr_writer: attribute_name

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