Blog

POST #62 : const_missing, autoload V2, convention plutot que configuration

Added by Romain GEORGES 9 months ago

une autre façon de régler les pbm de require, convention/configuration

je viens de tomber sur un post de Noah Gibbs sur Rubyflow [[http://www.rubyflow.com/items/8222-no-more-requires-autoload-classes]] et je trouve le concept intéressant, et utilisé intelligemment, (j'ai juste un peu enrobé l'affaire):

On peu régler les pbms de require une bonne fois pour toutes en faisant une surcharge de const_missing sur l'Object Ruby.

tel que :

 1 # test_const_missing.rb
 2 class Object
 3   def self.const_missing(c)
 4     require "./plugin" 
 5     Plugin
 6   end
 7 end
 8 
 9 Plugin.new.print_test

Et le code du plugin :

1 # plugin.rb
2 class Plugin
3   def print_test
4     puts "test!" 
5   end
6 end

On peu généraliser, respectant le paradigme très Ruby, "Convention plutôt que configuration" :

de l'usage du snake_case (MonModuleGenial => mon_module_genial.rb), on crée ou ajoute dans sa/ses lib(s) utilitaire(s) :

1 # utils/to_underscore.rb
2 def to_underscore(string)
3   string.gsub(/::/, '/').
4   gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
5   gsub(/([a-z\d])([A-Z])/,'\1_\2').
6   tr("-", "_").
7   downcase
8 end

puis (je referai un post sur un solution complète d'init/utils avec recherche dans un Gem):

 1 require "utils/to_underscore" 
 2 # utils/override_object.rb
 3 class Object
 4   def self.const_missing(c)
 5     if c =~ /^Plugin.*$/ then 
 6       plug_file = to_underscore(c.to_s).sub("_",'s/')
 7       require plug_file
 8       Object.const_get(c)
 9       puts "Loading plugin : #{c.to_s}" if $VERBOSE
10      else
11         STDERR.puts "Missing constant: #{c.inspect}!" 
12      end
13   end
14 end

le code d'un plugin :

1 # plugins/mon_module_genial.rb
2 class PluginMonModuleGenial
3   def print_test
4     puts "test!" 
5   end
6 end
  1. dans le code de l'appli
1 PluginMonModuleGenial.new.print_test

POST #58 : Autoload => qui se traduit par chargement différé ...

Added by Romain GEORGES about 1 year ago

Autoload en Ruby ne veut pas dire chargement automatique

autoload

En regardant micon (post précédant), je suis tomber sur un verbe Ruby que je ne connaissais pas!
On peut faire du Ruby depuis 10 ans et être passé à côté de ça ? et bien oui !
autoload qui d'ailleur est un faux ami,
Vous voyez le AUTOLOAD (démoniaque) de Perl, et bien "ça na rien à voir!"(c) zj, ic, prae(s) et ... ça me rajeunit pas tout ça...
il ne s'execute pas comme un pre-hook au require d'un fichier, mais il fait autre chose, "c'est comme Gif-sur-Yvette, c'est différent..." (c) les mêmes, (spéciale dédicace pour Ic, je suis nostalgique moi en ce moment....)

Explication

En fait le autoload Ruby, c'est bien ! surtout quand on code modulaire et que potentiellement certains modules ne sont pas exploités ou rarement mais qu'on veut pas se faire _ pendant l'initialisation de l'application, je m'explique par l'exemple :

Exemple :


$ vi mon_module_optionel.rb
--

 1 
 2 module MonModule
 3 
 4   def MonModule::init
 5     puts 'initialisation'
 6     # init code
 7   end
 8 
 9   # methods ...
10   def self.test
11     #nop
12   end
13 
14 end
15 
16 MonModule::init
17 

test via irb

Avec require

irb(main):001:0> require 'mon_module_optionel'
initialisation
=> true

Avec autoload

irb(main):001:0> autoload :MonModule, 'mon_module_optionel'
=> nil
irb(main):002:0> include MonModule
initialisation
=> Object

Observation

On voit bien que le load réel (interpretation) est différé, mais le compilateur n'hurle pas de NameError, car il "s'attend" a trouver un namespace (module,classe) avec comme nom la Constante fournit à autoload.
en gros on enregistre le symbole du namespace :MonModule est on y l'associe à un wrapper qui va faire le require qui va charger le code, détachant le wrapper au profis du module ou de la classe voulue. c'est beau !

Dans l'exemple j'ai fait un include, mais MonModule.test aurait fournit le même résultat, jouer avec un classe aussi en faisant un new.

Also available in: Atom