Forum: Ruby dynamic programming

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.
Frank T. (Guest)
on 2008-12-02 18:32
# I have a class Adam
# I want to modify the method("m_a") so that it will return the cached
result
# If no cached result is available, then return the original result
# I want to create a class method(AKA: macro) to make it DRY
#
# So far, I have issues like
# 1) dynmically define class variable
# 2) failed to include a module inside a method of a class
#
# Any suggestion or comments will be appreciated
#
class Adam
  def self.m_a
    ["Adam#m_a"]
  end
end

class CachedAdam
  caching_method :adam, :m_a

  def self.caching_method
    # this method should do something to make the following codes
  end
end


## CachedAdam#caching_method should make and load the following codes
#
# module AdamWithCache
#   def m_a_with_cache
#     CachedAdam.get_cached_m_a || m_a_without_cache
#   end
#   def self.include(base)
#     base.alias_method_chain :m_a, :cache
#   end
# end
# Adam.class_eval{ include AdamWithCache }
#
# class CachedAdam
#   def self.get_cached_m_a( adam_id )
#     @@cache && @@cache[adam_id]
#   end
#
#   def self.set_cached_m_a(hash_list)
#     hash_list.each do |k,v|
#       @@cache ||={}  # failed, any suggestion?
#       @@cache[k] = v
#     end
#   end
# end
Brian C. (Guest)
on 2008-12-02 23:16
Frank Tao wrote:
> # I have a class Adam
> # I want to modify the method("m_a") so that it will return the cached
> result
> # If no cached result is available, then return the original result
> # I want to create a class method(AKA: macro) to make it DRY

You might want to look at the 'memoize' method in ActiveRecord trunk.

> # So far, I have issues like
> # 1) dynmically define class variable

I suggest: don't use a class variable :-) An instance variable of the
class would be fine. But personally I wouldn't keep the memoized values
in the class; I'd keep them in the instances themselves.

> # 2) failed to include a module inside a method of a class

Look at Module.included for this, as shown below.

There are probably cleaner and/or more efficient ways than the
following, but it demonstrates the principle.

  module Cache
    def self.included(base)
      base.extend ClassMethods
    end

    module ClassMethods
      def caching_method(m)
        name = "orig_#{m}"
        var = "@__#{m}"
        alias_method name, m
        define_method(m) { |*args|
          return instance_variable_get(var) if
instance_variable_defined?(var)
          instance_variable_set(var, send(name,*args))
        }
      end
    end
  end

  class Adam
    include Cache
    def foo
      rand(100)
    end
    caching_method :foo
  end

  a = Adam.new
  p a.foo
  p a.foo
  p a.foo

Beware of what you really want here though. If foo takes arguments, do
you want foo(1) and foo(2) to be able to return different values? Do you
want them both to be cached? If so, I leave that as an exercise for you.
Ron F. (Guest)
on 2008-12-03 18:10
(Received via mailing list)
Brian C. wrote:
>> # 1) dynmically define class variable
>
> I suggest: don't use a class variable :-) An instance variable of the
> class would be fine. But personally I wouldn't keep the memoized values
> in the class; I'd keep them in the instances themselves.
>
I suggest: don't make blanket statements like that ;-)
That depends on what he wants the cache to accomplish.  If the cache
should cache across all instances of Adam, a class variable is
_exactly_ what he wants.  If it should only cache for the specific
instance of Adam an instance variable is what he wants.
   If there will be many instances of Adam, and if Adam can peform
operations that invalidate cache then a global cache is so much easier
to handle than one distributed over all instances of Adam...since two
instances of adam may have the same item in their caches, and if
instance one invalidates.. how do you find/invalidate the item in
instance 2?

RF
Frank T. (Guest)
on 2008-12-03 22:05
Frank Tao wrote:
> # I have a class Adam
> # I want to modify the method("m_a") so that it will return the cached
> result
> # If no cached result is available, then return the original result
> # I want to create a class method(AKA: macro) to make it DRY
> #
> # So far, I have issues like
> # 1) dynmically define class variable
> # 2) failed to include a module inside a method of a class
> #
> # Any suggestion or comments will be appreciated
> #
> class Adam
>   def self.m_a
>     ["Adam#m_a"]
>   end
> end
>
> class CachedAdam
>   caching_method :adam, :m_a
>
>   def self.caching_method
>     # this method should do something to make the following codes
>   end
> end
>
>
> ## CachedAdam#caching_method should make and load the following codes
> #
> # module AdamWithCache
> #   def m_a_with_cache
> #     CachedAdam.get_cached_m_a || m_a_without_cache
> #   end
> #   def self.include(base)
> #     base.alias_method_chain :m_a, :cache
> #   end
> # end
> # Adam.class_eval{ include AdamWithCache }
> #
> # class CachedAdam
> #   def self.get_cached_m_a( adam_id )
> #     @@cache && @@cache[adam_id]
> #   end
> #
> #   def self.set_cached_m_a(hash_list)
> #     hash_list.each do |k,v|
> #       @@cache ||={}  # failed, any suggestion?
> #       @@cache[k] = v
> #     end
> #   end
> # end

THANKS FOR YOUR HELPFUL RESPONSE. I FOUND THAT MY ORIGINAL HAD SOME
MISTAKES AND THEREFORE DID NOT REFLECT EXACTLY WHAT I WANT.
SO I UPDATE IT AS FOLLOWS:

class Adam
  def m_a
    "a"
  end
end

class CachedMethod
  @@cached_variables = {}

  def self.caching_method(model, method)
    model, method = model.to_s, method.to_s
    @@cached_variables["#{model}_#{method}_cache"] = {}

    (class << self; self; end).instance_eval do
      define_method "set_cached_#{model}_#{method}" do |hash_list|
        hash_list.each do |k,v|
          @@cached_variables["#{model}_#{method}_cache"][k] = v
        end
      end
      define_method "get_cached_#{model}_#{method}" do |model_id|
        @@cached_variables["#{model}_#{method}_cache"] &&
          @@cached_variables["#{model}_#{method}_cache"][model_id]
      end
    end


    #
    # Purpose: create a module and let it be included by Adam class
    # NOT WORKING, any suggestion?
    self.class_eval <<-EOD
     module AdamWithCache
       def m_a_with_cache
         CachedMethod.get_cached_adam_m_a(self.id) || m_a_without_cache
       end
       def self.include(base)
         base.alias_method_chain :m_a, :cache
       end
     end
     Adam.class_eval{ include AdamWithCache }
    EOD
  end

  def self.reset
    @@cached_variables = {}
  end

  self.caching_method :adam, :m_a

end

#module AdamWithCache
#  def m_a_with_cache
#    CachedMethod.get_cached_adam_m_a(self.id) || m_a_without_cache
#  end
#  def self.include(base)
#    base.alias_method_chain :m_a, :cache
#  end
#end
#Adam.class_eval{ include AdamWithCache }


#
#Example usage:
#=============================================

puts @adam_1 = Adam.new  #==> assume: @adam.id == 1
puts @adam_2 = Adam.new  #==> assume: @adam.id == 2
puts @adam_3 = Adam.new  #==> assume: @adam.id == 3
puts @adam_1.m_a         #==> "a"
puts @adam_2.m_a         #==> "a"
puts @adam_3.m_a         #==> "a"
puts @adam_1.id         #==> 1
puts @adam_2.id         #==> 2
puts @adam_3.id         #==> 3

hash_list = {@adam_1.id => "b", @adam_2.id => "c"}
CachedMethod.set_cached_adam_m_a(hash_list)
puts CachedMethod.get_cached_adam_m_a(@adam_1.id)  #==> "b"
puts @adam_1.m_a         #==> "b"    !!! DID NOT OUTPUT AS I EXPECTED
puts @adam_2.m_a         #==> "c"    !!! DIDO
puts @adam_3.m_a         #==> "a"    !!! DIDO

CachedMethod.reset
puts @adam_1.m_a         #==> "a"
puts @adam_2.m_a         #==> "a"
puts @adam_3.m_a         #==> "a"
Robert D. (Guest)
on 2008-12-04 00:09
(Received via mailing list)
On Wed, Dec 3, 2008 at 5:04 PM, Ron F. <removed_email_address@domain.invalid> 
wrote:
>> You might want to look at the 'memoize' method in ActiveRecord trunk.
>>
>>> # So far, I have issues like
>>> # 1) dynmically define class variable
>>
>> I suggest: don't use a class variable :-) An instance variable of the
>> class would be fine. But personally I wouldn't keep the memoized values in
>> the class; I'd keep them in the instances themselves.
>>
> I suggest: don't make blanket statements like that ;-)
Correct, this should be thoroughly explained, well I will try ;)
> That depends on what he wants the cache to accomplish.  If the cache should
> cache across all instances of Adam, a class variable is _exactly_ what he
> wants.  If it should only cache for the specific instance of Adam an
> instance variable is what he wants.
This might indeed be what he wants, but if it is he should be aware of
the implications that it has. It is against one of the most valued
principles of OO design, a class shall nothing know about its
subclasses (1). Now I agree that occasionally this principle shall be
violated for good reason. But I have not yet seen a use case for class
variables and IIRC not many have on this list. That is why by
instinct, and instinct can be wrong of course, many of us warn against
class variables, and sometimes without thorough explications.
>  If there will be many instances of Adam, and if Adam can peform operations
> that invalidate cache then a global cache is so much easier to handle than
> one distributed over all instances of Adam...since two instances of adam may
> have the same item in their caches, and if instance one invalidates.. how do
> you find/invalidate the item in instance 2?
And how would that be solved by class variables if I may ask?

(1) this is not necessarily easy to see, but assume this code and
please forgive me for shouting ;)
class Supa
  @@supa = :super
  def self.supa; @@supa end
end

p Supa.supa

class Dupa < Supa
  @@supa = :duper
  def self.supa; @@supa end
end

p Dupa.supa
p Supa.supa ### AND THAT REALLY HURTS

Cheers
Robert
--
Ne baisse jamais la tête, tu ne verrais plus les étoiles.

Robert D. ;)
Frank T. (Guest)
on 2008-12-04 16:55
Brian C. wrote:
> Frank Tao wrote:
>> # I have a class Adam
>> # I want to modify the method("m_a") so that it will return the cached
>> result
>> # If no cached result is available, then return the original result
>> # I want to create a class method(AKA: macro) to make it DRY
>
> You might want to look at the 'memoize' method in ActiveRecord trunk.

'memoize'? Do you mean by the one in Rails 2.2 under
ActiveSupport::Memoizable ?

I also found Rails.cache or RAILS_CACHE in Rails 2.1 very helpful.
This topic is locked and can not be replied to.