flattery 0.0.4 → 0.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.
- data/README.md +20 -4
- data/lib/flattery/settings.rb +2 -0
- data/lib/flattery/value_provider/processor.rb +18 -5
- data/lib/flattery/value_provider/settings.rb +4 -2
- data/lib/flattery/version.rb +1 -1
- data/spec/unit/settings_spec.rb +21 -0
- data/spec/unit/value_provider/processor_spec.rb +32 -1
- data/spec/unit/value_provider/settings_spec.rb +8 -8
- metadata +2 -2
data/README.md
CHANGED
@@ -98,6 +98,19 @@ The default mechanism for performing the update of cached values is with the sta
|
|
98
98
|
|
99
99
|
This should be fine for modest applications, but if the update will affect many records - especially if there is a high likelihood of read/write contention - then it may need finessing. Flattery allows you to define your own update procedure for these cases - see the next section.
|
100
100
|
|
101
|
+
### How to updating cached values with batched transactional update
|
102
|
+
|
103
|
+
Use the <tt>:batch_size</tt> option to define the batch size:
|
104
|
+
|
105
|
+
class Category < ActiveRecord::Base
|
106
|
+
has_many :notes
|
107
|
+
|
108
|
+
include Flattery::ValueProvider
|
109
|
+
push_flattened_values_for :name => :notes, :as => 'cat_name', :batch_size => 100
|
110
|
+
end
|
111
|
+
|
112
|
+
This will update of cached values in batches of the specified size - each wrapped in its own transaction.
|
113
|
+
|
101
114
|
### How to provide a custom method for updating cached values
|
102
115
|
|
103
116
|
Use the <tt>:method</tt> option to declare the instance method to be used:
|
@@ -108,16 +121,19 @@ Use the <tt>:method</tt> option to declare the instance method to be used:
|
|
108
121
|
include Flattery::ValueProvider
|
109
122
|
push_flattened_values_for :name => :notes, :as => 'cat_name', :method => :my_custom_updater
|
110
123
|
|
111
|
-
#
|
124
|
+
# Your custom update method definition. Parameters:
|
112
125
|
# * +attribute+ is the attribute name that the value is coming from e.g. :name
|
113
126
|
# * +new_value+ is the new value that has been set e.g. 'a value that was just set'
|
114
127
|
# * +association_name+ is the association that updates need to be pushed to set e.g. :notes
|
115
|
-
# * +target_attribute+ is the attribute name that needs to be updated
|
116
|
-
|
128
|
+
# * +target_attribute+ is the attribute name that needs to be updated e.g. :cat_name
|
129
|
+
# * +batch_size+ is desired batch size to use for updates. 0 or nil implies no batch limits. e.g. 10
|
130
|
+
#
|
131
|
+
def my_custom_updater(attribute,new_value,association_name,target_attribute,batch_size)
|
117
132
|
# implement your custom update algorithm here. It could do some funky batched SQL for example.
|
118
|
-
# For now here's just a simple update_all implementation:
|
133
|
+
# For now, here's just a simple update_all implementation:
|
119
134
|
self.send(association_name).update_all(target_attribute => new_value)
|
120
135
|
end
|
136
|
+
|
121
137
|
end
|
122
138
|
|
123
139
|
### How can I get cached value updates pushed in the background?
|
data/lib/flattery/settings.rb
CHANGED
@@ -28,6 +28,7 @@ class Flattery::Settings
|
|
28
28
|
as_setting = opt.delete(:as).try(:to_s)
|
29
29
|
method_setting = opt.delete(:method).try(:to_sym)
|
30
30
|
background_setting = opt.delete(:background_with).try(:to_sym)
|
31
|
+
batch_size_setting = opt.delete(:batch_size).try(:to_i)
|
31
32
|
|
32
33
|
if from_entity = opt.keys.first
|
33
34
|
cache_options[:from_entity] = from_entity
|
@@ -36,6 +37,7 @@ class Flattery::Settings
|
|
36
37
|
cache_options[:as] = as_setting
|
37
38
|
cache_options[:method] = method_setting if method_setting
|
38
39
|
cache_options[:background_with] = background_setting if background_setting
|
40
|
+
cache_options[:batch_size] = batch_size_setting if batch_size_setting
|
39
41
|
|
40
42
|
cache_options
|
41
43
|
end
|
@@ -9,10 +9,11 @@ class Flattery::ValueProvider::Processor
|
|
9
9
|
attribute = key.to_sym
|
10
10
|
new_value = record.send(key)
|
11
11
|
association_name = options[:to_entity]
|
12
|
+
batch_size = options[:batch_size]
|
12
13
|
if options[:background_with] == :delayed_job && self.respond_to?(:delay)
|
13
|
-
self.delay.apply_push(record,method,attribute,new_value,association_name,target_attribute)
|
14
|
+
self.delay.apply_push(record,method,attribute,new_value,association_name,target_attribute,batch_size)
|
14
15
|
else
|
15
|
-
apply_push(record,method,attribute,new_value,association_name,target_attribute)
|
16
|
+
apply_push(record,method,attribute,new_value,association_name,target_attribute,batch_size)
|
16
17
|
end
|
17
18
|
else
|
18
19
|
raise Flattery::CacheColumnInflectionError.new("#{record.class.name} #{key}: #{options}")
|
@@ -23,12 +24,24 @@ class Flattery::ValueProvider::Processor
|
|
23
24
|
end
|
24
25
|
|
25
26
|
# Command: performs an update for a specific cache setting
|
26
|
-
def apply_push(record,method,attribute,new_value,association_name,target_attribute)
|
27
|
+
def apply_push(record,method,attribute,new_value,association_name,target_attribute,batch_size)
|
27
28
|
case method
|
28
29
|
when :update_all
|
29
|
-
|
30
|
+
if batch_size > 0
|
31
|
+
total_rows_affected = 0
|
32
|
+
rows_affected = 0
|
33
|
+
begin
|
34
|
+
ActiveRecord::Base.connection.transaction do
|
35
|
+
rows_affected = record.send(association_name).where("NOT #{target_attribute} = ?",new_value).limit(batch_size).update_all(target_attribute => new_value)
|
36
|
+
total_rows_affected += rows_affected
|
37
|
+
end
|
38
|
+
end while rows_affected >= batch_size
|
39
|
+
total_rows_affected
|
40
|
+
else
|
41
|
+
record.send(association_name).update_all({target_attribute => new_value})
|
42
|
+
end
|
30
43
|
else # it is a custom update method
|
31
|
-
record.send(method,attribute,new_value,association_name,target_attribute)
|
44
|
+
record.send(method,attribute,new_value,association_name,target_attribute,batch_size)
|
32
45
|
end
|
33
46
|
end
|
34
47
|
|
@@ -2,7 +2,7 @@ class Flattery::ValueProvider::Settings < Flattery::Settings
|
|
2
2
|
|
3
3
|
# Returns the basic settings template
|
4
4
|
def setting_template
|
5
|
-
{method: :update_all}
|
5
|
+
{method: :update_all, batch_size: 0}
|
6
6
|
end
|
7
7
|
|
8
8
|
# Command: sets resolved_settings. Returns true if resolution was success (which will set the resolution status)
|
@@ -27,6 +27,7 @@ class Flattery::ValueProvider::Settings < Flattery::Settings
|
|
27
27
|
|
28
28
|
push_method = setting[:method]
|
29
29
|
background_with = setting[:background_with]
|
30
|
+
batch_size = setting[:batch_size]
|
30
31
|
attribute_name = "#{from_entity}"
|
31
32
|
|
32
33
|
assoc = klass.reflect_on_association(to_entity)
|
@@ -52,7 +53,8 @@ class Flattery::ValueProvider::Settings < Flattery::Settings
|
|
52
53
|
to_entity: to_entity,
|
53
54
|
as: cached_attribute_name,
|
54
55
|
method: push_method,
|
55
|
-
background_with: background_with
|
56
|
+
background_with: background_with,
|
57
|
+
batch_size: batch_size
|
56
58
|
}
|
57
59
|
end
|
58
60
|
|
data/lib/flattery/version.rb
CHANGED
data/spec/unit/settings_spec.rb
CHANGED
@@ -103,6 +103,27 @@ describe Flattery::Settings do
|
|
103
103
|
]) }
|
104
104
|
end
|
105
105
|
|
106
|
+
describe ":batch_size option" do
|
107
|
+
context "when specified with Symbol keys" do
|
108
|
+
before { settings.add_setting({category: :name, batch_size: 10}) }
|
109
|
+
its(:raw_settings) { should eql([
|
110
|
+
{ from_entity: :category, to_entity: :name, as: nil, batch_size: 10 }
|
111
|
+
]) }
|
112
|
+
end
|
113
|
+
context "when specified with String keys" do
|
114
|
+
before { settings.add_setting({'category' => 'name', 'batch_size' => 10}) }
|
115
|
+
its(:raw_settings) { should eql([
|
116
|
+
{ from_entity: :category, to_entity: :name, as: nil, batch_size: 10 }
|
117
|
+
]) }
|
118
|
+
end
|
119
|
+
context "when not a valid integer" do
|
120
|
+
before { settings.add_setting({category: :name, batch_size: "abc"}) }
|
121
|
+
its(:raw_settings) { should eql([
|
122
|
+
{ from_entity: :category, to_entity: :name, as: nil, batch_size: 0 }
|
123
|
+
]) }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
106
127
|
end
|
107
128
|
|
108
129
|
describe "#settings" do
|
@@ -35,6 +35,37 @@ describe Flattery::ValueProvider::Processor do
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
context "with a custom batch size" do
|
39
|
+
let(:provider_class) do
|
40
|
+
class ::ValueProviderHarness < Category
|
41
|
+
include Flattery::ValueProvider
|
42
|
+
push_flattened_values_for name: :notes, as: :category_name, batch_size: 10
|
43
|
+
end
|
44
|
+
ValueProviderHarness
|
45
|
+
end
|
46
|
+
|
47
|
+
let(:cache_class) do
|
48
|
+
class ::ValueCacheHarness < Note
|
49
|
+
include Flattery::ValueCache
|
50
|
+
flatten_value category: :name
|
51
|
+
end
|
52
|
+
ValueCacheHarness
|
53
|
+
end
|
54
|
+
|
55
|
+
let!(:resource) { provider_class.create(name: 'category_a') }
|
56
|
+
let!(:target_a) { cache_class.create(category_id: resource.id) }
|
57
|
+
let!(:target_other_a) { cache_class.create }
|
58
|
+
context "when cached value is updated" do
|
59
|
+
it "should push the new cache value" do
|
60
|
+
expect {
|
61
|
+
resource.update_attributes(name: 'new category name')
|
62
|
+
}.to change {
|
63
|
+
target_a.reload.category_name
|
64
|
+
}.from('category_a').to('new category name')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
38
69
|
context "with provider that cannot correctly infer the cache column name" do
|
39
70
|
let(:provider_class) do
|
40
71
|
class ::ValueProviderHarness < Category
|
@@ -133,7 +164,7 @@ describe Flattery::ValueProvider::Processor do
|
|
133
164
|
class ::ValueProviderHarness < Category
|
134
165
|
include Flattery::ValueProvider
|
135
166
|
push_flattened_values_for name: :notes, as: :category_name, method: :my_updater_method
|
136
|
-
def my_updater_method(attribute,new_value,association_name,target_attribute)
|
167
|
+
def my_updater_method(attribute,new_value,association_name,target_attribute,batch_size)
|
137
168
|
self.send(association_name).update_all(target_attribute => "#{new_value} (set by my_updater_method)")
|
138
169
|
end
|
139
170
|
end
|
@@ -17,7 +17,7 @@ describe Flattery::ValueProvider::Settings do
|
|
17
17
|
context "before resolution" do
|
18
18
|
it { should be_a(settings_class) }
|
19
19
|
its(:raw_settings) { should eql([
|
20
|
-
{from_entity: :name, to_entity: :notes, as: 'category_name', method: :update_all}
|
20
|
+
{from_entity: :name, to_entity: :notes, as: 'category_name', method: :update_all, batch_size: 0}
|
21
21
|
]) }
|
22
22
|
its(:resolved) { should be_false }
|
23
23
|
end
|
@@ -25,7 +25,7 @@ describe Flattery::ValueProvider::Settings do
|
|
25
25
|
before { settings.settings }
|
26
26
|
its(:resolved) { should be_true }
|
27
27
|
its(:settings) { should eql({
|
28
|
-
"name"=>{to_entity: :notes, as: :category_name, method: :update_all, background_with: nil}
|
28
|
+
"name"=>{to_entity: :notes, as: :category_name, method: :update_all, background_with: nil, batch_size: 0}
|
29
29
|
}) }
|
30
30
|
end
|
31
31
|
end
|
@@ -48,14 +48,14 @@ describe Flattery::ValueProvider::Settings do
|
|
48
48
|
describe "parent" do
|
49
49
|
let(:settings) { parent_provider_class.value_provider_options }
|
50
50
|
its(:raw_settings) { should eql([
|
51
|
-
{from_entity: :name, to_entity: :notes, as: 'category_name', method: :update_all}
|
51
|
+
{from_entity: :name, to_entity: :notes, as: 'category_name', method: :update_all, batch_size: 0}
|
52
52
|
]) }
|
53
53
|
end
|
54
54
|
describe "child" do
|
55
55
|
let(:settings) { child_provider_class.value_provider_options }
|
56
56
|
its(:raw_settings) { should eql([
|
57
|
-
{from_entity: :name, to_entity: :notes, as: 'category_name', method: :update_all},
|
58
|
-
{from_entity: :description, to_entity: :notes, as: 'category_description', method: :update_all}
|
57
|
+
{from_entity: :name, to_entity: :notes, as: 'category_name', method: :update_all, batch_size: 0},
|
58
|
+
{from_entity: :description, to_entity: :notes, as: 'category_description', method: :update_all, batch_size: 0}
|
59
59
|
]) }
|
60
60
|
end
|
61
61
|
end
|
@@ -65,15 +65,15 @@ describe Flattery::ValueProvider::Settings do
|
|
65
65
|
let(:settings) { parent_provider_class.value_provider_options }
|
66
66
|
its(:resolved) { should be_true }
|
67
67
|
its(:settings) { should eql({
|
68
|
-
"name"=>{to_entity: :notes, as: :category_name, method: :update_all, background_with: nil}
|
68
|
+
"name"=>{to_entity: :notes, as: :category_name, method: :update_all, background_with: nil, batch_size: 0}
|
69
69
|
}) }
|
70
70
|
end
|
71
71
|
describe "child" do
|
72
72
|
let(:settings) { child_provider_class.value_provider_options }
|
73
73
|
its(:resolved) { should be_true }
|
74
74
|
its(:settings) { should eql({
|
75
|
-
"name"=>{to_entity: :notes, as: :category_name, method: :update_all, background_with: nil},
|
76
|
-
"description"=>{to_entity: :notes, as: :category_description, method: :update_all, background_with: nil}
|
75
|
+
"name"=>{to_entity: :notes, as: :category_name, method: :update_all, background_with: nil, batch_size: 0},
|
76
|
+
"description"=>{to_entity: :notes, as: :category_description, method: :update_all, background_with: nil, batch_size: 0}
|
77
77
|
}) }
|
78
78
|
end
|
79
79
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flattery
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-09-
|
12
|
+
date: 2013-09-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|