flattery 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
- # You custom update method definition. Parameters:
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 e.g. :cat_name
116
- def my_custom_updater(attribute,new_value,association_name,target_attribute)
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?
@@ -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
- record.send(association_name).update_all({target_attribute => new_value})
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
 
@@ -1,3 +1,3 @@
1
1
  module Flattery
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -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
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-11 00:00:00.000000000 Z
12
+ date: 2013-09-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport