Global Scope plugin

Plugin details

This plugin allows to define ActiveRecord's method parameters with a global scope.

Websitehttp://code.google.com/p/dvisionfactory/ Repositoryhttp://dvisionfactory.googlecode.com/svn/rails/plugins/global_scope/ Author Dimitrij Denissenko Tags scope LicenseMIT

Documentation

Install the plugin:
ruby script/plugin install http://dvisionfactory.googlecode.com/svn/rails/plugins/global_scope/

Example:

  # with_scope impact example
  class Article < ActiveRecord::Base
  end
  
  Article.find(:all)   # -> SELECT * FROM articles
  with_scope(:find => {:conditions => ["author_id = ?", 1]}) do
    Article.find(:all) # -> SELECT * FROM articles WHERE author_id = 1
  end

  # global_scope impact example
  class Article < ActiveRecord::Base
    global_scope(:find_committed_to_author, :find => {:conditions => ["author_id = ?", 1]})
  end

  Article.find(:all)   # -> SELECT * FROM articles WHERE author_id = 1
  without_global_scope(:find_committed_to_author) do
    Article.find(:all) # -> SELECT * FROM articles
  end



== Background

The plugin was originally not meant to be used in a usual application development process because general scopes are almost never a good idea. The purpose of the provided code is to fix an side-effect issue of multiple aliasing of ActiveRecord's 'find' method. Details of the issue and a possible integration example are given in the next chapters.

=== Side-effects of "find aliasing" - an example

Think of an example application that uses the acts_as_paranoid[http://www.agilewebdevelopment.com/plugins/acts_as_paranoid] and the default_order[http://www.agilewebdevelopment.com/plugins/default_order] plugins. Both alias the 'find' method, to append conditions and enhance its functionality:

default_order

  alias_method :find_without_order, :find
  alias_method :find, :find_with_order

  def find_with_order(*args)
	...  # new find method
  end


acts_as_paranoid

  alias_method :find_with_deleted,  :find
  ...
  def find(*args) 
    ... # new find method
  end


A problem occurs as soon as a model uses two(or more) 'find' manipulating extensions at the same time. Example:

  class Author < ActiveRecord::Base
    order_by "last_name" 
    acts_as_paranoid
    ...
  end


The first 'order_by' call aliases the original 'find' to 'find_without_order' and defines a 'find_with_order', which basically is the new 'find'. The second alias ('acts_as_paranoid') makes 'find_with_order' to 'find_with_deleted' and introduces a new, own 'find'.

So what exactly happens? The original, unaliased 'find' method has the following behaviour:

  Author.find(:all)                 # SELECT * FROM authors


After 'order_by "last_name"' the behaviour changes to:

  Author.find_without_order(:all)   # SELECT * FROM authors
  Author.find(:all)                 # SELECT * FROM authors ORDER BY last_name


And finally, after 'acts_as_paranoid':

  Author.find_with_deleted(:all)    # SELECT * FROM authors ORDER BY last_name
  Author.find(:all)                 # SELECT * FROM authors WHERE deleted_at IS NULL ORDER BY last_name


Some might already see sources for possible defects. Doing multiple 'find' aliasing almost certainly results in side-effects. Although in the example above, 'find' should (per definition) return only not-deleted records (those where 'deleted_at' is nil), a call like

  Author.find_without_order(:all, :order => "pre_name")


would break this definition and perform

  SELECT * FROM authors ORDER BY pre_name


statement, which certainly does not reflect the intention of the programmer.

=== Integration with existing applications

Just a short example of "How existing applications could be integrated with my global_scope plugin?":

Again, I will take recently published default_order extension as an example.
The original code looks like that:

  module DefaultOrder
    ...  
    module ClassMethods
      def order_by(order_string)
        self.class_eval %{
          class << self
            def find_with_order(*args)
              if args[1] 
                args[1][:order] = "#{order_string}" if args[1].is_a?(Hash) && !args[1][:order]
              else
                args[1] = {:order => "#{order_string}"}
              end
              find_without_order(*args)
            end
        
            alias_method :find_without_order, :find
            alias_method :find, :find_with_order
          end
        }
      end
    end
  end


A possible integration of global_scope could be:

  module DefaultOrder
    ...  
    module ClassMethods
      def order_by(order_string)
        self.class_eval do
          if self.respond_to?(:global_scope) # new way ... one single code line
            global_scope(:set_by_default_order_plugin, :find => {:order => order_string})
          else # old way
            class << self
              def find_with_order(*args)
                if args[1] 
                  args[1][:order] = "#{order_string}" if args[1].is_a?(Hash) && !args[1][:order]
                else
                  args[1] = {:order => "#{order_string}"}
                end
                find_without_order(*args)
              end
              alias_method :find_without_order, :find
              alias_method :find, :find_with_order
            end
          end
        end
      end
    end
  end

Further Documentation

There is currently no advanced documentation for this plugin.

New documentation

Edit plugin | Back in time (1 older version) | Last edited by: hardway, 7 months ago