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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YTY0OTYzZmI1MjFmYmQ4YTMwZDk0MDNjMTFkNzZhYTAyNTMzMzJjYw==
4
+ NjBhNDAxOGI2NDljOWZiOTMwMjI2YmE1YTgyYjc0ZDdkNGMwMjE2MQ==
5
5
  data.tar.gz: !binary |-
6
- YzgxYjBmZjVjMzUxMjEzMDA4NjY4ZjZjYTc2Nzk3OTRiMDI2YWFkMQ==
6
+ YmU2NGMwNmU2NzUwNDEzMzI5MGE4OWFlMjZhOTM4OTM5NTk5MGVjZQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- NDQ4YTI3Mjg0NzE0ZDk3NDI3NGZjMjA5MzMwMmI3NjM3MDFkYjNiYmVhMTYz
10
- ZDA1Mzk2M2U2MDQ0OGJiNzcyMmIyNTk0OWM3ZDc3YTk2ZWRiMGE1MmVhNDI1
11
- NTVhNzU2YjI2OTYyMmI4YzVmM2MwMzQ1OTg2ZTA1OTRiNWM4NDY=
9
+ NmU3ZjQ4NTM5MDNmYmFmZDNmOGYyZTdjOGZmYjViYTE5NzgyMjRkZDdmNmMy
10
+ ZDJlNDk4ODQxMjk0ZGZiNDgwYzUzYTc3MWJkNmNiZWFkZjcwYmIyN2M4MWUw
11
+ ZTUyYWQ2ZGE2Zjk3OGJkZTZjZGJhNjFiNGFjZWU2ZDgyZGVkNzk=
12
12
  data.tar.gz: !binary |-
13
- NGRlZjEzODY2YWUxNGJmYzAwNzlkZmEyZDZkZmQzYzgyMzlkMGZhODc0OGEy
14
- YzUzNmI5NDFmMzJiNGZhYWEwYTU5ZTViMTM3YmIzNWJkOTIzMmI3YWY3Mjk5
15
- NGRlNjMwMjc0NzljYjZjM2IzYjliZjE4NTRjNWM2MjUzYTg4NWI=
13
+ OTgyYWUzMjJkMjY0MWY2Zjg3MjM3NDc1YzZlNGMwZjNlNTc4MWZkMzQyNTRk
14
+ ZGI3OWMxNjAxYmY5NGJjYzgwY2ZhOTA4NTY0YjFkYzhkM2Q2MzBhMGRjZGU0
15
+ NWExZThlNTM4MzY2NWFkMGIxNGRiZmRjYmUxMTMwNDE4MGNiZWE=
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "cacheable_delegator"
8
- s.version = "1.0.0"
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-09"
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 = [
@@ -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
- def build_cache(source_obj)
18
- att_hsh = self.value_column_names.inject({}) do |hsh, cname|
19
- hsh[cname] = source_obj.send cname
20
- hsh
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
- obj
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
- c
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
- self.source_class
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
- # assumes @@source_class has been set
56
- def upgrade_schema!
57
- source_columns.each do |source_col|
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
- self.auto_upgrade!( :gentle => true )
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 'delegations' do
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
- it 'should allow addition of different columns'
36
- it 'should allow exclusion of specified columns'
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.0.0
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-09 00:00:00.000000000 Z
11
+ date: 2013-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry