cacheable_delegator 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|