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 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