Blog

POST #68 : Methodic, and be methodic

Added by Romain GEORGES 4 months ago

Version de production 1.2 de methodic

Bonjour, je viens de finir une nouvelle version de methodic suffisamment mature, une librairie Gem Ruby pour assister la gestion des arguments d'une méthode avec un hash d'options.
J'en suis certes le premier(seul) client, mais elle me rend de grands services.

voir :

a installer via :

  1. gem ins methodic

sur toute bonne Ruby-ready (so happy) machine

POST #56 : RSpec : Follow the Mock Turtle

Added by Romain GEORGES over 1 year ago

de la bonne pratique du test et du design logiciel

Imaginons un Composant logiciel qui doit executer des #action, un objet. Imaginons que comme il se doit, il loggue ses actions.
Allons-y de plus en plus élégamment (donc la première version c'est comme l'Australie en kangourou, il faut pas le faire !):

dans ./composant.rb :

 1 class Composant 
 2 
 3   def initialize
 4     # nothing 
 5   end
 6 
 7  def action 
 8    # a process
 9    puts 'action made' 
10  end
11 
12 end

Voila la c'est bien sale !
si on veut changer de méthode de logging, on modifie la classe Composant, methode :action ! c'est mal !

Déja, reprennons sur de bonne base :
TDD, même BDD => Rspec !! Behaviour driven Dev..

$ sudo gem install rspec 

On va donc écrire des Spec dans ./composant.spec:

1 require 'composant.rb'
2 describe Composant do
3   specify { described_class.class.should be_a_kind_of(Class) }
4   specify { described_class.should equal(Composant) }
5   subject { Composant::new }
6   specify { subject.should respond_to :action }
7 end

A priori notre Composant devrai faire l'affaire

 1 #rspec -fn composant.spec
 2 Composant
 3   should be a kind of Class
 4   should equal Composant
 5   should respond to #action
 6 
 7 Finished in 0.00138 seconds
 8 3 examples, 0 failures
 9 #

L'idéal est de créer une classe à part pour logguer, pour bien séparer les activités,
on va specifier une class de log Logutils dans ./logutils.spec

1 require 'logutils.rb'
2 describe Logutils do
3   specify { described_class.class.should be_a_kind_of(Class) }
4   specify { described_class.should equal(Logutils) }
5   subject { Logutils::new }
6   specify { subject.should respond_to :echo }
7 end

on lance rspec :

 1 #rspec -fn logutils.spec                                              
 2 /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:36:in `gem_original_require': no such file to load -- logutils.rb (LoadError)
 3     from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:36:in `require'
 4     from /home/lecid/logutils.spec:1
 5     from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/configuration.rb:698:in `load'
 6     from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/configuration.rb:698:in `load_spec_files'
 7     from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/configuration.rb:698:in `map'
 8     from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/configuration.rb:698:in `load_spec_files'
 9     from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/command_line.rb:22:in `run'
10     from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/runner.rb:80:in `run_in_process'
11     from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/runner.rb:69:in `run'
12     from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/runner.rb:10
13     from /usr/bin/rspec:19
14 #

Normal, on va donc implémenter Logutils dans le fichier ./logutils.rb :

1 class Logutils
2   def initialize
3   end
4   def echo(msg) 
5     return true
6   end
7 end

on lance rspec :

 1 #rspec -fn logutils.spec                                              
 2 Logutils
 3   should be a kind of Class
 4   should equal Logutils
 5   should respond to #echo
 6 
 7 Finished in 0.00134 seconds
 8 3 examples, 0 failures
 9 #

Bon début.
Mais revenons à notre premier cas de conscience, utiliser un logguer séparé dans notre Composant, on spécifie dans ./composant.spec.

 1 require 'composant.rb'
 2 describe Composant do
 3   specify { described_class.class.should be_a_kind_of(Class) }
 4   specify { described_class.should equal(Composant) }
 5   subject { Composant::new }
 6   specify { subject.should respond_to :action }
 7   context "attribut @logger" do
 8     specify { subject.instance_variable_get(:@logger).should be_an_instance_of Logutils }
 9   end
10 end

on relance rspec

 1 Composant
 2   should be a kind of Class
 3   should equal Composant
 4   should respond to #action
 5   attribut @logger
 6      (FAILED - 1)
 7 
 8 Failures:
 9 
10   1) Composant attribut @logger 
11      Failure/Error: specify { subject.instance_variable_get(:@logger).should be_an_instance_of Logutils }
12      NameError:
13        uninitialized constant Logutils
14      # ./composant.spec:8
15 
16 Finished in 0.00194 seconds
17 4 examples, 1 failure
18 
19 Failed examples:
20 
21 rspec ./composant.spec:8 # Composant attribut @logger

Allons donc coder ça dans ./composant.rb

 1 require 'logutils.rb'
 2 class Composant 
 3 
 4   def initialize
 5     @logger = Logutils::new 
 6   end
 7 
 8  def action 
 9    # a process
10    @logger.echo 'action made' 
11  end
12 
13 end

on relance rspec :

 1 Composant
 2   should be a kind of Class
 3   should equal Composant
 4   should respond to #action
 5   attribut @logger
 6     should be an instance of Logutils
 7     should be true
 8 
 9 Finished in 0.00165 seconds
10 4 examples, 0 failures

Bien !

Par contre @logger est obligratoirement un Logutils...
si on veut pour tester Composant sans alternative de code dans les deux classe, une seule solution le Mock Object
Ici, une bonne pratique s'impose, le découplage !!
il faut faire en sorte qu'un composant reçoive en paramètre d'initialisation un logger, encore mieux serait de faire une IOC ( inversion de contrôle), depuis une factory ou un gestionnaire de ressource (server d'application)

j'en viens directement au cas le plus élégant (avec détails et fioritures) :

d'abord les specs : ./logutils.spec (oui, elles sont précises !!)

 1 require './logutils.rb'
 2 describe Logutils do
 3   before :all do
 4     @logger = Logutils::new
 5     @agent_logger = Logutils::new('Agent')
 6   end
 7 
 8   specify { described_class.class.should be_a_kind_of(Class) }
 9   specify { described_class.should equal(Logutils) }
10   context "#initialize" do
11     context "Exceptions" do
12       it "should not raise ArgumentError if running without any arguments" do
13         lambda { Logutils::new }.should_not raise_error ArgumentError
14       end
15       it "should raise TypeError if running with one only non String argument" do
16         lambda { Logutils::new(10)}.should raise_error TypeError
17       end
18       it "should not raise ArgumentError if running with one String argument" do
19         lambda { Logutils::new('Agent')}.should_not raise_error ArgumentError
20       end
21       it "should raise ArgumentError if running with more than 1 argument" do
22         lambda { Logutils::new('Agent','arg')}.should raise_error ArgumentError
23       end
24     end
25     specify { @logger.should respond_to :echo }
26     context "when running #new" do
27       context "attribut @caller" do
28         specify { @logger.instance_variable_get(:@caller).should eq('unknown') }
29       end
30       context "attribut @last_message" do
31         specify { @logger.instance_variable_get(:@last_message).should eq('') }
32       end
33       context "attribut @last_args" do
34         specify { @logger.instance_variable_get(:@last_args).should eq([]) }
35       end
36     end
37     context "when running #new('Agent')"do
38       context "attribut @caller" do
39         specify { @agent_logger.instance_variable_get(:@caller).should eq('Agent') }
40       end
41       context "attribut @last_message" do
42         specify { @agent_logger.instance_variable_get(:@last_message).should eq('') }
43       end
44       context "attribut @last_args" do
45         specify { @agent_logger.instance_variable_get(:@last_args).should eq([]) }
46       end
47     end
48 
49   end
50 
51   context "#echo" do
52     context "Exceptions" do
53       subject { Logutils::new }
54       it "should raise ArgumentError if running without any arguments" do
55         lambda { subject.echo}.should raise_error ArgumentError 
56       end
57       it "should raise ArgumentError if running with one only non String argument" do
58         lambda { subject.echo(10)}.should raise_error ArgumentError 
59       end
60       it "should raise ArgumentError if running with many arguments but not all Strings" do
61         lambda { subject.echo('toto',10)}.should raise_error ArgumentError
62       end
63       it "should not raise ArgumentError if running with one String argument" do
64         lambda { subject.echo('message')}.should_not raise_error ArgumentError
65       end
66       it "should not raise ArgumentError if running with many String only arguments" do
67         lambda { subject.echo('message','arg')}.should_not raise_error ArgumentError
68       end
69     end
70     context "when running #echo('message')" do
71       specify { @logger.echo('message').should be_true }
72       context "attribut @last_message" do 
73         specify { @logger.instance_variable_get(:@last_message).should eq('message') }
74       end
75       context "attribut @last_args" do 
76         specify { @logger.instance_variable_get(:@last_args).should eq([]) }
77       end
78     end
79     context "when running #echo('message','arg1','arg2')" do
80       specify { @logger.echo('message','arg1','arg2').should be_true }
81       context "attribut @last_message" do 
82         specify { @logger.instance_variable_get(:@last_message).should eq('message') }
83       end
84       context "attribut @last_args" do 
85         specify { @logger.instance_variable_get(:@last_args).should eq(['arg1','arg2']) }
86       end
87     end
88   end
89 end

pour le code qui match ces specs, dans ./logutils.rb

 1 class Logutils
 2 
 3   def initialize (caller = nil)
 4     raise TypeError::new('Caller is not a String') unless caller.class == String or caller.nil?
 5     @caller = (caller.nil?)? 'unknown' : caller
 6     @last_message = String::new
 7     @last_args = Array::new
 8   end
 9 
10   def echo (message,*args)
11     raise ArgumentError::new('Message is not a String') unless message.class == String
12     args.each do |item|
13       raise ArgumentError::new("Arguments contains non String") unless item.class == String
14     end
15     @last_message = message
16     @last_args = args
17     puts "from #{@caller} send : #{message} with args : #{args.join(',')}" 
18     return true
19   end
20 end

Attention à l'execution de Rspec, cette class fait des 'puts', donc regardez bien la commande :

 1 #rspec -fn -c test.spec -o test.txt; cat test.txt
 2 from unknown send : message with args : 
 3 from unknown send : message with args : arg
 4 from unknown send : message with args : 
 5 from unknown send : message with args : arg1,arg2
 6 
 7 Logutils
 8   should be a kind of Class
 9   should equal Logutils
10   #initialize
11     should respond to #echo
12     Exceptions
13       should not raise ArgumentError if running without any arguments
14       should raise TypeError if running with one only non String argument
15       should not raise ArgumentError if running with one String argument
16       should raise ArgumentError if running with more than 1 argument
17     when running #new
18       attribut @caller
19         should eq "unknown" 
20       attribut @last_message
21         should eq "" 
22       attribut @last_args
23         should eq []
24     when running #new('Agent')
25       attribut @caller
26         should eq "Agent" 
27       attribut @last_message
28         should eq "" 
29       attribut @last_args
30         should eq []
31   #echo
32     Exceptions
33       should raise ArgumentError if running without any arguments
34       should raise ArgumentError if running with one only non String argument
35       should raise ArgumentError if running with many arguments but not all Strings
36       should not raise ArgumentError if running with one String argument
37       should not raise ArgumentError if running with many String only arguments
38     when running #echo('message')
39       should be true
40       attribut @last_message
41         should eq "message" 
42       attribut @last_args
43         should eq []
44     when running #echo('message','arg1','arg2')
45       should be true
46       attribut @last_message
47         should eq "message" 
48       attribut @last_args
49         should eq ["arg1", "arg2"]
50 
51 Finished in 0.0533 seconds
52 24 examples, 0 failures

Ensuite, et c'est la que ça devient vraiment intéressant !!!
Les specs du squelette du Composant et l'usage du 'Mock', il s'agit d'un squelette de Composant, cette spec n'est exhaustive, il manque notamment les Exceptions

./composant.spec

 1 describe Composant do
 2   before :all do
 3     @comp = Composant::new
 4     @comp_dummy = Composant::new(Logutils::new('Dummy'))
 5    end
 6   specify { described_class.class.should be_a_kind_of(Class) }
 7   specify { described_class.should equal(Composant) }
 8   subject { Composant::new }
 9   context "initialization" do
10     context "when running #new" do
11       context "attribut @logger" do
12         specify { @comp.instance_variable_get(:@logger).should be_an_instance_of Logutils }
13       end
14     end
15     context "when running #new(Logutils::new('Dummy'))" do
16       context "attribut @logger" do
17         specify { @comp_dummy.instance_variable_get(:@logger).should be_an_instance_of Logutils }
18       end
19     end
20   end
21   specify { subject.should respond_to :action }
22   context "#action" do
23     context "Exceptions" do
24 
25     end
26     context "when running" do
27       it "@logger should receive message :echo with arguments  'action',['arg1','arg2'] and return true" do
28         @aFakeLogutils = mock(Logutils)
29         @test = Composant::new(@aFakeLogutils)
30         testexpectation = @aFakeLogutils.should_receive(:echo).with('action',['arg1','arg2']).and_return(true)
31         @test.action('arg1','arg2')
32       end
33     end
34   end
35 end

Remarque : il ne semble pas possible de faire le mock dans le before :all, je ne sais pas pourquoi, je trouve ça dommage.

passons donc au code :

./composant.rb

 1 class Composant
 2 
 3   def initialize (alogger = Logutils::new(self.class.to_s))
 4     @logger = alogger
 5   end
 6 
 7   def action(*args)
 8     args.each do |item|
 9       raise ArgumentError::new("Arguments contains non String") unless item.class == String
10     end
11     return @logger.send(:echo,__method__.to_s,args) unless @logger.nil?
12     # do process #                                                                                                                                                          
13   end
14 
15 end

Les mesquins ne manquerons pas de noter la débauche d'énergie pour ça ! mais cette exemple doit-être situé dans un contexte plus large, la démarche doit être généralisée et cette fois-ci, le travail et le formalisme, fait une bonne fois pour toute, n'est plus a faire.

si on execute Rspec :

#rspec -fn -c test.spec -o test.txt; cat test.txt
Composant
  should be a kind of Class
  should equal Composant
  should respond to #action
  initialization
    when running #new
      attribut @logger
        should be an instance of Logutils
    when running #new(Logutils::new('Dummy'))
      attribut @logger
        should be an instance of Logutils
  #action
    when running
      @logger should receive message :echo with arguments  'action',['arg1','arg2'] and return true

Finished in 0.01601 seconds
6 examples, 0 failures

And "Voila" !!

POST #55 : ActiveLDAP + FreeBSD + [net|ruby|meuh|coin]-ldap

Added by Romain GEORGES over 1 year ago

net-ldap ou ruby-ldap avec Freebsd

Après 3h de souffrance, je vous donne ça comme ça vient.

N'utilisez pas ruby-ldap sur FreeBSD 7.X ou 8.X, ni en port, ni en gem, sinon ça SEGFAULT gentillement !

en gros d'urgence :

1 $ sudo zsh
2 # gem uni ruby-ldap
3 # pkg_delete ruby18-ldap-X.X.X-X
4 # gem ins net-ldap
 

POST #53 : Pourquoi je ne fait plus de Perl

Added by Romain GEORGES about 2 years ago

defined hash et Web Service

Imaginez un hash de données structurés servants à être sérialisées en XML pour être fournies par un Web service
On met en place un filtrage/routage pour déterminer les Web services vers lesquels on doit communiquer les données structurés.
Je passe le fait que le SOAP et Perl, c'est déjà de la science fiction, que le SOAP Document, c'est pas possible avec SOAP::Lite, WSDL j'oublie aussi...
Je passe aussi la faiblesse du support XML/Schema .....

J'en viens au cas bien précis :

on veut créer une méthode qui renvoie la valeur d'un élément si il existe, pour des fins de filtrages routages

tel que en Xpath, la recherche :

/racine/noeud/feuille[item[$valeur]

code de simulation,

1 %params = {};
2 $valeur = 'test';
3 return $params{'racine'}{'noeud'}{'feuille'}{$valeur} if defined($params{'racine'}{'noeud'}{'feuille'}{$valeur});

Et la, on pourrait croire que le defined ne va pas altérer mon hash, et bien si !!

toute l'arborescence vers feuille[$valeur] existe !!

1 $ perl -e 'use Data::Dumper; %params = (); $valeur = 'test'; print Dumper %params; print "tutu" if defined($params{'racine'}{'noeud'}{'feuille'}{$valeur}); print Dumper %params' 
2 $VAR1 = 'racine';
3 $VAR2 = {
4           'noeud' => {
5                        'feuille' => {}
6                      }
7         };

si on passe le hash par référence, et tout le monde fait ça chez les mongueurs, et bien on vient de fusiller son hash.

on va donc sérialiser :


<racine>
  <noeud>
   <feuille>
   </feuille>
  <noeud>
<racine>

Merci Perl !!

POST #47 : Redmine : I18n, Rails, ou Redmine ? (1 comment)

Added by Romain GEORGES over 2 years ago

bug, cause évidente, imputabilité plus problématique

L'internationnalisation, un sujet compliqué ? oui, il faut croire.

J'installe , un de mes gems en local sur le serveur Rails, pour faire des tests de déploiement sur le serveur de Gems d'Ultragreen.
Une dépendance volontaire de mon gem, I18n.

Et la après 20 minute, je m'aperçois que le helper application de Redmine:

1 def authoring(created, author, options={})
2     l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
3 end

Se prend les pieds dans le tapis.

genre "Ajouté par {{author}} il y a {{age}}"

On sent bien le problème finalement.

la méthode l()

Cette méthode est la méthode d'interpretation des chaînes I18n.
Et, cette version n'interpole pas les tokens !!
J'ai viré le gem I18n, touch tmp/restart.txt et ça remarche !!
Alors le coupable à l'air simple, le package I18n !!
Sauf que, le fait qu'il ne marche pas sur Redmine, vient de :

  1. Redmine qui surcharge l() pour lui faire interpoler et qui se fait piéger par le gestionnaire de dépendance de Rails 2.X (a tester avec le Bundler Rails 3) ?
  2. Redmine qui embarque en vendor/gems un I18n non officiel avec la dite surcharge ?
  3. Rails 2.X qui possède une version inline de I18n pas officielle ?
  4. I18n en gem qui est pas "officiel" ?

D'un point de vue général, il y a un truc pas uniforme dans la chaîne, et le package I18n gem à l'air "Fénéant".
Bref, pas drôle !! une new version de I18n iso avec la version utile pour Rails s'impose avec un joli Bundler Rails 3

S'il vous plais, portez Redmine sous Rails 3 !!

POST #45 : Comment-ça tu bosses pas le Week End ?

Added by Romain GEORGES over 2 years ago

ou comment gérer les charges humainement

résoudre un problème avec élégance, une des vertus de Ruby :

Le temps Business, qu'est-ce que c'est ?

c'est le temps effectivement chargeable !!

Genre ton chef te demande un truc, et en suite il te dit, tu pense qu'il te faut combien de temps ?

Toi innocent, tu lui réponds, "ben 4 jours..", et on est vendredi matin

Dans la tête du chef, vendredi + 4 ====> okai, tu me le mail lundi soir !!

Ou alors tu peux lui dire, rentre ou laisse moi rentrer mes charges dans mon superbe outils de timesheeting qui implémente :

business_time : https://github.com/bokmann/business_time

avec des jolies syntaxes du genre :

 1 1.business_hour.from_now
 2 4.business_hours.from_now
 3 8.business_hours.from_now
 4 
 5 1.business_hour.ago
 6 4.business_hours.ago
 7 8.business_hours.ago
 8 
 9 1.business_day.from_now
10 4.business_days.from_now
11 8.business_days.from_now

POST #41 : Ruby : parallelisation en distribution

Added by Romain GEORGES over 2 years ago

Drb, kiss ruby distribution

Faire de la distrubtion et/ou du parallelisme en Ruby rien de plus simple, avec DRB.
Interpréter du code à distance devient un jeu d'enfant.

Un serveur DRB ressemble à ça :

 1 class BoiteACalculs
 2 
 3     def addition(a,b)
 4      return a+b
 5     end
 6 
 7 end
 8 
 9 require 'drb/drb'
10 
11 bac = BoiteACalculs::new
12 DRb.start_service("druby://localhost:3030",bac)
13 DRb.thread.join
et le client à ça :
1 require 'drb/drb'
2 
3 bac = DRbObject.new_with_uri('druby://localhost:3030')
4 
5 p somme = bac.addition(10,23)

A partir de là, on s'imagine qu'il est simple de créer des RPC de façon trivial, de découper de application dans une architecture SOA de façon aussi trivial, etc...

Certain parle d'EJB ;) moi, aussi !

POST #35 : Ruby : design pattern itérateur et yield

Added by Romain GEORGES over 2 years ago

du bon usage des itérateurs et du "yield"

On commence par la base, Ruby permet de passer un bloc anonyme à une méthode ou un objet proc et de l'executer sur place avec l'aide du mot-clef "yield" (un des rares mots-clef du Ruby), tel que :

 1 def test1
 2   3.times { yield } if block_given? 
 3 end
 4 test1
 5 test1 { puts 'coucou'}
 6 # coucou
 7 # coucou
 8 # coucou
 9 => 3

ou encore

 1 def operateur(a)
 2   yield(a)
 3 end
 4 p operateur(14) {|a| a * a}
 5 # 196
 6 
 7 def calcul(a,b)
 8   yield(a,b)
 9 end
10 p calcul(14,18) {|a,b| a * b}
11 # 252

Si on voulait ré-implémenter le méthode times vue plus haut, rien de plus simple avec ce principe :

 1 class Fixnum 
 2   def fois
 3     for i in 1..self
 4       yield
 5     end
 6     return self
 7   end
 8 
 9   def fois_avec_index
10     for i in 1..self
11       yield(i)
12     end
13     return self
14   end
15 end
16 
17 3.fois { puts 'toto' }
18 # toto
19 # toto
20 # toto
21 => 3
22 3.fois_avec_index { |i| puts "toto de #{i}" }
23 # toto de 1
24 # toto de 2
25 # toto de 3
26 => 3

les itérateurs prennent toute leur importance avec les listes :

 1 class Array
 2   def cherche_le_premier_qui_match
 3     for i in 0...size
 4       valeur = self[i]
 5       return valeur if yield(valeur)
 6     end
 7     return nil
 8   end
 9 end
10 
11 p [1, 3, 5, 7, 9].cherche_le_premier_qui_match {|v| v*v > 10 }
12 # 5

Note : .size est donc self.size, la taille du Array

 1 class Array
 2   def cherche_tout_ceux_qui_match
 3     res = Array::new
 4     for i in 0...size
 5       valeur = self[i]
 6       res.push valeur if yield(valeur)
 7     end
 8     return res
 9   end
10 end
11 
12 [1, 3, 5, 7, 9].cherche_tout_ceux_qui_match {|v| v*v > 10 }
13 => [5,7,9]

Voila le principe de tous les itérateurs présent dans Ruby

POST #33 : Ruby : Design patterns : Forwardable

Added by Romain GEORGES over 2 years ago

Le design pattern Forwardable, le re-périmètrage d'objet

Le but de l'exercice est de créer une file d'attente ou Queue en anglais,
La file que l'on veut créer est bien spécifique : une FIFO (First In, First Out)

Il à plusieur approche au problème :

1/ la première c'est de se dire, La classe Array :
- est une liste
- possède des méthodes pour empiler et dépiler, vider, mesurer
=> remplie donc tous mes besoins

Donc j'utilise un Array

1 queue = Array::new
2 
3 queue.push 1
4 queue.push 2
5 queue.size
6 queue.pop

Mais hélas, je vais pourvoir faire aussi

1 queue = Array::new
2 
3 queue.push 1
4 queue.push 2
5 queue.size
6 queue.shift

Et là, je n'est pas fait une opération fonctionnellement conforme à une FIFO.

2/ On peut donc créer une classe Queue qui instancie un Array et créer les méthodes de Queue pour accéder à cette Array

 1 class Queue
 2 
 3   def initialize
 4       @q = [ ]    
 5   end
 6 
 7   def enq item
 8     @q.push item
 9   end
10 
11   def deq
12     return @q.pop
13   end
14 
15   def clear
16    @q.clear
17   end
18 
19   def first
20    @q.first
21   end
22 
23   def size
24    @q.size
25   end
26 
27 end

3/ la solution c'est d'utiliser le module forwardable qui va permettre de lier les méthodes nécessaires et suffisantes d'une queue à celle d'un Array en attribut de la classe :

 1 
 2 require 'forwardable'
 3 
 4 class Queue
 5     extend Forwardable
 6 
 7     def initialize
 8       @q = [ ]    # forward un Tableau
 9     end
10 
11     def_delegator :@q, :push, :enq
12     def_delegator :@q, :pop, :deq
13 
14     def_delegators :@q, :clear, :first, :size
15 end
16 

POST #29 : Ruby : Duck typing

Added by Romain GEORGES over 2 years ago

Bon ou mauvais concept

Pour présenter le Duck typing l'exemple type c'est le canard, bizarre non ?

1 class Canard
2  def faire_coincoin
3   puts "coincoin" 
4  end
5 end

Un canard fait coin coin, jusque la rien d'impressionnant, et ..

un homme parle !!

1 class Humain
2  def parle
3   puts "bla bla" 
4  end
5 end

Il s'agit de capacités intrinsèque
Donc si un canard est instancié, il sait ou pourra parler.
Et Si un homme est instancié, il nait, il sait ou pourra parler.

1 canard = Canard.new
2 humain = Humain.new

Maintenant, prenons le cas d'un imitateur et ajoutons lui une méthode singleton : faire_coincoin

 1 
 2 imitateur = Humain.new
 3 
 4 # Methode Singleton
 5 def imitateur.faire_coincoin
 6  puts "coin, coin !" 
 7 end
 8 
 9 canard.faire_coincoin 
10 imitateur.faire_coincoin 
11 humain.faire_coincoin 

Dans le noir il fera illusion et c'est tant mieux.

Le Duck typing est un concept où peut importe la nature d'un objet, on considère, qu'il peut évoluer, progresser, comme dans la vrai vie.

On ne nait pas Einstein, on le devient, en apprenant.
Il est trop simple de juger sur la nature simple et unique de l'objet, on doit le juger sur ses aptitudes.

Oui mais vous allez me dire comment le discriminer ou l'identifier le cas échéant ?

La réponse : en lui demandant si il sait faire ce pour quoi on l'interpèle et lui apprendre si il ne le sait pas.

 1 module Aptitudes_genious
 2 [...]
 3 # une parmis tant d'autres
 4   def integrer_par_partie(formule)
 5      [...]
 6      return resultat 
 7   end
 8 end
 9 [...]
10 end
11 
12 humain.extend Aptitudes_genious if humain.respond_to?(:integrer_par_partie)
13 # on appelle ça apprendre 
14 humain.integrer_par_partie(_formule)

Demander l'origine d'un objet (ou son identité) pour savoir ce qu'il sait faire est réducteur, limite discriminatoire ;).

Pour conclure, le duck typing est-il un bon concept, ma réponse, assurément OUI !

1 2 Next »

Also available in: Atom