Explanation of "alias_method"


#1

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


#2

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…


#3

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 H. - removed_email_address@domain.invalid - http://segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com


#4

Eric H. 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.


#5

On Feb 1, 2006, at 10:47 AM, Alex Y. wrote:

Eric H. 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 H. - removed_email_address@domain.invalid - http://segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com


#6

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


#7

On Feb 2, 2006, at 1:15 AM, Dimitrij Denissenko wrote:

def self.find(*args)
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 H. - removed_email_address@domain.invalid - http://segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com


#8

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:

def self.find(*args)
class SomeObject < MyRecord
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
removed_email_address@domain.invalid
http://lists.rubyonrails.org/mailman/listinfo/rails

-Ezra Z.
Yakima Herald-Republic
WebMaster
http://yakimaherald.com
509-577-7732
removed_email_address@domain.invalid