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
JackDanger (author)
Wed Mar 12 20:31:33 -0700 2008
commit  c1544c5228fb8c690b3eb6e0e002d18640965ba4
tree    5d54e71b217da9d97479a5ebfef6258912355681
parent  9983606dee88432361c44b5cfea73ecaf6a62255
cached_values / lib / cached_values / cached_value.rb
100644 129 lines (106 sloc) 3.246 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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
require 'object_proxy'
  
module ActiveRecord
  class CachedValue < ObjectProxy
 
    def initialize(owner, reflection)
      @owner, @reflection = owner, reflection
      reset
    end
    
    def reset
      @target = nil
    end
 
    def load
      reset
      @target = find_target(true)
      update_cache(@target)
    end
 
    def reload
      @owner.instance_variable_set("@#{@reflection.name}", nil)
      load
      @owner.send @reflection.name
    end
    
    alias update reload
    
    def clear
      clear_cache
      @owner.instance_variable_set("@#{@reflection.name}", nil)
    end
 
    def target
      @target
    end
 
    def find_target(skip_cache = false)
      target = find_target_from_cache unless skip_cache
      unless target
        target ||= @reflection.options[:sql] ? find_target_by_sql : find_target_by_eval
      end
      target
    end
    
    protected
      
      def find_target_from_cache
        @owner.send(:read_attribute, cache_column) if has_cache?
      end
      
      def find_target_by_sql
        sql = sanitize_sql(interpolate_sql(@reflection.options[:sql]))
        result = @owner.class.connection.select_value(sql)
        result = typecast_result(result)
        result
      end
    
      def find_target_by_eval
        if @reflection.options[:eval].is_a?(String)
          eval(@reflection.options[:eval], @owner.send(:binding))
        elsif @reflection.options[:eval].is_a?(Proc)
          @reflection.options[:eval].call(@owner)
        elsif @reflection.options[:eval].is_a?(Symbol)
          @owner.send @reflection.options[:eval]
        else
          raise ArgumentError.new("The :eval option on a cached_values must be a String or a Proc or a Symbol")
        end
      end
      
      def cache_column
        @reflection.options[:cache]
      end
 
      def has_cache?
        @reflection.options[:cache] && @owner.attribute_names.include?(@reflection.options[:cache].to_s)
      end
      
      def clear_cache
        update_cache(nil)
      end
      
      def update_cache(value)
        return unless has_cache?
        unless @owner.new_record?
          @owner.class.update_all(["#{cache_column} = ?", value], ["id = ?", @owner.id])
        end
        @owner.send(:write_attribute, cache_column, value) unless @owner.frozen?
      end
      
      def typecast_result(result)
        if has_cache?
          typecast_sql_result_by_cache_column_type(result)
        else
          if result =~ /^\d+\.\d+$/
            result.to_f
          elsif result =~ /^\d+$/
            result.to_i
          else
            result
          end
        end
      end
      
      def typecast_sql_result_by_cache_column_type(result)
        type = @owner.column_for_attribute(cache_column).type
        case type
        when :integer
          result.to_i
        when :float
          result.to_f
        when :boolean
          [0,'0', 'NULL', 'nil', 'false', '', 'FALSE', 'False'].include?(result)
        else
          result
        end
      end
      
      def interpolate_sql(sql, record = nil)
        @owner.send(:interpolate_sql, sql, record)
      end
 
      def sanitize_sql(sql)
        @owner.class.send(:sanitize_sql, sql)
      end
      
  end
end