activerecord-cached_at 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 172ead451c54fddc715cc2caa791a8eb64ee2854
4
- data.tar.gz: aba2523bd6cd9e8934438397c908696b3ed127af
3
+ metadata.gz: d2078ad2e295f56f2163f9f813cc3bd87796c0bf
4
+ data.tar.gz: 90fb34c29372d3cb83ed043777ac3cb137c79961
5
5
  SHA512:
6
- metadata.gz: 0af8779024a2f87a249614f37498173e80311c5132e02db7e56c100f3d4fca326c560f451f3c428b7e3f0074e4d94184dbc95adf7eebf00edf1c293108298766
7
- data.tar.gz: 2e452b8fc450c01ffe41fafbcd2f81554fa983b89a8ebdb3d7a5a9ea2fcb4e1278634bd04e404ea2cc1d8a04dfd5478d3694f20f8eda0ea0709869df26f59172
6
+ metadata.gz: 6507b89f69f5172ee7d1e8c0f30b64e72764ad6b4a3f853765eeb139df0ae55612f8671acb11fed693593fd7b39d1a4bfb17c4b51c4da67640bd7e0f61ba6969
7
+ data.tar.gz: 35b31f8fcafc9a939328da0b163fdf964ceec28d1f627e80c2d0581de642515d19bb11f1300e4e84e59d8fbd51253faf8bda8cd44795c57ca8e96128ff085af8
data/README.md CHANGED
@@ -1,2 +1,28 @@
1
- # activerecord-cached_at
2
- Allows ActiveRecord and Rails to use a `cached_at` column for the `cache_key` if available
1
+ # ActiveRecord - CachedAt
2
+
3
+ This gem causes ActiveRecord to update a `cached_at` column if present, like the
4
+ `updated_at` column.
5
+
6
+ When calculating a `cache_key` for a model it will also consider the `cached_at`
7
+ column to determin the key of a model.
8
+
9
+ Any `ActiveRecord::Migration` that calls `timestamps` will include a `cached_at`
10
+ column.
11
+
12
+ TODO: Document about relationship caches
13
+
14
+ ## Installation
15
+
16
+ Add the following line to your `Gemfile`:
17
+
18
+ gem 'activerecord-cached_at', require: 'cached_at'
19
+
20
+ If you just need the `ActiveRecord::Base#cache_key` and associated helpers and
21
+ aren't updating the models you can just require the helpers:
22
+
23
+ gem 'activerecord-cached_at', require: 'cached_at/helpers'
24
+
25
+
26
+ ## TODO:
27
+
28
+ Add a `cache_key` method to the Model class that gets `MAX(cached_at)`
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters #:nodoc:
3
+
4
+ class TableDefinition
5
+ def timestamps(*args)
6
+ options = args.extract_options!
7
+
8
+ options[:null] = false if options[:null].nil?
9
+
10
+ column(:created_at, :datetime, options)
11
+ column(:updated_at, :datetime, options)
12
+ column(:cached_at, :datetime, options)
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters #:nodoc:
3
+
4
+ module SchemaStatements
5
+ def add_timestamps(table_name, options = {})
6
+ options[:null] = false if options[:null].nil?
7
+
8
+ add_column table_name, :created_at, :datetime, options
9
+ add_column table_name, :updated_at, :datetime, options
10
+ add_column table_name, :cached_at, :datetime, options
11
+ end
12
+
13
+ def remove_timestamps(table_name, options = {})
14
+ remove_column table_name, :updated_at
15
+ remove_column table_name, :created_at
16
+ remove_column table_name, :cached_at
17
+ end
18
+ end
19
+
20
+ end
21
+ end
data/lib/cached_at.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'active_record'
2
+
3
+ require File.expand_path(File.join(__FILE__, '../cached_at/base'))
4
+ require File.expand_path(File.join(__FILE__, '../cached_at/helpers'))
5
+ require File.expand_path(File.join(__FILE__, '../cached_at/timestamp'))
6
+ require File.expand_path(File.join(__FILE__, '../cached_at/connection_adapters/abstract/schema_definitions'))
7
+ require File.expand_path(File.join(__FILE__, '../cached_at/connection_adapters/abstract/schema_statements'))
@@ -0,0 +1,41 @@
1
+ module CachedAt
2
+ module Association
3
+
4
+ def traverse_relationships(klass, relationships, query, cache_column, timestamp)
5
+ if relationships.is_a?(Symbol)
6
+ reflection = klass.reflect_on_association(relationships)
7
+ case reflection
8
+ when ActiveRecord::Reflection::BelongsToReflection
9
+ cache_column = "#{reflection.inverse_of.name}_#{cache_column}"
10
+ reflection.klass.joins(reflection.inverse_of.name).merge(query).update_all({
11
+ cache_column => timestamp
12
+ })
13
+ when ActiveRecord::Reflection::HasManyReflection
14
+ cache_column = "#{reflection.inverse_of.name}_#{cache_column}"
15
+ reflection.klass.joins(reflection.inverse_of.name).merge(query).update_all({
16
+ cache_column => timestamp
17
+ })
18
+ when ActiveRecord::Reflection::HasAndBelongsToManyReflection
19
+ query = reflection.klass.joins(reflection.inverse_of.name).merge(query)
20
+ puts '!!!!!!!!!!!!!!!!!'
21
+ when ActiveRecord::Reflection::ThroughReflection
22
+ cache_column = "#{reflection.inverse_of.name}_#{cache_column}"
23
+ reflection.klass.joins(reflection.inverse_of.name).merge(query).update_all({
24
+ cache_column => timestamp
25
+ })
26
+ end
27
+ elsif relationships.is_a?(Hash)
28
+ relationships.each do |key, value|
29
+ traverse_relationships(klass, key, query, cache_column, timestamp)
30
+ end
31
+ elsif relationships.is_a?(Array)
32
+ relationships.each do |value|
33
+ traverse_relationships(klass, value, query, cache_column, timestamp)
34
+ end
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+
41
+ ActiveRecord::Associations::Association.include(CachedAt::Association)
@@ -0,0 +1,67 @@
1
+ module CachedAt
2
+ module BelongsToAssociation
3
+
4
+ def touch_cached_at(timestamp, method)
5
+ return unless options[:cached_at]
6
+
7
+ if !options[:inverse_of]
8
+ puts "WARNING: cannot updated cached at for relationship: #{owner.class.name}.#{name}, inverse_of not set"
9
+ return
10
+ end
11
+
12
+ types = {}
13
+
14
+ cache_column = "#{options[:inverse_of]}_cached_at"
15
+ if options[:polymorphic]
16
+ oldtype = owner.send("#{reflection.foreign_type}_was")
17
+ oldid = owner.send("#{reflection.foreign_key}_was")
18
+ newtype = owner.send(reflection.foreign_type)
19
+ newid = owner.send(reflection.foreign_key)
20
+ if !oldtype.nil? && oldtype == newtype
21
+ model_klass = oldtype.constantize
22
+ query = model_klass.where({ (options[:primary_key] || 'id') => [oldid, newid] })
23
+ query.update_all({ cache_column => timestamp })
24
+ traverse_relationships(model_klass, options[:cached_at], query, cache_column, timestamp)
25
+ else
26
+ if oldtype
27
+ model_klass = oldtype.constantize
28
+ query = model_klass.where({ (options[:primary_key] || 'id') => oldid })
29
+ query.update_all({ cache_column => timestamp })
30
+ traverse_relationships(model_klass, options[:cached_at], query, cache_column, timestamp)
31
+ end
32
+
33
+ if newtype
34
+ model_klass = newtype.constantize
35
+ query = model_klass.where({ (options[:primary_key] || 'id') => newid })
36
+ query.update_all({ cache_column => timestamp })
37
+ traverse_relationships(model_klass, options[:cached_at], query, cache_column, timestamp)
38
+ end
39
+ end
40
+ else
41
+ ids = [owner.send(reflection.foreign_key), owner.send("#{reflection.foreign_key}_was")].compact.uniq
42
+ query = klass.where({ reflection.association_primary_key => ids })
43
+ query.update_all({ cache_column => timestamp })
44
+ traverse_relationships(klass, options[:cached_at], query, cache_column, timestamp)
45
+ end
46
+
47
+ if loaded? && target
48
+ target.raw_write_attribute(cache_column, timestamp)
49
+ end
50
+ end
51
+
52
+ def replace(record)
53
+ if options[:cached_at] && options[:inverse_of]
54
+ timestamp = Time.now
55
+ cache_column = "#{options[:inverse_of]}_cached_at"
56
+ if loaded? && target
57
+ target.raw_write_attribute(cache_column, timestamp)
58
+ end
59
+ end
60
+
61
+ super
62
+ end
63
+
64
+ end
65
+ end
66
+
67
+ ActiveRecord::Associations::BelongsToAssociation.prepend(CachedAt::BelongsToAssociation)
@@ -0,0 +1,89 @@
1
+ module CachedAt
2
+ module CollectionAssociation
3
+
4
+ def touch_cached_at(timestamp, method)
5
+ return unless options[:cached_at]
6
+
7
+ if reflection.inverse_of.nil?
8
+ puts "WARNING: cannot updated cached at for relationship: #{owner.class.name}.#{name}, inverse_of not set"
9
+ return
10
+ end
11
+
12
+ cache_column = "#{reflection.inverse_of.name}_cached_at"
13
+ ids = [owner.send(reflection.association_primary_key), owner.send("#{reflection.association_primary_key}_was")].compact.uniq
14
+ query = klass.where({ reflection.foreign_key => ids })
15
+
16
+ if loaded?
17
+ target.each { |r| r.raw_write_attribute(cache_column, timestamp) }
18
+ end
19
+
20
+ if method != :destroy
21
+ query.update_all({ cache_column => timestamp })
22
+ traverse_relationships(klass, options[:cached_at], query, cache_column, timestamp)
23
+ else
24
+ if options[:dependent].nil?
25
+ query.update_all({ cache_column => timestamp })
26
+ traverse_relationships(klass, options[:cached_at], query, cache_column, timestamp)
27
+ else
28
+ traverse_relationships(klass, options[:cached_at], query, cache_column, timestamp)
29
+ end
30
+ end
31
+ end
32
+
33
+ def touch_records_added_cached_at(records, timestamp)
34
+ return if owner.new_record? || records.empty?
35
+
36
+ if reflection.options[:cached_at]
37
+ if reflection.inverse_of.nil?
38
+ puts "WARNING: cannot updated cached at for relationship: #{klass.name}.#{name}, inverse_of not set"
39
+ return
40
+ end
41
+
42
+ cache_column = "#{reflection.inverse_of.name}_cached_at"
43
+ if loaded?
44
+ target.each { |r| r.raw_write_attribute(cache_column, timestamp) }
45
+ end
46
+
47
+ ids = records.inject([]) { |a, o| a += [o.send(klass.primary_key), o.send("#{klass.primary_key}_was")] }.compact.uniq
48
+ query = klass.where(klass.primary_key => ids)
49
+ query.update_all({ cache_column => timestamp })
50
+ traverse_relationships(klass, reflection.options[:cached_at], query, cache_column, timestamp)
51
+ end
52
+ end
53
+
54
+ def touch_records_removed_cached_at(records, timestamp)
55
+ return if owner.new_record? || records.empty?
56
+
57
+ return unless options[:cached_at]
58
+
59
+ if reflection.inverse_of.nil?
60
+ puts "WARNING: cannot updated cached at for relationship: #{klass.name}.#{name}, inverse_of not set"
61
+ return
62
+ end
63
+
64
+ cache_column = "#{reflection.inverse_of.name}_cached_at"
65
+ ids = records.inject([]) { |a, o| a += [o.send(klass.primary_key), o.send("#{klass.primary_key}_was")] }.compact.uniq
66
+ query = klass.where(klass.primary_key => ids)
67
+ traverse_relationships(klass, reflection.options[:cached_at], query, cache_column, timestamp)
68
+ end
69
+
70
+ def concat_records(records, should_raise = false)
71
+ value = super
72
+ touch_records_added_cached_at(records, Time.now)
73
+ value
74
+ end
75
+
76
+ def remove_records(existing_records, records, method)
77
+ touch_records_removed_cached_at(existing_records, Time.now)
78
+ super
79
+ end
80
+
81
+ def delete_all(dependent = nil)
82
+ touch_cached_at(Time.now, :destroy)
83
+ super
84
+ end
85
+
86
+ end
87
+ end
88
+
89
+ ActiveRecord::Associations::CollectionAssociation.prepend(CachedAt::CollectionAssociation)
@@ -0,0 +1,103 @@
1
+ module CachedAt
2
+ module HasManyThroughAssociation
3
+
4
+ def touch_cached_at(timestamp, method)
5
+ using_reflection = reflection.parent_reflection || reflection
6
+ return unless using_reflection.options[:cached_at]
7
+
8
+ if using_reflection.inverse_of.nil?
9
+ puts "WARNING: cannot updated cached at for relationship: #{owner.class.name}.#{using_reflection.name}, inverse_of not set"
10
+ return
11
+ end
12
+
13
+ cache_column = "#{using_reflection.inverse_of.name}_cached_at"
14
+ ids = [owner.send(using_reflection.association_primary_key), owner.send("#{using_reflection.association_primary_key}_was")].compact.uniq
15
+
16
+
17
+ arel_table = klass._reflections[using_reflection.inverse_of.options[:through].to_s].klass.arel_table
18
+ query = klass.joins(using_reflection.inverse_of.options[:through])
19
+ query = if using_reflection.is_a?(ActiveRecord::Reflection::HasAndBelongsToManyReflection)
20
+ query.where(arel_table[using_reflection.foreign_key].in(ids))
21
+ else
22
+ query.where(arel_table[using_reflection.inverse_of.foreign_key].in(ids))
23
+ end
24
+
25
+ if loaded?
26
+ target.each { |r| r.raw_write_attribute(cache_column, timestamp) }
27
+ end
28
+
29
+ query.update_all({ cache_column => timestamp })
30
+ traverse_relationships(klass, options[:cached_at], query, cache_column, timestamp)
31
+ end
32
+
33
+ def touch_records_added_cached_at(records, timestamp)
34
+ return if records.empty?
35
+
36
+ using_reflection = reflection.parent_reflection || reflection
37
+
38
+ if using_reflection.options[:cached_at]
39
+
40
+ if using_reflection.inverse_of.nil?
41
+ puts "WARNING: cannot updated cached at for relationship: #{klass.name}.#{name}, inverse_of not set"
42
+ return
43
+ end
44
+
45
+ cache_column = "#{using_reflection.inverse_of.name}_cached_at"
46
+ ids = records.inject([]) { |a, o| a += [o.send(klass.primary_key), o.send("#{klass.primary_key}_was")] }.compact.uniq
47
+ query = klass.where(klass.primary_key => ids)
48
+ query.update_all({ cache_column => timestamp })
49
+ traverse_relationships(klass, using_reflection.options[:cached_at], query, cache_column, timestamp)
50
+ end
51
+
52
+ if using_reflection.inverse_of&.options.try(:[], :cached_at) || using_reflection.inverse_of&.parent_reflection&.options.try(:[], :cached_at)
53
+ cache_column = "#{using_reflection.name}_cached_at"
54
+ owner.update_column(cache_column, timestamp) unless owner.new_record?
55
+ end
56
+ end
57
+
58
+ def touch_records_removed_cached_at(records, timestamp)
59
+ return if records.empty?
60
+
61
+ using_reflection = reflection.parent_reflection || reflection
62
+ inverse_reflection = using_reflection.inverse_of&.parent_reflection || using_reflection.inverse_of
63
+
64
+ if using_reflection.options[:cached_at]
65
+ if inverse_reflection.nil?
66
+ puts "WARNING: cannot updated cached at for relationship: #{klass.name}.#{name}, inverse_of not set"
67
+ return
68
+ end
69
+
70
+ cache_column = "#{inverse_reflection.name}_cached_at"
71
+ ids = records.inject([]) { |a, o| a += [o.send(klass.primary_key), o.send("#{klass.primary_key}_was")] }.compact.uniq
72
+ query = klass.where(klass.primary_key => ids)
73
+ query.update_all({ cache_column => timestamp })
74
+ traverse_relationships(klass, reflection.options[:cached_at], query, cache_column, timestamp)
75
+ end
76
+
77
+ if !inverse_reflection.nil? && inverse_reflection.options[:cached_at]
78
+ cache_column = "#{using_reflection.name}_cached_at"
79
+ owner.raw_write_attribute(cache_column, timestamp)
80
+ ids = records.inject([]) { |a, o| a += [o.send(klass.primary_key), o.send("#{klass.primary_key}_was")] }.compact.uniq
81
+
82
+ arel_table = if inverse_reflection.is_a?(ActiveRecord::Reflection::HasAndBelongsToManyReflection)
83
+ inverse_reflection.klass.const_get(:"HABTM_#{using_reflection.name.to_s.camelize}").arel_table
84
+ else
85
+ using_reflection.inverse_of.klass._reflections[using_reflection.inverse_of.options[:through].to_s].klass.arel_table
86
+ end
87
+ query = if inverse_reflection.is_a?(ActiveRecord::Reflection::HasAndBelongsToManyReflection)
88
+ using_reflection.inverse_of.klass.joins(inverse_reflection.join_table)
89
+ else
90
+ using_reflection.inverse_of.klass.joins(using_reflection.inverse_of.options[:through])
91
+ end
92
+
93
+ query.where(arel_table[using_reflection.inverse_of.foreign_key].in(ids))
94
+ query.update_all({ cache_column => timestamp })
95
+ traverse_relationships(klass, reflection.options[:cached_at], query, cache_column, timestamp)
96
+ end
97
+ end
98
+
99
+
100
+ end
101
+ end
102
+
103
+ ActiveRecord::Associations::HasManyThroughAssociation.prepend(CachedAt::HasManyThroughAssociation)
@@ -0,0 +1,52 @@
1
+ module CachedAt
2
+ module HasOneAssociation
3
+
4
+ def touch_cached_at(timestamp, method)
5
+ return unless options[:cached_at]
6
+
7
+ if reflection.inverse_of.nil?
8
+ puts "WARNING: cannot updated cached at for relationship: #{owner.class.name}.#{name}, inverse_of not set"
9
+ return
10
+ end
11
+
12
+ cache_column = "#{reflection.inverse_of.name}_cached_at"
13
+ ids = [owner.send(reflection.association_primary_key), owner.send("#{reflection.association_primary_key}_was")].compact.uniq
14
+ query = klass.where({ reflection.foreign_key => ids })
15
+
16
+ case options[:dependent]
17
+ when nil
18
+ query.update_all({ cache_column => timestamp })
19
+ traverse_relationships(klass, options[:cached_at], query, cache_column, timestamp)
20
+ when :destroy
21
+ # don't need to worry about :destroy, that will touch the other caches
22
+ when :delete, :nullify
23
+ traverse_relationships(klass, options[:cached_at], query, cache_column, timestamp)
24
+ end
25
+
26
+ if loaded? && target
27
+ target.raw_write_attribute(cache_column, timestamp)
28
+ end
29
+ end
30
+
31
+ def delete(method = options[:dependent])
32
+ if load_target
33
+ case method
34
+ when :delete
35
+ target.delete
36
+ when :destroy
37
+ target.destroy
38
+ when :nullify
39
+ updates = {reflection.foreign_key => nil}
40
+ if reflection.options[:cached_at]
41
+ cache_column = "#{reflection.inverse_of.name}_cached_at"
42
+ updates[cache_column] = Time.now
43
+ end
44
+ target.update_columns(updates) if target.persisted?
45
+ end
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+
52
+ ActiveRecord::Associations::HasOneAssociation.prepend(CachedAt::HasOneAssociation)
@@ -0,0 +1,69 @@
1
+ require File.expand_path(File.join(__FILE__, '../associations/association'))
2
+ require File.expand_path(File.join(__FILE__, '../associations/has_one_association'))
3
+ require File.expand_path(File.join(__FILE__, '../associations/belongs_to_association'))
4
+ require File.expand_path(File.join(__FILE__, '../associations/collection_association'))
5
+ require File.expand_path(File.join(__FILE__, '../associations/has_many_through_association'))
6
+
7
+ module CachedAt
8
+ module Base
9
+ extend ActiveSupport::Concern
10
+
11
+ module AssociationExtension
12
+
13
+ def self.build(model, reflection)
14
+ return unless reflection.options[:cached_at]
15
+ end
16
+
17
+ def self.valid_options
18
+ [:cached_at]
19
+ end
20
+
21
+ end
22
+
23
+ included do
24
+ before_save :update_belongs_to_cached_at_keys
25
+ before_destroy { update_relations_cached_at(method: :destroy) }
26
+ after_touch { update_relations_cached_at_from_cached_at(method: :touch) }
27
+
28
+ after_save :update_relations_cached_at_from_cached_at
29
+ end
30
+
31
+ private
32
+
33
+ def update_relations_cached_at_from_cached_at(method: nil)
34
+ update_relations_cached_at({
35
+ timestamp: (self.class.column_names.include?('cached_at') ? cached_at : nil),
36
+ method: method
37
+ })
38
+ end
39
+
40
+ def update_relations_cached_at(timestamp: nil, method: nil)
41
+ return if (method == nil && changes.empty?) && method != :destroy && method != :touch
42
+ timestamp ||= current_time_from_proper_timezone
43
+
44
+ self._reflections.each do |name, reflection|
45
+ association(name.to_sym).touch_cached_at(timestamp, method)
46
+ end
47
+ end
48
+
49
+ def update_belongs_to_cached_at_keys
50
+ self.class.reflect_on_all_associations.each do |reflection|
51
+ next unless reflection.is_a?(ActiveRecord::Reflection::BelongsToReflection)
52
+ cache_column = "#{reflection.name}_cached_at"
53
+
54
+ if self.attribute_names.include?(cache_column)
55
+ if self.changes.has_key?(reflection.foreign_key) && self.changes[reflection.foreign_key][1].nil?
56
+ self.assign_attributes({ cache_column => current_time_from_proper_timezone })
57
+ elsif (self.changes[reflection.foreign_key] || self.new_record? || (self.association(reflection.name).loaded? && self.send(reflection.name) && self.send(reflection.name).id.nil?)) && self.send(reflection.name).try(:cached_at)
58
+ self.assign_attributes({ cache_column => self.send(reflection.name).cached_at })
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+
68
+ ActiveRecord::Associations::Builder::Association.extensions << CachedAt::Base::AssociationExtension
69
+ ActiveRecord::Base.include(CachedAt::Base)
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters #:nodoc:
3
+
4
+ class TableDefinition
5
+ def timestamps(*args)
6
+ options = args.extract_options!
7
+
8
+ options[:null] = false if options[:null].nil?
9
+
10
+ column(:created_at, :datetime, options)
11
+ column(:updated_at, :datetime, options)
12
+ column(:cached_at, :datetime, options)
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -1,36 +1,10 @@
1
- module ActiveRecord
2
- module Timestamp
3
- private
4
- def timestamp_attributes_for_update
5
- [:updated_at, :updated_on, :cached_at]
6
- end
7
-
8
- def timestamp_attributes_for_create
9
- [:created_at, :created_on, :cached_at]
10
- end
11
- end
12
- end
13
-
14
-
15
1
  module ActiveRecord
16
2
  module ConnectionAdapters #:nodoc:
17
-
18
- class TableDefinition
19
- def timestamps(*args)
20
- options = args.extract_options!
21
3
 
22
- options[:null] = false if options[:null].nil?
23
-
24
- column(:created_at, :datetime, options)
25
- column(:updated_at, :datetime, options)
26
- column(:cached_at, :datetime, options)
27
- end
28
- end
29
-
30
4
  module SchemaStatements
31
5
  def add_timestamps(table_name, options = {})
32
6
  options[:null] = false if options[:null].nil?
33
-
7
+
34
8
  add_column table_name, :created_at, :datetime, options
35
9
  add_column table_name, :updated_at, :datetime, options
36
10
  add_column table_name, :cached_at, :datetime, options
@@ -42,6 +16,6 @@ module ActiveRecord
42
16
  remove_column table_name, :cached_at
43
17
  end
44
18
  end
45
-
19
+
46
20
  end
47
21
  end
@@ -0,0 +1,87 @@
1
+ module CachedAt
2
+ module Base
3
+ module Helpers
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+
8
+ def can_cache?(includes)
9
+ cache_columns = ['cached_at'] + cached_at_columns_for_includes(includes)
10
+
11
+ (cache_columns - column_names).empty?
12
+ end
13
+
14
+ def cached_at_columns_for_includes(includes, prefix=nil)
15
+ if includes.is_a?(Array)
16
+ includes.inject([]) { |s, k| s + cached_at_columns_for_includes(k, prefix) }
17
+ elsif includes.is_a?(Hash)
18
+ includes.map { |k, v|
19
+ value = ["#{prefix}#{k}_cached_at"]
20
+ if v != true
21
+ value << cached_at_columns_for_includes(v, "#{prefix}#{k}_")
22
+ end
23
+ value
24
+ }.flatten
25
+ else
26
+ ["#{prefix}#{includes}_cached_at"]
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ def cache_key(includes = nil)
33
+ if includes.nil? || includes.empty?
34
+ super
35
+ else
36
+ timestamp_keys = ['cached_at'] + self.class.cached_at_columns_for_includes(includes)
37
+ timestamp = max_updated_column_timestamp(timestamp_keys).utc.to_s(cache_timestamp_format)
38
+ digest ||= Digest::MD5.new()
39
+ digest << paramaterize_cache_includes(includes)
40
+ "#{model_name.cache_key}/#{id}+#{digest.hexdigest}@#{timestamp}"
41
+ end
42
+ end
43
+
44
+ # TODO
45
+ # def association_cache_key(association_name, includes = nil)
46
+ # end
47
+
48
+ private
49
+
50
+ def paramaterize_cache_includes(includes, paramaterized_cache_key = nil)
51
+ paramaterized_cache_key ||= ""
52
+
53
+ if includes.is_a?(Hash)
54
+ includes.keys.sort.each_with_index do |key, i|
55
+ paramaterized_cache_key << ',' unless i == 0
56
+ paramaterized_cache_key << key.to_s
57
+ if includes[key].is_a?(Hash) || includes[key].is_a?(Array)
58
+ paramaterized_cache_key << "["
59
+ paramaterize_cache_includes(includes[key], paramaterized_cache_key)
60
+ paramaterized_cache_key << "]"
61
+ elsif includes[key] != true
62
+ paramaterized_cache_key << "["
63
+ paramaterized_cache_key << includes[key].to_s
64
+ paramaterized_cache_key << "]"
65
+ end
66
+ end
67
+ elsif includes.is_a?(Array)
68
+ includes.sort.each_with_index do |value, i|
69
+ paramaterized_cache_key << ',' unless i == 0
70
+ if value.is_a?(Hash) || value.is_a?(Array)
71
+ paramaterize_cache_includes(value, paramaterized_cache_key)
72
+ else
73
+ paramaterized_cache_key << value.to_s
74
+ end
75
+ end
76
+ else
77
+ paramaterized_cache_key << includes.to_s
78
+ end
79
+
80
+ paramaterized_cache_key
81
+ end
82
+
83
+ end
84
+ end
85
+ end
86
+
87
+ ActiveRecord::Base.include(CachedAt::Base::Helpers)
@@ -0,0 +1,15 @@
1
+ module ActiveRecord
2
+ module Timestamp
3
+
4
+ private
5
+
6
+ def timestamp_attributes_for_update
7
+ [:updated_at, :cached_at]
8
+ end
9
+
10
+ def timestamp_attributes_for_create
11
+ [:created_at, :udpated_at, :cached_at] + self.class.column_names.select{|c| c.end_with?('_cached_at') }.map(&:to_sym)
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module CachedAt
2
+ VERSION = '2.0.0'
3
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-cached_at
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Bracy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-12 00:00:00.000000000 Z
11
+ date: 2017-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '4.0'
19
+ version: 5.0.0.beta1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '4.0'
26
+ version: 5.0.0.beta1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: byebug
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  description: Allows ActiveRecord and Rails to use a `cached_at` column for the `cache_key`
112
126
  if available
113
127
  email:
@@ -118,9 +132,20 @@ extra_rdoc_files:
118
132
  - README.md
119
133
  files:
120
134
  - README.md
121
- - ext/active_record/timestamp.rb
122
- - lib/active_record/cached_at.rb
123
- - lib/active_record/cached_at/version.rb
135
+ - ext/active_record/connection_adapters/abstract/schema_definitions.rb
136
+ - ext/active_record/connection_adapters/abstract/schema_statements.rb
137
+ - lib/cached_at.rb
138
+ - lib/cached_at/associations/association.rb
139
+ - lib/cached_at/associations/belongs_to_association.rb
140
+ - lib/cached_at/associations/collection_association.rb
141
+ - lib/cached_at/associations/has_many_through_association.rb
142
+ - lib/cached_at/associations/has_one_association.rb
143
+ - lib/cached_at/base.rb
144
+ - lib/cached_at/connection_adapters/abstract/schema_definitions.rb
145
+ - lib/cached_at/connection_adapters/abstract/schema_statements.rb
146
+ - lib/cached_at/helpers.rb
147
+ - lib/cached_at/timestamp.rb
148
+ - lib/cached_at/version.rb
124
149
  homepage: https://github.com/malomalo/activerecord-cached_at
125
150
  licenses:
126
151
  - MIT
@@ -143,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
168
  version: '0'
144
169
  requirements: []
145
170
  rubyforge_project:
146
- rubygems_version: 2.4.5
171
+ rubygems_version: 2.5.2
147
172
  signing_key:
148
173
  specification_version: 4
149
174
  summary: Allows ActiveRecord and Rails to use a `cached_at` column for the `cache_key`
@@ -1,3 +0,0 @@
1
- require 'active_record'
2
-
3
- require File.expand_path(File.join(__FILE__, '../../../ext/active_record/timestamp'))
@@ -1,5 +0,0 @@
1
- module ActiveRecord
2
- module CachedAt
3
- VERSION = '1.0.0'
4
- end
5
- end