Module: IdentityCache::ConfigurationDSL::ClassMethods
- Defined in:
- lib/identity_cache/configuration_dsl.rb
Instance Method Summary collapse
- #add_parent_expiry_hook(options) ⇒ Object
-
#attribute_dynamic_fetcher(attribute, fields, values) ⇒ Object
:nodoc:.
-
#build_denormalized_association_cache(association, options) ⇒ Object
:nodoc:.
-
#build_normalized_has_many_cache(association, options) ⇒ Object
:nodoc:.
-
#cache_attribute(attribute, options = {}) ⇒ Object
Will cache a single attribute on its own blob, it will add a fetch_attribute_by_id (or the value of the by option).
-
#cache_has_many(association, options = {}) ⇒ Object
Will cache an association to the class including IdentityCache.
-
#cache_has_one(association, options = {}) ⇒ Object
Will cache an association to the class including IdentityCache.
-
#cache_index(*fields) ⇒ Object
Declares a new index in the cache for the class where IdentityCache was included.
- #disable_primary_cache_index ⇒ Object
-
#identity_cache_multiple_value_dynamic_fetcher(fields, values) ⇒ Object
:nodoc.
-
#identity_cache_single_value_dynamic_fetcher(fields, values) ⇒ Object
:nodoc:.
- #identity_cache_sql_conditions(fields, values) ⇒ Object
Instance Method Details
#add_parent_expiry_hook(options) ⇒ Object
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 |
# File 'lib/identity_cache/configuration_dsl.rb', line 275 def add_parent_expiry_hook() child_class = [:association_class] child_association = child_class.reflect_on_association([:inverse_name]) raise InverseAssociationError unless child_association foreign_key = child_association.association_foreign_key parent_class ||= self.name new_parent = [:inverse_name] child_class.send(:include, ArTransactionChanges) unless child_class.include?(ArTransactionChanges) child_class.send(:include, ParentModelExpiration) unless child_class.include?(ParentModelExpiration) child_class.class_eval(ruby = <<-CODE, __FILE__, __LINE__) after_commit :expire_parent_cache after_touch :expire_parent_cache def expire_parent_cache expire_parent_cache_on_changes(:#{[:inverse_name]}, '#{foreign_key}', #{parent_class}, #{[:only_on_foreign_key_change]}) end CODE end |
#attribute_dynamic_fetcher(attribute, fields, values) ⇒ Object
:nodoc:
268 269 270 271 272 273 |
# File 'lib/identity_cache/configuration_dsl.rb', line 268 def attribute_dynamic_fetcher(attribute, fields, values) #:nodoc: cache_key = rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, values) sql_on_miss = "SELECT `#{attribute}` FROM `#{table_name}` WHERE #{identity_cache_sql_conditions(fields, values)} LIMIT 1" IdentityCache.fetch(cache_key) { connection.select_value(sql_on_miss) } end |
#build_denormalized_association_cache(association, options) ⇒ Object
:nodoc:
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/identity_cache/configuration_dsl.rb', line 205 def build_denormalized_association_cache(association, ) #:nodoc: [:association_class] ||= reflect_on_association(association).klass [:cached_accessor_name] ||= "fetch_#{association}" [:records_variable_name] ||= "cached_#{association}" [:population_method_name] ||= "populate_#{association}_cache" unless instance_methods.include?([:cached_accessor_name].to_sym) self.class_eval(ruby = <<-CODE, __FILE__, __LINE__) def #{[:cached_accessor_name]} fetch_denormalized_cached_association('#{[:records_variable_name]}', :#{association}) end def #{[:population_method_name]} populate_denormalized_cached_association('#{[:records_variable_name]}', :#{association}) end CODE add_parent_expiry_hook(.merge(:only_on_foreign_key_change => false)) end end |
#build_normalized_has_many_cache(association, options) ⇒ Object
:nodoc:
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/identity_cache/configuration_dsl.rb', line 227 def build_normalized_has_many_cache(association, ) #:nodoc: singular_association = association.to_s.singularize [:association_class] ||= reflect_on_association(association).klass [:cached_accessor_name] ||= "fetch_#{association}" [:ids_name] ||= "#{singular_association}_ids" [:cached_ids_name] ||= "fetch_#{[:ids_name]}" [:ids_variable_name] ||= "cached_#{[:ids_name]}" [:records_variable_name] ||= "cached_#{association}" [:population_method_name] ||= "populate_#{association}_cache" [:prepopulate_method_name] ||= "prepopulate_fetched_#{association}" self.class_eval(ruby = <<-CODE, __FILE__, __LINE__) attr_reader :#{[:ids_variable_name]} def #{[:cached_ids_name]} #{[:population_method_name]} unless @#{[:ids_variable_name]} @#{[:ids_variable_name]} end def #{[:population_method_name]} @#{[:ids_variable_name]} = #{[:ids_name]} association_cache.delete(:#{association}) end def #{[:cached_accessor_name]} if IdentityCache.should_cache? || #{association}.loaded? #{[:population_method_name]} unless @#{[:ids_variable_name]} || @#{[:records_variable_name]} @#{[:records_variable_name]} ||= #{[:association_class]}.fetch_multi(*@#{[:ids_variable_name]}) else #{association} end end def #{[:prepopulate_method_name]}(records) @#{[:records_variable_name]} = records end CODE add_parent_expiry_hook(.merge(:only_on_foreign_key_change => true)) end |
#cache_attribute(attribute, options = {}) ⇒ Object
Will cache a single attribute on its own blob, it will add a fetch_attribute_by_id (or the value of the by option).
Example:
class Product
cache_attribute :quantity, :by => :name
cache_attribute :quantity :by => [:name, :vendor]
end
Parameters
attribute Symbol with the name of the attribute being cached
Options
-
by: Other attribute or attributes in the model to keep values indexed. Default is :id
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/identity_cache/configuration_dsl.rb', line 164 def cache_attribute(attribute, = {}) [:by] ||= :id fields = Array([:by]) self.cache_attributes.push [attribute, fields] field_list = fields.join("_and_") arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(',') self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__) def fetch_#{attribute}_by_#{field_list}(#{arg_list}) attribute_dynamic_fetcher(#{attribute.inspect}, #{fields.inspect}, [#{arg_list}]) end CODE end |
#cache_has_many(association, options = {}) ⇒ Object
Will cache an association to the class including IdentityCache. The embed option, if set, will make IdentityCache keep the association values in the same cache entry as the parent.
Embedded associations are more effective in offloading database work, however they will increase the size of the cache entries and make the whole entry expire when any of the embedded members change.
Example:
class Product
cached_has_many :options, :embed => false
cached_has_many :orders
cached_has_many :buyers, :inverse_name => 'line_item'
end
Parameters
association Name of the association being cached as a symbol
Options
-
embed: If set will cause IdentityCache to keep the values for this association in the same cache entry as the parent, instead of its own.
-
inverse_name: The name of the parent in the association if the name is not the lowercase pluralization of the parent object’s class
99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/identity_cache/configuration_dsl.rb', line 99 def cache_has_many(association, = {}) [:embed] ||= false [:inverse_name] ||= self.name.underscore.to_sym raise InverseAssociationError unless self.reflect_on_association(association) self.cached_has_manys[association] = if [:embed] build_denormalized_association_cache(association, ) else build_normalized_has_many_cache(association, ) end end |
#cache_has_one(association, options = {}) ⇒ Object
Will cache an association to the class including IdentityCache. The embed option if set will make IdentityCache keep the association values in the same cache entry as the parent.
Embedded associations are more effective in offloading database work, however they will increase the size of the cache entries and make the whole entry expire with the change of any of the embedded members
Example:
class Product
cached_has_one :store, :embed => false
cached_has_one :vendor
end
Parameters
association Symbol with the name of the association being cached
Options
-
embed: If set will cause IdentityCache to keep the values for this association in the same cache entry as the parent, instead of its own.
-
inverse_name: The name of the parent in the association ( only necessary if the name is not the lowercase pluralization of the parent object’s class)
136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/identity_cache/configuration_dsl.rb', line 136 def cache_has_one(association, = {}) [:embed] ||= true [:inverse_name] ||= self.name.underscore.to_sym raise InverseAssociationError unless self.reflect_on_association(association) self.cached_has_ones[association] = if [:embed] build_denormalized_association_cache(association, ) else raise NotImplementedError end end |
#cache_index(*fields) ⇒ Object
Declares a new index in the cache for the class where IdentityCache was included.
IdentityCache will add a fetch_by_field1_and_field2_and_…field for every index.
Example:
class Product
include IdentityCache
cache_index :name, :vendor
end
Will add Product.fetch_by_name_and_vendor
Parameters
fields Array of symbols or strings representing the fields in the index
Options
-
unique: if the index would only have unique values
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 |
# File 'lib/identity_cache/configuration_dsl.rb', line 46 def cache_index(*fields) raise NotImplementedError, "Cache indexes need an enabled primary index" unless primary_cache_index_enabled = fields. self.cache_indexes.push fields field_list = fields.join("_and_") arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(',') if [:unique] self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__) def fetch_by_#{field_list}(#{arg_list}) identity_cache_single_value_dynamic_fetcher(#{fields.inspect}, [#{arg_list}]) end # exception throwing variant def fetch_by_#{field_list}!(#{arg_list}) fetch_by_#{field_list}(#{arg_list}) or raise ActiveRecord::RecordNotFound end CODE else self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__) def fetch_by_#{field_list}(#{arg_list}) identity_cache_multiple_value_dynamic_fetcher(#{fields.inspect}, [#{arg_list}]) end CODE end end |
#disable_primary_cache_index ⇒ Object
180 181 182 183 |
# File 'lib/identity_cache/configuration_dsl.rb', line 180 def disable_primary_cache_index raise NotImplementedError, "Secondary indexes rely on the primary index to function. You must either remove the secondary indexes or don't disable the primary" if self.cache_indexes.size > 0 self.primary_cache_index_enabled = false end |
#identity_cache_multiple_value_dynamic_fetcher(fields, values) ⇒ Object
:nodoc
197 198 199 200 201 202 203 |
# File 'lib/identity_cache/configuration_dsl.rb', line 197 def identity_cache_multiple_value_dynamic_fetcher(fields, values) # :nodoc sql_on_miss = "SELECT `id` FROM `#{table_name}` WHERE #{identity_cache_sql_conditions(fields, values)}" cache_key = rails_cache_index_key_for_fields_and_values(fields, values) ids = IdentityCache.fetch(cache_key) { connection.select_values(sql_on_miss) } ids.empty? ? [] : fetch_multi(*ids) end |
#identity_cache_single_value_dynamic_fetcher(fields, values) ⇒ Object
:nodoc:
185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/identity_cache/configuration_dsl.rb', line 185 def identity_cache_single_value_dynamic_fetcher(fields, values) # :nodoc: sql_on_miss = "SELECT `id` FROM `#{table_name}` WHERE #{identity_cache_sql_conditions(fields, values)} LIMIT 1" cache_key = rails_cache_index_key_for_fields_and_values(fields, values) id = IdentityCache.fetch(cache_key) { connection.select_value(sql_on_miss) } unless id.nil? record = fetch_by_id(id.to_i) IdentityCache.cache.delete(cache_key) unless record end record end |
#identity_cache_sql_conditions(fields, values) ⇒ Object
296 297 298 |
# File 'lib/identity_cache/configuration_dsl.rb', line 296 def identity_cache_sql_conditions(fields, values) fields.each_with_index.collect { |f, i| "`#{f}` = #{quote_value(values[i])}" }.join(" AND ") end |