home icon contact icon rss icon

Archive for rails

RailsEngines 2.3 (suite)

La solution dans l’article précédent ne me plaisait pas, alors j’ai fais ma solution … bon j’avoue j’ai pas cherché s’il existe déjà une solution prête à l’emploi.

Personnellement, je préfère que config. reload_plugins soit à false. Mais je ne veux pas mettre dans mes plugins un bout de code qui dépend plus de l’application et du contexte (development/production) que du plugin lui même.

Je pourrais mettre reload_plugin à true, oui mais je n’ai pas forcément envie de recharger toute la masse de plugin à chaque requête.

Dans un premier temps, on va ajouter une directive de configuration à savoir reloadable_plugins, et on va la traiter au moment du chargement des plugins. Voici le code :

Rails::Configuration.class_eval do
  attr_accessor :reloadable_plugins

  def initialize_with_reloadable_plugins
    initialize_without_reloadable_plugins
    self.reloadable_plugins = default_reloadable_plugins
  end

  def default_reloadable_plugins
    []
  end

  alias_method_chain :initialize, :reloadable_plugins
end

Rails::Plugin::Loader.class_eval do
  def add_plugin_load_paths_with_reloadable_plugins
    load_path = add_plugin_load_paths_without_reloadable_plugins
    plugins.each do |plugin|
      if configuration.reloadable_plugins.include?(plugin.name.to_sym)
        ActiveSupport::Dependencies.load_once_paths -= plugin.load_paths
      end
    end
    load_path
  end
  alias_method_chain :add_plugin_load_paths, :reloadable_plugins
end

qu’on va mettre dans lib/configuration_reloadable_plugins.rb par exemple.

Ensuite ajouter dans le fichier config/environnement.rb, juste après le boot.rb (l’ordre est important)

  ...
require File.join(File.dirname(__FILE__), 'boot')
require File.join(File.dirname(__FILE__), '../vendor/plugins/engines/boot')
require 'lib/configuration_reloadable_plugins'
  ...

Et toujours dans config/environnement.rb on indique les plugins à recharger :

...
Rails::Initializer.run do |config|
  config.reloadable_plugins = [ :foo, :bar ]
  config.reload_plugins = false 
  ...
end
...

Et pour vérifier que c’est bon :

$ script/console
>> ActiveSupport::Dependencies.load_once_paths.select{ |p| p =~ /foo/ }
=> []

RailsEngines 2.3

Les problèmes avec rails 2.3 continuent.

J’utilise depuis longtemps le plugin rails-engines qui permet de transformer une application en un plugin.

Et nouveauté de la version 2.3 de rails, RailsEngine y a été incorporé. Mais comme certains plugins qui ont été incorporé (has_finder, globalize, …) à chaque fois ce n’est pas complètement, bref il faut quand compléter les fonctionnalités en installant le plugin engine expurgé du code commun avec rails.

Mais bon ça bug encore, on obtient un :

A copy of X has been removed from the module tree but is still active!

ou bien un

can’t dup NilClass

uniquement en mode dev … grrr

Alors heureusement, d’autres sont passé par là et certains ont déjà résolu le problème en ajoutant un unloadable dans la classe concernée

class Toto
  unloadable
end

That’s it !

UPDATE :

En lisant les commentaires en fait ce n’est pas la bonne solution. En fait il faut lire la doc, enfin la doc dans le code :) dans railties/lib/initializer.rb :

    # Enables or disables plugin reloading.  You can get around this setting per plugin.
    # If reload_plugins? is false, add this to your plugin's init.rb
    # to make it reloadable:
    #
    #   ActiveSupport::Dependencies.load_once_paths.delete lib_path
    #
    # If reload_plugins? is true, add this to your plugin's init.rb
    # to only load it once:
    #
    #   ActiveSupport::Dependencies.load_once_paths << lib_path
    #

C’est clair ! soit d’office vous ne voulez pas du tout recharger les plugins (reload_plugin à false), et seulement certains (ceux que vous développez par exemple) alors il faut mettre dans le init.rb :

# config/environments/development.rb
config.reload_plugins = false

# plugin's init.rb : remove plugin from unloadable list
ActiveSupport::Dependencies.load_once_paths -= load_paths

soit l’inverse vous voulez que tous les modules soient rechargeable (reload_plugins à true), sauf certains :


# config/environments/development.rb
config.reload_plugins = true

# plugin's init.rb : add plugin in unloadable list
ActiveSupport::Dependencies.load_once_paths += load_paths

UPDATE 2 ou alors encore mieux voir l’article sur l’extension pour rails

Divers problèmes avec Rails 2.2+

Ah les joies de se mettre à jour et son code à l’occasion … Oui depuis longtemps je trainais avec rails 1.2.6 (oui je sais ça commence à dater mais j’ai mes raisons). Il me tardait donc de pouvoir utiliser les nouvelles versions de rails avec son lots d’améliorations.

Bon tout d’abord grosse surprise un bug uniquement présent en development, et pour cause il concerne le système de déchargement/rechargement du code de l’application.

ça donne soit un

stack level too deep

ou alors un pénible

vendor/rails/activerecord/lib/active_record/attribute_methods.rb:142:in `create_time_zone_conversion_attribute?'
vendor/rails/activerecord/lib/active_record/attribute_methods.rb:75:in `define_attribute_methods'
vendor/rails/activerecord/lib/active_record/attribute_methods.rb:71:in `each'
vendor/rails/activerecord/lib/active_record/attribute_methods.rb:71:in `define_attribute_methods'
vendor/rails/activerecord/lib/active_record/attribute_methods.rb:242:in `method_missing'

Le problème semble être valable depuis rails 2.2 et toujours pas corrigé. Il semble que ce ne soit pas une priorité car le problème se pose uniquement en mode development.

Donc la solution est de patcher rails en attendant. Patch qui se trouve dans le ticket chez lighthouseapp :

if ENV['RAILS_ENV'] != 'production'
  class ActiveRecord::Base
    class_eval do
      def self.reset_subclasses
        nonreloadables = []
        subclasses.each do |klass|
          unless ActiveSupport::Dependencies.autoloaded? klass
            nonreloadables << klass
            next
          end
          klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
          klass.instance_methods(false).each { |m| klass.send(:undef_method, m) unless m =~ /^id(=$|\?$|$)/ }

        end
        @@subclasses = {}
        nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
      end
    end
  end
end

Voilà et maintenant ça roule on peut développer en paix.

with_scope & joins

Dans rails, on trouve des pépites et with_scope en est une. Une vieille pépite et toujours pas finie, je serais tenté de dire.

Par exemple, il ne tient pas compte de la clause :order.

De mon côté, j’ai besoin d’un ordre précis pour faire le merge de mes jointures.

Prenons l’exemple suivant :

class Dumb
  class << self

    def foo_bar
      with_scope(:find => { :join => "INNER JOIN bar ON bar.id=foo.bar_id"}) do
        want_foo
      end
    end

    def want_foo
        with_scope(:find => { :join => "INNER JOIN foo ON foo.id=dump.foo_id"}) do
        # do your stuff
        ...
        end
    end

  end
end
 ... INNER JOIN bar ON bar.id=foo.bar_id INNER JOIN foo ON foo.id=dump.foo_id ...
... ActiveRecord::StatementInvalid: Mysql::Error: Unknown column 'foo.bar_id' ..

On souhaite avoir l’ordre inversé genre :

 ... INNER JOIN foo ON foo.id=dump.foo_id INNER JOIN bar ON bar.id=foo.bar_id  ...

Voici la solution :

    ...
    do_reverse_merge_joins do
      def foo_bar
        with_scope(:find => { :join => "INNER JOIN bar ON bar.id=foo.bar_id"}) do
          want_foo
        end
      end
    end
    ...

et le patch ou l’extension comme vous voulez (testé pour rails 2.3)

ActiveRecord::Base.class_eval do
  class << self
    def merge_joins_with_reverse(*joins)
      merge_joins_without_reverse(reverse_merge_joins? ? joins.reverse : joins)
    end
    alias_method_chain :merge_joins, :reverse

    def do_reverse_merge_joins(&block)
      set_reverse_merge_joins
      result = yield
      reset_reverse_merge_joins
      result
    end

    def reset_reverse_merge_joins
      set_reverse_merge_joins(nil)
    end

    def set_reverse_merge_joins(value = true)
      Thread.current[:"#{self}_reverse_merge_joins"] = value
    end

    def reverse_merge_joins?
      Thread.current[:"#{self}_reverse_merge_joins"]
    end

  end
end