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 +4 -4
- data/README.md +28 -2
- data/ext/active_record/connection_adapters/abstract/schema_definitions.rb +17 -0
- data/ext/active_record/connection_adapters/abstract/schema_statements.rb +21 -0
- data/lib/cached_at.rb +7 -0
- data/lib/cached_at/associations/association.rb +41 -0
- data/lib/cached_at/associations/belongs_to_association.rb +67 -0
- data/lib/cached_at/associations/collection_association.rb +89 -0
- data/lib/cached_at/associations/has_many_through_association.rb +103 -0
- data/lib/cached_at/associations/has_one_association.rb +52 -0
- data/lib/cached_at/base.rb +69 -0
- data/lib/cached_at/connection_adapters/abstract/schema_definitions.rb +17 -0
- data/{ext/active_record/timestamp.rb → lib/cached_at/connection_adapters/abstract/schema_statements.rb} +2 -28
- data/lib/cached_at/helpers.rb +87 -0
- data/lib/cached_at/timestamp.rb +15 -0
- data/lib/cached_at/version.rb +3 -0
- metadata +33 -8
- data/lib/active_record/cached_at.rb +0 -3
- data/lib/active_record/cached_at/version.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2078ad2e295f56f2163f9f813cc3bd87796c0bf
|
4
|
+
data.tar.gz: 90fb34c29372d3cb83ed043777ac3cb137c79961
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6507b89f69f5172ee7d1e8c0f30b64e72764ad6b4a3f853765eeb139df0ae55612f8671acb11fed693593fd7b39d1a4bfb17c4b51c4da67640bd7e0f61ba6969
|
7
|
+
data.tar.gz: 35b31f8fcafc9a939328da0b163fdf964ceec28d1f627e80c2d0581de642515d19bb11f1300e4e84e59d8fbd51253faf8bda8cd44795c57ca8e96128ff085af8
|
data/README.md
CHANGED
@@ -1,2 +1,28 @@
|
|
1
|
-
#
|
2
|
-
|
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
|
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:
|
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:
|
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:
|
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:
|
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/
|
122
|
-
-
|
123
|
-
- lib/
|
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.
|
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`
|