public
Rubygem
Description: Rails Plugin - memoize and store to the db a record attribute generated by any expression or SQL query.
Homepage: http://6brand.com
Clone URL: git://github.com/JackDanger/cached_values.git
studioda (author)
Fri Aug 31 08:58:19 -0700 2007
commit  36fe5e1742b2e2e5b1bcb4e1445ba178b82d539b
tree    8f9f51588cdb206cb5ecde592c53085711add4dd
parent  72148ad1d259541dfb68e03fe479669c15fa91c0
cached_values / lib / cached_values.rb
100644 83 lines (70 sloc) 3.234 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
module CachedValues # :nodoc:
  def self.included(base)
    base.extend(ClassMethods)
  end
  module ClassMethods
    # USAGE:
    #
    # a very simple case in which cached_values works just like the .count method on a has_many association:
    #
    # class Company < ActiveRecord::Base
    # caches_value :total_employees, :sql => 'select count(*) from employees where company_id = #{id}'
    # end
    #
    # a more sophisticated example:
    #
    # class User < ActiveRecord::Base
    # has_many :trinkets
    # has_many :sales, :through => :trinkets
    # caches_value :remaining_trinket_sales_allotted, :sql => '... very complicated sql here ...'
    # end
    #
    # user = User.find(:first)
    # user.remaining_trinket_sales_allotted # => 70
    # Trinket.delete_all # <= any operation that would affect our value
    # user.remaining_trinket_sales_allotted # => 70
    # user.remaining_trinket_sales_allotted.reload # => 113
    #
    # You can also calculate the value in Ruby. This can be done by a string to be eval'ed or a Proc. Both are evaluated
    # in the context of the record instance.
    #
    # class User < ActiveRecord::Base
    # caches_value :expensive_calculation, :eval => "some_big_expensize_calculation(self.id)"
    # caches_value :other_expensive_process, :eval => Proc.new {|record| record.other_expensize_process }
    # end
    #
    
    def caches_value(name, options = {})
      reflection = create_cached_value_reflection(name, options)
 
      configure_dependency_for_cached_value(reflection)
 
      reflection.options[:cache] ||= reflection.name unless false == options[:cache]
 
      cached_value_accessor_method(reflection, ActiveRecord::CachedValue)
    end
 
    private
    
      def configure_dependency_for_cached_value(reflection)
      
        if !reflection.options[:sql] && !reflection.options[:eval]
          raise ArgumentError, "You must specify either the :eval or :sql options for caches_value in #{self.name}"
        end
      
        if reflection.options[:sql] && reflection.options[:eval]
          raise ArgumentError, ":eval and :sql are mutually exclusive options. You may specify one or the other for caches_value in #{self.name}"
        end
      end
    
      def create_cached_value_reflection(name, options)
        options.assert_valid_keys(:sql, :eval, :cache)
        
        reflection = ActiveRecord::Reflection::MacroReflection.new(:cached_value, name, options, self)
        write_inheritable_hash :reflections, name => reflection
        reflection
      end
 
      def cached_value_accessor_method(reflection, association_proxy_class)
        define_method(reflection.name) do |*params|
          force_reload = params.first unless params.empty?
          association = instance_variable_get("@#{reflection.name}")
          
          if association.nil? || force_reload
            association = association_proxy_class.new(self, reflection)
            instance_variable_set("@#{reflection.name}", association)
            force_reload ? association.reload : association.load
          end
          association.target.nil? ? nil : association
        end
      end
  end
end