Quelle est la bonne façon de remplacer une méthode setter dans Ruby on Rails?

184

J'utilise Ruby on Rails 3.2.2 et j'aimerais savoir si ce qui suit est un moyen "correct" / "correct" / "sûr" de remplacer une méthode setter pour un attribut my class.

attr_accessible :attribute_name

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

  self[:attribute_name] = value
end

Le code ci-dessus semble fonctionner comme prévu. Cependant, j'aimerais savoir si, en utilisant le code ci-dessus, j'aurai des problèmes à l'avenir ou, du moins, à quels problèmes "devrais-je m'attendre" / "pourraient survenir" avec Ruby on Rails . Si ce n'est pas la bonne façon de remplacer une méthode setter, quelle est la bonne façon?


Remarque : si j'utilise le code

attr_accessible :attribute_name

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

  self.attribute_name = value
end

J'obtiens l'erreur suivante:

SystemStackError (stack level too deep):
  actionpack (3.2.2) lib/action_dispatch/middleware/reloader.rb:70
Backo
la source
4
J'adore la terminologie appliquée «« correct »/« correct »/« sûr »». Lorsque vous lui donnez 3 façons, cela garantit vraiment qu'il n'y a pas de mauvaise interprétation. Bon travail!
Jay
5
@Jay - "Italianismes de finesse"; -)
Backo
2
Juste pour être clair, le "niveau de pile trop profond" fait référence au fait que c'est un appel récursif ... son appel lui-même.
Nippysaurus

Réponses:

295

=================================================== ========================== Mise à jour: 19 juillet 2017

Maintenant, la documentation Rails suggère également d'utiliser supercomme ceci:

class Model < ActiveRecord::Base

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

end

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

Réponse originale

Si vous souhaitez remplacer les méthodes de définition des colonnes d'une table lors de l'accès via des modèles, c'est la manière de le faire.

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

Voir Remplacer les accesseurs par défaut dans la documentation Rails.

Donc, votre première méthode est la bonne façon de remplacer les setters de colonne dans Models of Ruby on Rails. Ces accesseurs sont déjà fournis par Rails pour accéder aux colonnes de la table en tant qu'attributs du modèle. C'est ce que nous appelons le mappage ORM ActiveRecord.

Gardez également à l'esprit que la partie attr_accessiblesupérieure du modèle n'a rien à voir avec les accesseurs. Il a une fonctionnalité complètement différente (voir cette question )

Mais en Ruby pur, si vous avez défini des accesseurs pour une classe et que vous souhaitez remplacer le setter, vous devez utiliser une variable d'instance comme celle-ci:

class Person
  attr_accessor :name
end

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

Cela sera plus facile à comprendre une fois que vous saurez ce que attr_accessorfait. Le code attr_accessor :nameest équivalent à ces deux méthodes (getter et setter)

def name # getter
  @name
end

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

De plus, votre deuxième méthode échoue car elle provoquera une boucle infinie lorsque vous appelez la même méthode attribute_name=dans cette méthode.

rubisprince
la source
9
Pour Rails 4, sautez attr_accessiblecar il n'est plus là et cela devrait fonctionner
zigomir
11
Pourquoi ne pas appeler super?
Nathan Lilienthal
1
J'avais l'impression que puisque les accesseurs et les écrivains sont créés dynamiquement, cela superpourrait ne pas fonctionner. Mais il semble que ce ne soit pas le cas. Je viens de le vérifier et cela fonctionne pour moi. Aussi, cette question pose la même chose
rubyprince
4
Il y a un énorme piège avec write_attribute. Les conversions seront ignorées. Sachez que write_attributeles conversions de fuseau horaire avec des dates seront ignorées, ce qui sera presque toujours indésirable.
Tim Scott
2
super fonctionnera également, mais il y a une raison pour laquelle vous pourriez ne pas le vouloir. Par exemple, dans mongoid gem, il y a un bogue où vous ne pouvez pas pousser vers le tableau si vous super la méthode getter. C'est un bug à cause de la gestion du tableau en mémoire. De plus, @name renverra également la valeur définie plutôt que d'appeler la méthode que vous écrasez. Cependant, dans la solution ci-dessus, les deux fonctionneront très bien.
newdark-it
44

Utilisez le supermot - clé:

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

Inversement, pour remplacer le lecteur:

def attribute_name
  super.some_custom_decode
end
Robert Kajic
la source
1
Meilleure réponse que l'OMI acceptée car elle maintient l'appel de méthode limité au même nom. Cela préserve le comportement remplacé hérité de attribute_name =
Andrew Schwartz
Le remplacement de la méthode getter est devenu dangereux dans Rails 4.2 à cause de ce changement: github.com/rails/rails/commit / ... Auparavant, les assistants de formulaire appelaient la valeur untypecast du champ et n'appelaient pas votre getter personnalisé. Maintenant, ils appellent votre méthode et produiront donc des résultats confus dans vos formulaires en fonction de la façon dont vous remplacez la valeur.
Brendon Muir
16

Dans les rails 4

disons que vous avez un attribut d' âge dans votre tableau

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

Remarque: pour les nouveaux arrivants dans les rails 4, vous n'avez pas besoin de spécifier attr_accessible dans le modèle. Au lieu de cela, vous devez mettre en liste blanche vos attributs au niveau du contrôleur en utilisant la méthode d' autorisation .

Taimoor Changaiz
la source
3

J'ai trouvé que (au moins pour les collections de relations ActiveRecord) le modèle suivant fonctionne:

has_many :specialties

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

(Cela récupère les 3 premières entrées non dupliquées du tableau passées.)

Robin Daugherty
la source
0

Utilisation attr_writerpour écraser le setter attr_writer: nom_attribut

  def attribute_name=(value)
    # manipulate value
    # then send result to the default setter
    super(result)
  end
banane
la source