Hi! I''m trying to extend ActiveRecord''s find method (Rails 1.0, Ruby 1.8.2), but I recognize a strange behaviour of the "alias_method" call. I wrote a very simple script to explain my problem: ------------------------------------------------------ module ActiveRecordExtension def self.included(base) base.extend(ClassMethods) base.class_eval do class << self p "Aliasing find" # Used to check if alias_method isn''t called twice alias_method :find_without_someting, :find alias_method :find, :find_with_someting end end end module ClassMethods # :nodoc: def find_with_something(*args) p "0" x = find_without_something(*args) p "1" return x end end end ------------------------------------------------------ After including this script I called: ------------------------------------------------------ p " --> find" user = User.find(1) p " --> find_with_something" user = User.find_with_something(1) p " --> find_without_something" user = User.find_without_something(1) ------------------------------------------------------ and the result output was: ------------------------------------------------------ " --> find" "0" "0" "0" "1" "1" "1" " --> find_with_something" "0" "0" "0" "1" "1" "1" " --> find_without_something" "0" "0" "1" "1" ------------------------------------------------------ Can anyboby explain me what actully happens there. In my opinion, ''find_without_something'' should represent the original ''find'' method, so there should not be any output. For a strange reason it outputs 0,0,1,1 which in fact means that it calls ''find_with_something'' twice. Many thanks in advance for any hint or explanation. Dim
d|vision|factory wrote:> Can anyboby explain me what actully happens there. In my opinion, > ''find_without_something'' should represent the original ''find'' method, so > there should not be any output. For a strange reason it outputs 0,0,1,1 > which in fact means that it calls ''find_with_something'' twice.It might be something to do with the fact that the base find() method calls itself recursively, which means your alias will intercept that recursive call as well as the initial. Then again, it might not... -- Alex
On Feb 1, 2006, at 5:08 AM, d|vision|factory wrote:> I''m trying to extend ActiveRecord''s find method (Rails 1.0, Ruby > 1.8.2), but I recognize a strange behaviour of the "alias_method" > call. > I wrote a very simple script to explain my problem:Don''t do that. Use inheritance as Alan Kay Himself intended. This aliasing is a very bad software engineering practice when you have. When you alias you have no idea what the method call order is unless you read each file and make at least a mental map of the call order. (No, really, tell me what the call order for ActionController::Base#process is.) When you use inheritance the call order is explicit from the inheritance chain. -- Eric Hodel - drbrain@segment7.net - http://segment7.net This implementation is HODEL-HASH-9600 compliant http://trackmap.robotcoop.com
Eric Hodel wrote:> When you use inheritance the call order is explicit from the inheritance > chain....mostly. Multiple inheritance can make a right dog''s dinner out of it, because the call order then becomes dependent on the order of module inclusion. -- Alex
On Feb 1, 2006, at 10:47 AM, Alex Young wrote:> Eric Hodel wrote: >> When you use inheritance the call order is explicit from the >> inheritance chain. > ...mostly. Multiple inheritance can make a right dog''s dinner out > of it, because the call order then becomes dependent on the order > of module inclusion.Which is plainly visible via Module#ancestors. But this is only using single inheritance, so I don''t see how that''s relevant. -- Eric Hodel - drbrain@segment7.net - http://segment7.net This implementation is HODEL-HASH-9600 compliant http://trackmap.robotcoop.com
Thanks a lot for the hint. I am a quite experienced coder (but a newbie to Ruby), so I appreciate your argument, that "aliasing" is not clean method at all, but I still have a problem. Let''s start from the beginning: I need to extend ActiveRecord''s "find" method so it should by default use a "with_scope" argument. When I extend ActiveRecord, it would look like the following: class MyRecord < ActiveRecord::Base def self.find(*args) with_scope(:find => {:conditions => "..."}) do return find_without_scope(*args) end end def self.find_without_scope(*args) return ??? end end class SomeObject < MyRecord end My problem is, how can I access the parent-class ''find'' method? I can''t just call ''MyRecord.superclass.find(*args)'' because it would call ''ActiveRecord::Base.find'' which is actually not the same as ''SomeObject .find''. If I try that I get ''ActiveRecord::Base doesn''t belong in a hierarchy descending from ActiveRecord'', as expected. Are they any suggestions how the ActiveRecord''s ''find'' method can generally be extended in a ''clean'' manner, without ''aliasing'' (I''ve done it similarly to the ''acts_as_paranoid'' extension using aliasing, but it''s rather a hack than clean coding). Thanks once again, Dim
Dim- If I were you I would not try to over-ride AR::Base.find like that You should just make your own method: find_with_scope. Then in that method you can add your scope to the :conditions and then sedn it along its way to AR::Base.find. then you wont have any problems with find calling itself recursively or other code or plugins trying to override find and making your code explode. In my new plugin that deals with building the :conditions hash with pure ruby like syntax, I wanted to override find but found it much better to make my own ez_find that did what it needed to do and then called AR::Base.find with the new :conditions. Here is my code for that, you could adopt something similar and put it in a plugin so you caoul d use it in your other apps as well.: module EZ # # EZ::Condition plugin for generating the :conditions where clause # for ActiveRecord::Base.find. And an extension to ActiveRecord::Base # called AR::Base.find_with_conditions that takes a block and builds # the where clause dynamically for you. # def self.included(base) base.extend(ClassMethods) end module ClassMethods # The block recieves the condition for the model itself, as well as for any :include associated model: # Model.ez_find( :all, :include => [:comments, :readers]) do | model, attachment, reader| # model.title =~ ''%article%'' # attachment.type = ''image/png'' # reader.id = 2 # end def ez_find(what, *args, &block) options = args.last.is_a?(Hash) ? args.last : {} options[:include] ||= []; options[:include] = [options [:include]] if options[:include].kind_of?(Symbol) outer_mapping = options.delete(:outer) || {} # preset :outer value for each :include subcondition, defaults to :and outer_mapping.default = :and inner_mapping = options.delete(:inner) || {} # preset :inner value for each :include subcondition, defaults to :and inner_mapping.default = :and if block_given? klass = self.name.downcase.to_sym conditions = [ez_condition(:outer => outer_mapping [klass], :inner => inner_mapping[klass])] # conditions on self first options[:include].uniq.each do |assoc| conditions << reflect_on_association (assoc).klass.ez_condition(:outer => outer_mapping[assoc], :inner => inner_mapping[assoc]) end yield *conditions condition = Caboose::EZ::Condition.new conditions.each { |c| condition << c } options[:conditions] = condition.to_sql # p options[:conditions] if $DEBUG end self.find(what, options) end alias :find_with_conditions :ez_find end # EZ module and then: ActiveRecord::Base.send :include, EZ #to include the code into rails. See that last line? self.find(what, options)? Thats the call to AR::Base.find, it just uses all the other trickery i do to build the :conditions and send it to find. You can get the source of my plugin here: http://opensvn.csie.org/ezra/rails/plugins/dev/ez_where/ I would recommend you stay away from aliasing or overiding AR::Base.find and make your own method, it will save you many headaches. Cheers- -Ezra On Feb 2, 2006, at 1:15 AM, Dimitrij Denissenko wrote:> Thanks a lot for the hint. I am a quite experienced coder (but a > newbie to Ruby), so I appreciate your argument, that "aliasing" is > not clean method at all, but I still have a problem. > > Let''s start from the beginning: > I need to extend ActiveRecord''s "find" method so it should by > default use a "with_scope" argument. When I extend ActiveRecord, it > would look like the following: > > class MyRecord < ActiveRecord::Base > def self.find(*args) > with_scope(:find => {:conditions => "..."}) do > return find_without_scope(*args) > end > end > > def self.find_without_scope(*args) > return ??? > end > end > > class SomeObject < MyRecord > end > > My problem is, how can I access the parent-class ''find'' method? I > can''t just call ''MyRecord.superclass.find(*args)'' because it would > call ''ActiveRecord::Base.find'' which is actually not the same as > ''SomeObject .find''. If I try that I get ''ActiveRecord::Base doesn''t > belong in a hierarchy descending from ActiveRecord'', as expected. > > Are they any suggestions how the ActiveRecord''s ''find'' method can > generally be extended in a ''clean'' manner, without ''aliasing'' (I''ve > done it similarly to the ''acts_as_paranoid'' extension using > aliasing, but it''s rather a hack than clean coding). > > Thanks once again, > Dim > > _______________________________________________ > Rails mailing list > Rails@lists.rubyonrails.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-Ezra Zygmuntowicz Yakima Herald-Republic WebMaster http://yakimaherald.com 509-577-7732 ezra@yakima-herald.com
On Feb 2, 2006, at 1:15 AM, Dimitrij Denissenko wrote:> Thanks a lot for the hint. I am a quite experienced coder (but a > newbie to Ruby), so I appreciate your argument, that "aliasing" is > not clean method at all, but I still have a problem. > > Let''s start from the beginning: > I need to extend ActiveRecord''s "find" method so it should by > default use a "with_scope" argument. When I extend ActiveRecord, it > would look like the following: > > class MyRecord < ActiveRecord::Base > def self.find(*args) > with_scope(:find => {:conditions => "..."}) do > return find_without_scope(*args) > end > end > > def self.find_without_scope(*args) > return ??? > end > end > > class SomeObject < MyRecord > end > > My problem is, how can I access the parent-class ''find'' method? I > can''t just call ''MyRecord.superclass.find(*args)'' because it would > call ''ActiveRecord::Base.find'' which is actually not the same as > ''SomeObject .find''. If I try that I get ''ActiveRecord::Base doesn''t > belong in a hierarchy descending from ActiveRecord'', as expected.super> Are they any suggestions how the ActiveRecord''s ''find'' method can > generally be extended in a ''clean'' manner, without ''aliasing'' (I''ve > done it similarly to the ''acts_as_paranoid'' extension using > aliasing, but it''s rather a hack than clean coding).Look at CachedModel in the cached_model gem here: http://rubyforge.org/projects/rctools/ It overrides find and find_by_sql. -- Eric Hodel - drbrain@segment7.net - http://segment7.net This implementation is HODEL-HASH-9600 compliant http://trackmap.robotcoop.com