0
module AssociationCache
0
def self.extended(base)
0
alias_method_chain :belongs_to, :cache
0
+ alias_method_chain :has_and_belongs_to_many, :cache
0
@@ -15,13 +24,17 @@ module ActiveRecord
0
belongs_to_without_cache(association_name, options)
0
- association_id_name = options[:foreign_key
_id] || "#{association_name}_id"
0
+ association_id_name = options[:foreign_key
] || "#{association_name}_id"
0
association_class = options[:class_name] || association_name.to_s.classify
0
def #{association_name}_with_cache
0
- id = #{association_id_name}
0
- Cache.get("#{association_class}::\#{id}") do
0
+ if ActiveRecord::AssociationCache.active?
0
+ id = #{association_id_name}
0
+ Cache.get("#{association_class}::\#{id}") do
0
+ #{association_name}_without_cache
0
#{association_name}_without_cache
0
@@ -38,16 +51,41 @@ module ActiveRecord
0
configure_dependency_for_has_many(reflection)
0
- collection_reader_method(reflection, HasManyThroughAssociation)
0
- collection_accessor_methods(reflection, HasManyThroughAssociation, false)
0
+ collection_reader_method(reflection, ::ActiveRecord::Associations::HasManyThroughAssociation)
0
+ collection_accessor_methods(reflection, ::ActiveRecord::Associations::HasManyThroughAssociation, false)
0
add_multiple_associated_save_callbacks(reflection.name)
0
add_association_callbacks(reflection.name, reflection.options)
0
collection_accessor_methods(reflection,
0
- cached ? ActiveRecord::Associations::CachedHasManyAssociation :
0
- ActiveRecord::Associations::HasManyAssociation)
0
+ cached ? ::ActiveRecord::Associations::CachedHasManyAssociation :
0
+ ::ActiveRecord::Associations::HasManyAssociation)
0
+ def has_and_belongs_to_many_with_cache(association_id, options = {}, &extension)
0
+ cached = options.delete(:cached)
0
+ return has_and_belongs_to_many_without_cache(association_id, options, &extension) unless cached
0
+ reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
0
+ add_multiple_associated_save_callbacks(reflection.name)
0
+ collection_accessor_methods(reflection,
0
+ ::ActiveRecord::Associations::CachedHasAndBelongsToManyAssociation)
0
+ # Don't use a before_destroy callback since users' before_destroy
0
+ # callbacks will be executed after the association is wiped out.
0
+ old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
0
+ class_eval <<-end_eval unless method_defined?(old_method)
0
+ alias_method :#{old_method}, :destroy_without_callbacks
0
+ def destroy_without_callbacks
0
+ #{reflection.name}.clear
0
+ add_association_callbacks(reflection.name, options)
0
@@ -56,14 +94,146 @@ ActiveRecord::Base.class_eval do
0
class ActiveRecord::Base
0
+ def find_with_cache(*args)
0
+ options = args.extract_options!
0
+ if [:first, :all].include?(args.first)
0
+ options[:select] = "#{quoted_table_name}.id"
0
+ results = find(*(args << options)) # make faster like for collections
0
+ CacheHelper.retrieve_records(results.map(&:id), self)
0
+ CacheHelper.retrieve_records([results.id], self).first
0
+ Cache.get("#{name}::#{id}") do
0
+ find(*(args << options))
0
+ Cache.put(cache_key, self)
0
+ Cache.delete(cache_key, self)
0
- "#{self.class.name}::#{self.id}"
0
+ key_class = self.class
0
+ while key_class.superclass != ActiveRecord::Base && key_class.superclass != Object
0
+ key_class = key_class.superclass
0
+ "#{key_class.name}::#{self.id}"
0
+ def retrieve_records(ids, klass)
0
+ cache_keys = ids.map { |id| "#{klass.name}::#{id}" }
0
+ record_hash = Cache.get_multiple(cache_keys)
0
+ if record_hash.size < ids.size
0
+ record_ids = record_hash.keys.map { |k| k.gsub(/.*::/, '').to_i}
0
+ missing_record_ids = ids.select { |id| !record_ids.include?(id) }
0
+ missing_records = klass.find(:all,
0
+ :conditions => ["#{klass.quoted_table_name}.id in (?)", missing_record_ids])
0
+ missing_records.each do |record|
0
+ Cache.put(record.cache_key, record)
0
+ missing_records.each { |record| record_hash[record.cache_key] = record }
0
+ ids.collect { |id| record_hash["#{klass.name}::#{id}"] }
0
+ module AssociationHelper
0
+ def select_ids(options)
0
+ connection.select_all(
0
+ construct_finder_sql_for_ids(options), "#{name} Loading ids")
0
+ def construct_finder_sql_for_ids(options)
0
+ sql = "SELECT #{quoted_table_name}.id FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
0
+ add_conditions!(sql, options[:conditions], scope)
0
+ add_group!(sql, options[:group], scope)
0
+ add_order!(sql, options[:order], scope)
0
+ add_lock!(sql, options, scope)
0
+ return sanitize_sql(sql)
0
+ class CachedHasAndBelongsToManyAssociation < HasAndBelongsToManyAssociation
0
+ include AssociationHelper
0
+ options = args.extract_options!
0
+ # If using a custom finder_sql, scan the entire collection.
0
+ # The finder_sql condition is unchanged from the superclass definition
0
+ if @reflection.options[:finder_sql]
0
+ expects_array = args.first.kind_of?(Array)
0
+ ids = args.flatten.compact.uniq
0
+ record = load_target.detect { |r| id == r.id }
0
+ expects_array ? [record] : record
0
+ load_target.select { |r| ids.include?(r.id) }
0
+ conditions = "#{@finder_sql}"
0
+ if sanitized_conditions = sanitize_sql(options[:conditions])
0
+ conditions << " AND (#{sanitized_conditions})"
0
+ options[:conditions] = conditions
0
+ options[:joins] = @join_sql
0
+ options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
0
+ if options[:order] && @reflection.options[:order]
0
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
0
+ elsif @reflection.options[:order]
0
+ options[:order] = @reflection.options[:order]
0
+ merge_options_from_reflection!(options)
0
+ options[:select] = "#{@reflection.table_name}.id"
0
+ # # Pass through args exactly as we received them.
0
+ # @reflection.klass.find(*args)
0
+ # Pass through args exactly as we received them.
0
+ ids = @reflection.klass.find(*args).map(&:id)
0
+ CacheHelper.retrieve_records(ids, @reflection.klass)
0
class CachedHasManyAssociation < HasManyAssociation
0
+ include AssociationHelper
0
options = args.extract_options!
0
@@ -99,8 +269,8 @@ module ActiveRecord
0
# Pass through args exactly as we received them.
0
+ unless options[:join] || (scope(:find) && scope(:find)[:join])
0
# Doing it this way will cause complex joins to break,
0
# but its way faster then the else condition.
0
ids = select_ids(options).map { |row| row["id"].to_i }
0
@@ -110,45 +280,9 @@ module ActiveRecord
0
ids = @reflection.klass.find(*args).map(&:id)
0
- cache_keys = ids.map { |id| "#{@reflection.klass.name}::#{id}" }
0
- records = Cache.get_multiple(cache_keys)
0
- record_ids = records.map(&:id)
0
- missing_record_ids = ids.select { |id| !record_ids.include?(id) }
0
- missing_records = @reflection.klass.find(:all,
0
- :conditions => ['id in (?)', missing_record_ids])
0
- missing_records.each do |record|
0
- Cache.put(record.cache_key, record)
0
- records.concat(missing_records)
0
- # Slow sort method. Use a hash for more goodness
0
- ids.collect { |id| records.detect { |r| r.id == id } }
0
+ CacheHelper.retrieve_records(ids, @reflection.klass)
0
- def select_ids(options)
0
- connection.select_all(
0
- construct_finder_sql_for_ids(options), "#{name} Loading ids")
0
- def construct_finder_sql_for_ids(options)
0
- sql = "SELECT #{quoted_table_name}.id FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
0
- add_joins!(sql, options, scope)
0
- add_conditions!(sql, options[:conditions], scope)
0
- add_group!(sql, options[:group], scope)
0
- add_order!(sql, options[:order], scope)
0
- add_lock!(sql, options, scope)
0
- return sanitize_sql(sql)
Comments
No one has commented yet.