0
+module HasCachedValueExtension # :nodoc:
0
+ def self.included(base)
0
+ base.extend(ClassMethods)
0
+ # a very simple case in which has_cached_value works just like the .count method on a has_many association:
0
+ # class Company < ActiveRecord::Base
0
+ # has_cached_value :total_employees, :sql => 'select count(*) from employees where company_id = #{id}'
0
+ # a more sophisticated example:
0
+ # class User < ActiveRecord::Base
0
+ # has_many :sales, :through => :trinkets
0
+ # has_cached_value :remaining_trinket_sales_allotted, :sql => '... very complicated sql here ...'
0
+ # user = User.find(:first)
0
+ # user.remaining_trinket_sales_allotted # => 70
0
+ # Trinket.delete_all # <= any operation that would affect our value
0
+ # user.remaining_trinket_sales_allotted # => 70
0
+ # user.remaining_trinket_sales_allotted.reload # => 113
0
+ # 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
0
+ # in the context of the record instance.
0
+ # class User < ActiveRecord::Base
0
+ # has_cached_value :expensive_calculation, :eval => "some_big_expensize_calculation(self.id)"
0
+ # has_cached_value :other_expensive_process, :eval => Proc.new {|record| record.other_expensize_process }
0
+ def has_cached_value(association_id, options = {})
0
+ reflection = create_has_cached_value_reflection(association_id, options)
0
+ configure_dependency_for_has_cached_value(reflection)
0
+ reflection.options[:counter_cache] = reflection.options.delete(:cache) if reflection.options[:cache]
0
+ reflection.options[:counter_cache] ||= reflection.name unless false == reflection.options[:counter_cache]
0
+ association_accessor_method(reflection, ActiveRecord::Associations::HasCachedValueAssociation)
0
+ def configure_dependency_for_has_cached_value(reflection)
0
+ if reflection.options[:counter_cache] && reflection.options[:cache]
0
+ raise ArgumentError, ":cache is an alias for :counter_cache, don't use both options for has_cached_value in #{self.name}"
0
+ if !reflection.options[:sql] && !reflection.options[:eval]
0
+ raise ArgumentError, "You must specify either the :eval or :sql options for has_cached_value in #{self.name}"
0
+ if reflection.options[:sql] && reflection.options[:eval]
0
+ raise ArgumentError, ":eval and :sql are mutually exclusive options. You may specify one or the other for has_cached_value in #{self.name}"
0
+ def create_has_cached_value_reflection(association_id, options)
0
+ options.assert_valid_keys(:sql, :eval, :cache, :counter_cache)
0
+ reflection = ActiveRecord::Reflection::AssociationReflection.new(:has_cached_value, association_id, options, self)
0
+ write_inheritable_hash :reflections, association_id => reflection
0
+ def association_accessor_method(reflection, association_proxy_class)
0
+ define_method(reflection.name) do |*params|
0
+ force_reload = params.first unless params.empty?
0
+ association = instance_variable_get("@#{reflection.name}")
0
+ if association.nil? || force_reload
0
+ association = association_proxy_class.new(self, reflection)
0
+ instance_variable_set("@#{reflection.name}", association)
0
+ force_reload ? association.reload : association.load
0
+ association.target.nil? ? nil : association
0
+ class HasCachedValueAssociation
0
+ attr_reader :reflection
0
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$)/ }
0
+ def initialize(owner, reflection)
0
+ @owner, @reflection = owner, reflection
0
+ @owner.instance_variable_set("@#{@reflection.name}", nil)
0
+ def find_target_by_eval
0
+ if @reflection.options[:eval].is_a?(String)
0
+ eval(@reflection.options[:eval], @owner.send(:binding))
0
+ elsif @reflection.options[:eval].is_a?(Proc)
0
+ @reflection.options[:eval].call(@owner)
0
+ raise ArgumentError.new("The :eval option on a has_cached_value must be either a String or a Proc")
0
+ def find_target_by_sql
0
+ @owner.class.count_by_sql(sanitize_sql(interpolate_sql(@reflection.options[:sql])))
0
+ def find_target_from_cache
0
+ @owner.send(:read_attribute, @reflection.counter_cache_column) if has_cached_counter?
0
+ target = find_target_from_cache
0
+ target ||= @reflection.options[:sql] ? find_target_by_sql : find_target_by_eval
0
+ def update_cache(value)
0
+ return unless has_cached_counter?
0
+ unless @owner.new_record?
0
+ @owner.class.update_all(["#{@reflection.counter_cache_column} = ?", value], ["id = ?", @owner.id])
0
+ @owner.send(:write_attribute, @reflection.counter_cache_column, value)
0
+ return nil unless defined?(@loaded)
0
+ @target = find_target unless loaded?
0
+ def has_cached_counter?
0
+ @reflection.options[:counter_cache] && @owner.attribute_names.include?(@reflection.options[:counter_cache].to_s)
0
+ def interpolate_sql(sql, record = nil)
0
+ @owner.send(:interpolate_sql, sql, record)
0
+ @owner.class.send(:sanitize_sql, sql)
0
+ def method_missing(method, *args, &block)
0
+ @target.send(method, *args, &block)