Forum: Ruby on Rails How to redefine find() method for ActiveRecord::Base??

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
1c46e952a61c99d9eae4f21037a088fd?d=identicon&s=25 anatol.pomozov (Guest)
on 2005-12-06 22:15
(Received via mailing list)
Hi Railers.

I am trying to add very simple caching system for ActiveRecord to my
application and I need advice from Ruby (or RoR) guru.
Everything that I need is redefine find method for some of my models. I
am
trying to do with folowing code

class ActiveRecord::Base
  def self.cacheable
    class_eval <<-end_eval
      alias old__find find

      def self.find(*args)
        puts "We are in find"
      end
    end_eval
  end
end

Then I adding cacheable to my model. But when I run test I have
following
error

C:/work/agora/agora/config/../lib/model_cachable.rb:3:in `module_eval':
(eval):2:in `module_eval': undefined method `find' for class `Category'
(NameError)
        from C:/work/agora/agora/config/../lib/model_cachable.rb:3:in
`module_eval'
        from C:/work/agora/agora/config/../lib/model_cachable.rb:3:in
`cacheable'
        from C:/work/agora/agora/config/../app/models/category.rb:2
        from
c:/progra~1/ruby/lib/ruby/gems/1.8/gems/activesupport-1.2.3/lib/active_support/dependencies.rb:207:in
`load'
        from
c:/progra~1/ruby/lib/ruby/gems/1.8/gems/activesupport-1.2.3/lib/active_support/dependencies.rb:207:in
`load'
        from
c:/progra~1/ruby/lib/ruby/gems/1.8/gems/activesupport-1.2.3/lib/active_support/dependencies.rb:39:in
`require_or_load'
        from
c:/progra~1/ruby/lib/ruby/gems/1.8/gems/activesupport-1.2.3/lib/active_support/dependencies.rb:22:in
`depend_on'
        from
c:/progra~1/ruby/lib/ruby/gems/1.8/gems/activesupport-1.2.3/lib/active_support/dependencies.rb:30:in
`associate_with'
         ... 35 levels...
        from C:/work/agora/agora/test/unit/../test_helper.rb:3

Seems that alias could not find function find(). Where it defined?? How
could I do alias for it?? Probably there is other simplier way to
overwrite
find() method??
3ccecc71b9fb0a3d7f00a0bef6f0a63a?d=identicon&s=25 ksruby (Guest)
on 2005-12-06 23:08
(Received via mailing list)
In development mode, Rails reevaluates all your model classes. So your
first
call to cacheable method works, but the second gives you this exception
since
it lost the reference to the original AR::Base.find method.

Try this

class ActiveRecord::Base
  def self.cacheable
    return if singleton_methods.include? 'old__find'
    class << self
      alias old__find find
      def find
        puts "We are in find"
      end
    end
  end
end

Kent.
3ccecc71b9fb0a3d7f00a0bef6f0a63a?d=identicon&s=25 ksruby (Guest)
on 2005-12-06 23:08
(Received via mailing list)
In development mode, Rails reevaluates all your model classes. So your
first
call to cacheable method works, but the second gives you this exception
since
it lost the reference to the original AR::Base.find method.

Try this

class ActiveRecord::Base
  def self.cacheable
    return if singleton_methods.include? 'old__find'
    class << self
      alias old__find find
      def find
        puts "We are in find"
      end
    end
  end
end

Kent.
821395fe70906c8290df7f18ac4ac6cf?d=identicon&s=25 technoweenie (Guest)
on 2005-12-06 23:44
(Received via mailing list)
> Seems that alias could not find function find(). Where it defined?? How
> could I do alias for it?? Probably there is other simplier way to overwrite
> find() method??

There is no clean way to override find().  Due to the way it takes
multiple types of parameters, it ends up calling itself.  To counter
this, I actually added a new option:

def find(*args)
  options = extract_options_from_args!(args)
  call_original_find = lambda { original_find(*(args << options)) }

  if !options[:with_deleted]
    with_deleted_scope { return call_original_find.call }
  end

  call_original_find.call
end

def find_with_deleted(*args)
  original_find(*(args <<
extract_options_from_args!(args).merge(:with_deleted => true)))
end

The whole plugin can be viewed at
http://techno-weenie.net/svn/projects/plugins/acts...

As the author of a plugin that overrides find, I would suggest not
doing it.  It's way more trouble than it's worth.

KIDS: Don't try this at home

--
rick
http://techno-weenie.net
1c46e952a61c99d9eae4f21037a088fd?d=identicon&s=25 anatol.pomozov (Guest)
on 2005-12-08 18:08
(Received via mailing list)
Thanks everyone for the help.

I redefine find() method but seems ActiveRecord does not intended for
it. It
is strange because there could be a lot of plugins that would require
some
proxying between user calls and db operations. act_as_paranoid one of
them.

The problem is that find() recursively calls find(). (See AR code). So
when
I call for example MyModel.find(3) it first calls my version of find, I
am
doing caching operations and then call original method (from AR). AR
adds
some params and also calls find() which is MINE version, and again I do
some
cache operation and call original find(). In my case it works (without
any
infinite recursion) but for complex cases it would be painful, I
believe.

One of the solutions could be adding to Ruby something like self() or
this()
which calls the same method, do recursion. But it would be problematic
to
ask Matz about it, I guess.

Anyway there is very simple cache code that just adds cache for calls
like
MyModel.find(1) and MyModel.find("434"). It works for me.

class ActiveRecord::Base
  def self.cacheable
    return if singleton_methods.include? '__old__find'

    module_eval <<-end_eval
      mattr_reader :cache
      @@cache = {}

      mattr_accessor :cache_hits
      @@cache_hits = 0

      def self.flush_cache
        @@cache = {}
        @@cache_hits = 0
      end
    end_eval

    class << self
      alias_method :__old__find, :find

      def find(*args)
        #Search only if there is only argument that number or string
representing number
        if args.size == 1
          arg1 = args[0]
          id =
          case arg1
            when Integer
              arg1
            when String
              arg1.to_i
            else
              nil
          end

          if id and id != 0
            if self.cache[id]
              self.cache_hits += 1
              return self.cache[id]
            end

            #If we did not find it in cache try to select from db
            obj = __old__find(*args)
            if obj
              return self.cache[id] = obj
            end
          end
        end
        return __old__find(*args)
      end
    end
  end
end
This topic is locked and can not be replied to.