activerecord-cached_at 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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