cacheable_delegator 1.0.0 → 1.1.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 +8 -8
- data/VERSION +1 -1
- data/cacheable_delegator.gemspec +2 -2
- data/lib/cacheable_delegator.rb +118 -26
- data/spec/cacheable_delegator_spec.rb +24 -3
- data/spec/mixin_active_record_inline_schema_spec.rb +62 -13
- data/spec/spec_records.rb +27 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NjBhNDAxOGI2NDljOWZiOTMwMjI2YmE1YTgyYjc0ZDdkNGMwMjE2MQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YmU2NGMwNmU2NzUwNDEzMzI5MGE4OWFlMjZhOTM4OTM5NTk5MGVjZQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NmU3ZjQ4NTM5MDNmYmFmZDNmOGYyZTdjOGZmYjViYTE5NzgyMjRkZDdmNmMy
|
10
|
+
ZDJlNDk4ODQxMjk0ZGZiNDgwYzUzYTc3MWJkNmNiZWFkZjcwYmIyN2M4MWUw
|
11
|
+
ZTUyYWQ2ZGE2Zjk3OGJkZTZjZGJhNjFiNGFjZWU2ZDgyZGVkNzk=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
OTgyYWUzMjJkMjY0MWY2Zjg3MjM3NDc1YzZlNGMwZjNlNTc4MWZkMzQyNTRk
|
14
|
+
ZGI3OWMxNjAxYmY5NGJjYzgwY2ZhOTA4NTY0YjFkYzhkM2Q2MzBhMGRjZGU0
|
15
|
+
NWExZThlNTM4MzY2NWFkMGIxNGRiZmRjYmUxMTMwNDE4MGNiZWE=
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.1.0
|
data/cacheable_delegator.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "cacheable_delegator"
|
8
|
-
s.version = "1.
|
8
|
+
s.version = "1.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Dan Nguyen"]
|
12
|
-
s.date = "2013-10-
|
12
|
+
s.date = "2013-10-27"
|
13
13
|
s.description = "Create a cache model for your active records"
|
14
14
|
s.email = "dansonguyen@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
data/lib/cacheable_delegator.rb
CHANGED
@@ -10,69 +10,136 @@ module CacheableDelegator
|
|
10
10
|
|
11
11
|
included do
|
12
12
|
class_attribute :source_class
|
13
|
+
class_attribute :_custom_columns
|
13
14
|
end
|
14
15
|
|
15
16
|
module ClassMethods
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
# this method defines the relation to an existing ActiveModel
|
19
|
+
# it does not change the schema unless invoked with the bang!
|
20
|
+
def cache_and_delegate(klass, opts={}, &blk)
|
21
|
+
raise ArgumentError, "Must pass in a class, not a #{klass}" unless klass.is_a?(Class)
|
22
|
+
self.source_class = klass
|
23
|
+
belongs_to :source_record, class_name: self.source_class.to_s
|
24
|
+
|
25
|
+
# This is why you have to define custom columns in the given block
|
26
|
+
# or after the call to cache and delegate
|
27
|
+
|
28
|
+
reset_custom_columns!
|
29
|
+
if block_given?
|
30
|
+
yield self
|
21
31
|
end
|
22
32
|
|
23
|
-
obj = self.new(att_hsh)
|
24
|
-
obj.source_record = source_obj
|
25
33
|
|
26
|
-
|
34
|
+
self.source_class
|
27
35
|
end
|
28
36
|
|
29
|
-
def create_cache(obj)
|
30
|
-
c = build_cache(obj)
|
31
|
-
c.save
|
32
37
|
|
33
|
-
|
38
|
+
# also runs upgrade_schema!
|
39
|
+
def cache_and_delegate!(*args)
|
40
|
+
cache_and_delegate(*args)
|
41
|
+
|
42
|
+
upgrade_schema!
|
34
43
|
end
|
35
44
|
|
36
|
-
def cache_and_delegate(klass, opts={}, &blk)
|
37
|
-
raise ArgumentError, "Must pass in a class, not a #{klass}" unless klass.is_a?(Class)
|
38
|
-
self.source_class = klass
|
39
|
-
belongs_to :source_record, class_name: self.source_class.to_s
|
40
45
|
|
41
|
-
|
46
|
+
# assumes @@source_class has been set
|
47
|
+
#
|
48
|
+
# will call ActiveRecord::Base.serialize based on parameters passed in
|
49
|
+
# from
|
50
|
+
def upgrade_schema!
|
51
|
+
# first we create a hash of source_columns
|
52
|
+
source_column_hash = source_columns.inject({}) do |shash, source_col|
|
53
|
+
source_col_atts = COL_ATTRIBUTES_TO_UPGRADE.inject({}){|hsh, att| hsh[att.to_sym] = source_col.send att; hsh }
|
54
|
+
shash[source_col.name] = source_col_atts
|
55
|
+
|
56
|
+
shash
|
57
|
+
end
|
58
|
+
|
59
|
+
# then we merge it with custom_columns
|
60
|
+
columns_to_add = source_column_hash.merge(custom_columns)
|
61
|
+
|
62
|
+
|
63
|
+
# then we add them all in using .col method from active_record_inline_schema
|
64
|
+
columns_to_add.each_pair do |col_name, col_atts|
|
65
|
+
is_serialize = col_atts.delete(:serialize)
|
66
|
+
col col_name, col_atts
|
67
|
+
|
68
|
+
# by default, the client passes in serialize: true for plain serialization
|
69
|
+
# and has the option to pass in serialize: Hash
|
70
|
+
if is_serialize
|
71
|
+
serialize_klass = (is_serialize == true) ? Object : is_serialize
|
72
|
+
serialize col_name, serialize_klass
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
self.auto_upgrade!( :gentle => true )
|
42
77
|
end
|
43
78
|
|
44
79
|
|
80
|
+
##################### source conveniences
|
81
|
+
|
82
|
+
|
45
83
|
def source_columns
|
46
84
|
source_class.columns.reject{|c| EXCLUDED_FIELDS.any?{|f| f.to_s == c.name }}
|
47
85
|
end
|
48
86
|
|
87
|
+
def source_reflections
|
88
|
+
source_class.reflections
|
89
|
+
end
|
90
|
+
|
49
91
|
# value columns are just columns that aren't in EXCLUDED_FIELDS
|
50
92
|
def value_column_names
|
51
93
|
self.column_names - EXCLUDED_FIELDS.map{|f| f.to_s}
|
52
94
|
end
|
95
|
+
|
96
|
+
###### column adding
|
97
|
+
|
98
|
+
# Note: This must occur either during the .cache_and_delegate call
|
99
|
+
# or after it.
|
100
|
+
# .cache_and_delegate *will* empty out custom_columns
|
101
|
+
# And implicitly, @@source_class has already been defined at this point
|
102
|
+
# By default, raise an error if the column_name does not correspond
|
103
|
+
# to an instance_method of @@source_class, unless the :bespoke option
|
104
|
+
# is true
|
105
|
+
#
|
106
|
+
# opts: Hash of standard ActiveRecord::ConnectionAdapters::Column options
|
107
|
+
# and several custom opts:
|
108
|
+
# :bespoke => if true, then create this column on CachedRecord even
|
109
|
+
# if its source_class does not have method_defined?(column_name)
|
110
|
+
# :serialize => this is passed along to upgrade_schema!
|
111
|
+
# which will then serialize the column via ActiveRecord.serialize
|
112
|
+
def add_custom_column(col_name, opts={})
|
113
|
+
col_str = col_name.to_s
|
114
|
+
is_bespoke = opts.delete :bespoke
|
115
|
+
if !self.source_class.method_defined?(col_str) && is_bespoke != true
|
116
|
+
raise ArgumentError, "Since :bespoke != true, #{self.source_class} was expected to respond_to? :#{col_str}"
|
117
|
+
end
|
53
118
|
|
119
|
+
custom_columns[col_name.to_s] = opts
|
120
|
+
end
|
54
121
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
source_col_atts = COL_ATTRIBUTES_TO_UPGRADE.inject({}){|hsh, att| hsh[att.to_sym] = source_col.send att; hsh }
|
59
|
-
|
60
|
-
col source_col.name, source_col_atts
|
61
|
-
end
|
122
|
+
def custom_columns
|
123
|
+
self._custom_columns ||= {}
|
124
|
+
end
|
62
125
|
|
63
|
-
|
126
|
+
def reset_custom_columns!
|
127
|
+
self._custom_columns = {}
|
64
128
|
end
|
129
|
+
|
65
130
|
end
|
66
131
|
|
67
132
|
|
68
|
-
DELEGATING_REGEX = /^delegate_(\w+)/
|
69
133
|
|
70
134
|
|
135
|
+
DELEGATING_REGEX = /^delegate_(\w+)/
|
136
|
+
|
71
137
|
def method_missing(foo, *args, &block)
|
72
138
|
if foomatch = foo.to_s.match(DELEGATING_REGEX)
|
73
139
|
foo = foomatch[1].to_s
|
74
|
-
|
75
140
|
self.source_record.send(foo, *args, &block)
|
141
|
+
elsif source_record.respond_to?(foo)
|
142
|
+
source_record.send(foo, *args, &block)
|
76
143
|
else
|
77
144
|
super
|
78
145
|
end
|
@@ -81,10 +148,35 @@ module CacheableDelegator
|
|
81
148
|
def respond_to?(meth, x=false)
|
82
149
|
if meth.match(DELEGATING_REGEX)
|
83
150
|
true
|
151
|
+
elsif source_record.respond_to?(meth)
|
152
|
+
true
|
84
153
|
else
|
85
154
|
super
|
86
155
|
end
|
87
156
|
end
|
88
157
|
|
158
|
+
|
159
|
+
################### Builder methods
|
160
|
+
module ClassMethods
|
161
|
+
def build_cache(source_obj)
|
162
|
+
att_hsh = self.value_column_names.inject({}) do |hsh, cname|
|
163
|
+
hsh[cname] = source_obj.send cname
|
164
|
+
hsh
|
165
|
+
end
|
166
|
+
|
167
|
+
obj = self.new(att_hsh)
|
168
|
+
obj.source_record = source_obj
|
169
|
+
|
170
|
+
obj
|
171
|
+
end
|
172
|
+
|
173
|
+
def create_cache(obj)
|
174
|
+
c = build_cache(obj)
|
175
|
+
c.save
|
176
|
+
|
177
|
+
c
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
89
181
|
end
|
90
182
|
|
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe CacheableDelegator do
|
4
|
+
before(:each) do
|
5
|
+
MyCachedRecord.upgrade_schema!
|
6
|
+
end
|
4
7
|
|
5
8
|
context 'class methods' do
|
6
|
-
|
7
9
|
describe '.cache_and_delegate' do
|
8
10
|
it 'should accept only a class to cache' do
|
9
11
|
expect{ MyCachedRecord.cache_and_delegate("X::Foo") }.to raise_error ArgumentError
|
@@ -17,7 +19,24 @@ describe CacheableDelegator do
|
|
17
19
|
end
|
18
20
|
|
19
21
|
|
20
|
-
context '
|
22
|
+
context 'delegated methods via method_missing' do
|
23
|
+
|
24
|
+
before(:each) do
|
25
|
+
@source = MyRecord.new(awesome_value: 500)
|
26
|
+
@source.my_covers << MyCover.new(subject: 'Funny', year: 2000) << MyCover.new(subject: 'Yes')
|
27
|
+
@source.save
|
28
|
+
|
29
|
+
@cache = MyCachedRecord.create_cache(@source)
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'ActiveRecord relations' do
|
33
|
+
it 'should have the same relations as its source' do
|
34
|
+
expect(@cache.my_covers.count).to eq 2
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'explicit delegations' do
|
21
40
|
context 'it should delegate all the things' do
|
22
41
|
before(:each) do
|
23
42
|
@my_record = MyRecord.create
|
@@ -35,7 +54,7 @@ describe CacheableDelegator do
|
|
35
54
|
expect(@cache_record.delegate_foo).to eq @my_record.foo
|
36
55
|
end
|
37
56
|
|
38
|
-
it 'will use its own :foo, without :delegate_ prefix' do
|
57
|
+
it 'will always use its own :foo, without :delegate_ prefix' do
|
39
58
|
@cache_record.foo = 'baz'
|
40
59
|
expect(@cache_record.foo).to eq 'baz'
|
41
60
|
end
|
@@ -44,4 +63,6 @@ describe CacheableDelegator do
|
|
44
63
|
|
45
64
|
|
46
65
|
|
66
|
+
|
67
|
+
|
47
68
|
end
|
@@ -32,8 +32,59 @@ describe CacheableDelegator do
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
context 'customization of columns with .add_custom_column' do
|
36
|
+
after(:each) do
|
37
|
+
reload_records!
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should allow addition of different columns' do
|
41
|
+
MyCachedRecord.cache_and_delegate(MyRecord) do |cache|
|
42
|
+
cache.add_custom_column :my_record_special_foo
|
43
|
+
end
|
44
|
+
MyCachedRecord.upgrade_schema!
|
45
|
+
|
46
|
+
expect(MyCachedRecord.column_names).to include('my_record_special_foo')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'by default, should raise error if source_class does not respond_to custom column name' do
|
50
|
+
expect{ MyCachedRecord.add_custom_column :not_foo_of_record }.to raise_error ArgumentError
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
context ':serialize option' do
|
55
|
+
before(:each) do
|
56
|
+
@source = MyRecord.create
|
57
|
+
@the_foo_array = @source.foo_array.dup
|
58
|
+
end
|
59
|
+
after(:each) do
|
60
|
+
reload_records!
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should accept :serialize => true' do
|
64
|
+
MyCachedRecord.add_custom_column :foo_array, serialize: true
|
65
|
+
MyCachedRecord.upgrade_schema!
|
66
|
+
cache = MyCachedRecord.create_cache(@source)
|
67
|
+
# remove @source to make sure things are cached
|
68
|
+
@source.delete
|
69
|
+
cache = MyCachedRecord.find(cache.id)
|
70
|
+
|
71
|
+
expect(cache.foo_array).to be_an Array
|
72
|
+
expect(cache.foo_array).to include(*@the_foo_array)
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
it 'respects .serialize second argument' do
|
77
|
+
MyCachedRecord.add_custom_column :foo_array, serialize: Hash
|
78
|
+
MyCachedRecord.upgrade_schema!
|
79
|
+
expect{ MyCachedRecord.create_cache(@source) }.to raise_error ActiveRecord::SerializationTypeMismatch
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should allow exclusion of specified columns'
|
85
|
+
|
86
|
+
|
87
|
+
end
|
37
88
|
end
|
38
89
|
|
39
90
|
|
@@ -47,8 +98,7 @@ describe CacheableDelegator do
|
|
47
98
|
|
48
99
|
|
49
100
|
describe '#build_cache' do
|
50
|
-
|
51
|
-
|
101
|
+
|
52
102
|
it 'should have same awesome_value as its source_object' do
|
53
103
|
expect(@cache.awesome_value).to eq 88
|
54
104
|
end
|
@@ -65,22 +115,21 @@ describe CacheableDelegator do
|
|
65
115
|
describe '#create_cache' do
|
66
116
|
it 'should be a saved record' do
|
67
117
|
savedcache = MyCachedRecord.create_cache(@source)
|
118
|
+
|
68
119
|
expect(savedcache.valid?).to be_true
|
69
120
|
expect(savedcache.new_record?).to be_false
|
70
121
|
end
|
71
|
-
|
72
|
-
|
73
|
-
context 'duplicating associated records' do
|
74
|
-
it 'should only duplicate one level down'
|
75
|
-
end
|
76
|
-
|
77
122
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
123
|
end
|
82
124
|
|
83
125
|
context 'maintanence' do
|
126
|
+
|
127
|
+
context 'duplicating associated records' do
|
128
|
+
it 'should only duplicate one level down' do
|
129
|
+
pending "dumping to yaml"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
84
133
|
end
|
85
134
|
|
86
135
|
end
|
data/spec/spec_records.rb
CHANGED
@@ -10,11 +10,7 @@ ActiveRecord::Base.establish_connection(
|
|
10
10
|
:adapter => "sqlite3",
|
11
11
|
:database => ":memory:"
|
12
12
|
)
|
13
|
-
ActiveRecord::Schema.define do
|
14
|
-
|
15
|
-
|
16
|
-
drop_table :my_records if ActiveRecord::Base.connection.table_exists?(:my_records)
|
17
|
-
|
13
|
+
ActiveRecord::Schema.define do
|
18
14
|
create_table :my_records, force: true do |t|
|
19
15
|
t.integer :awesome_value
|
20
16
|
t.string :name
|
@@ -29,22 +25,46 @@ ActiveRecord::Schema.define do
|
|
29
25
|
t.integer :source_record_id
|
30
26
|
t.string :source_record_type
|
31
27
|
end
|
28
|
+
|
29
|
+
|
30
|
+
create_table :my_covers, force: true do |t|
|
31
|
+
t.integer :my_record_id
|
32
|
+
t.string :subject
|
33
|
+
t.integer :year, default: 1999
|
34
|
+
end
|
32
35
|
end
|
33
36
|
|
34
37
|
|
35
38
|
class MyRecord < ActiveRecord::Base
|
39
|
+
has_many :my_covers
|
36
40
|
def foo
|
37
41
|
"This is foo"
|
38
42
|
end
|
39
43
|
|
40
44
|
def foo_double_awesome_value
|
41
|
-
awesome_value * 2
|
45
|
+
awesome_value.to_i * 2
|
46
|
+
end
|
47
|
+
|
48
|
+
def my_record_special_foo
|
49
|
+
'special foo'
|
42
50
|
end
|
51
|
+
|
52
|
+
|
53
|
+
def foo_array
|
54
|
+
[awesome_value, 'foo!', awesome_value]
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
class MyCover < ActiveRecord::Base
|
60
|
+
belongs_to :my_record
|
43
61
|
end
|
44
|
-
MyRecord.reset_column_information
|
45
62
|
|
46
63
|
|
47
64
|
class MyCachedRecord < ActiveRecord::Base
|
65
|
+
# for testing purposes
|
66
|
+
self.serialized_attributes = {}
|
67
|
+
|
48
68
|
include CacheableDelegator
|
49
69
|
cache_and_delegate MyRecord
|
50
70
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cacheable_delegator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Nguyen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-10-
|
11
|
+
date: 2013-10-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|