JeuWeb - Crée ton jeu par navigateur
[Ruby on Rails] La délégation des méthodes - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : [Ruby on Rails] La délégation des méthodes (/showthread.php?tid=4398)



[Ruby on Rails] La délégation des méthodes - Sephi-Chan - 10-10-2009

La délégation des méthodes

ActiveSupport intègre un composant bien sympathique à la classe Module : la méthode delegate.
Cette méthode permet, comme son nom l'indique, de déléguer son travail à une autre méthode.

Le cas d'exemple est un jeu (Bugspirit) découpé en parties (les univers) pouvant accueillir un certain nombre de groupes de joueurs (les nuées).
Ce nombre de groupe est déterminé par la carte associée à l'univers. Une petite carte pourra accueillir un nombre restreint de groupe qu'une carte immense.

Et c'est là qu'entre en jeu le délégué : quand on demandera le nombre de joueurs qu'un univers peut accueillir, il ira demander cette information à sa carte.

Voici donc une partie de la classe Universe (très épurée, pour se focaliser sur l'exemple) :


class Universe < ActiveRecord::Base

belongs_to :map

has_many :participations
has_many Confusedwarms, :through => :participations,
:after_add => :try_to_flag_as_full

before_validation :default_locale
after_create :default_name
after_update Confusedtart_the_game, :if => :full_changed?
# full_changed? is an auto generated method. If the attribute "full" change during
# an update, the method start_the_game is executed.

delegate :maximum_number_of_swarms, :to => :map


protected

def try_to_flag_as_full
if swarms.count == maximum_number_of_swarms
self.full = true
save!
end
end

end


On précise que notre univers est associé à une carte grâce à une relation belongs_to :map (Map étant un autre modèle). Notre table universes contient une colonne map_id. Cette ligne ajoute (entre autre) à nos instances de la classe Universe une méthode map qui va chercher la carte associé à l'univers dans la base de données.

Grâce à la ligne delegate :maximum_number_of_swarms, :to => :map, on demande à nos instance de la classe Universe de répondre à la méthode maximum_number_of_swarms en appelant à son tour cette méthode sur l'objet map et en retournant le résultat.


Ainsi, les codes qui suivent sont équivalents :


@universe = Universe.find(1) # On récupère le territoire qui a 1 pour ID.

# Ceci :
@universe.maximum_number_of_swarms

# Est équivalent à :
@universe.map.maximum_number_of_swarms


Dans notre cas, à chaque fois qu'on ajoutera un nuée (Swarm) aux nuées associées à cet univers, le callback after_add sera appelé et vérifiera si — en comptant la nuée ajoutée — l'univers a accueilli le nombre maximal de groupes permis par la carte. Si c'est le cas, le flag qui indique si la partie est pleine, ce qui par ailleurs conduira au lancement de la partie grâce au callback after_update Confusedtart_the_game, :if => :full_changed?.


Notez que les méthodes délégués peuvent être délégués à des variables de classes, d'instance ou des constantes appartenant elle-mêmes à la classe en cours ou autre.



class Foo

CONSTANT_ARRAY = [ 0, 1, 2, 3 ]
@@class_array = [ 4, 5, 6, 7 ]

def initialize
@instance_array = [ 8, 9, 10, 11 ]
end

delegate Confusedum, :to => :CONSTANT_ARRAY
delegate :min, :to => Angry@class_array
delegate :max, :to => Angryinstance_array

end

Foo.new.sum # => 6
Foo.new.min # => 4
Foo.new.max # => 11


On peut également préfixer la méthode. Ainsi, si je veux connaître l'adresse de livraison d'une commande (qui est en fait l'adresse de la personne qui a commandé), appelée @order.person_address a plus de sens que d'appeller @order.address.


class Order < ActiveRecord::Base

belongs_to :person
delegate :address, :to => :client, :prefix => true

end

@order = Order.find(1)
@order.address # Non !
@order.person_address # Oui !


Notez que le préfixe peut être donné explicitement, ainsi, plutôt que de parler de l'adresse de la personne, on peut parler de l'adresse d'un client.


class Order < ActiveRecord::Base

belongs_to :person
delegate :address, :to => :client, :prefix => :customer

end

@order = Order.find(1)
@order.address # Non !
@order.person_address # Non plus !
@order.person.address # Oui on peut, on utilise pas de délégué en faisant ça.
@order.customer_address # Oui !


On peut aussi déléguer plusieurs méthodes en même temps :


class Order < ActiveRecord::Base

belongs_to :person
delegate :address, :name, :to => :client, :prefix => :customer

end


Enfin, parlons un peu de la robustesse du code.
Souvenez-vous de l'exemple de mon jeu. Quand on appelle la méthode maximum_number_of_swarms, elle est en fait appelée sur la carte associée à l'univers. Si cette carte n'existe pas, Ruby cherchera à appeler la méthode sur un objet inexistant (nil, puisque c'est ce que renverra la méthode map si l'univers n'a pas de carte) !

Pour éviter cela, on peut procéder à une petite modification :


class Universe < ActiveRecord::Base

belongs_to :map
delegate :maximum_number_of_swarms, :to => :map, :allow_nil => true

end

Ainsi, si on vient à demander le nombre de nuées que peut accueillir un univers alors que celui-ci n'a pas de carte associée, alors il se contentera simplement de répondre nil.

Voilà pour les délégués ! Smile


Si ce sujet vous a intéressé — que Ruby vous tente ou non — je vous serais reconnaissant de me donner votre ressenti afin que je puisse l'améliorer.


Sephi-Chan, qui ne pensait pas en écrire autant quand il a commencé…