* Copyright (C) 2006 Dimitrij Denissenko * http://www.dvisionfactory.com * http://retro.dvisionfactory.com/global-scope * svn://dvisionfactory.com/rails/plugins/global_scope * Please read LICENCE document for more information. This plugin allows to define ActiveRecord's method parameters with a global scope. The functionality is analogous to the original with_scope method, but the impact is exactly the opposite: with_scope applies selective scopes within given blocks, global_scope however acts globally but allows exceptional behaviour within witout_global_scope blocks. 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