flattery 0.0.3 → 0.0.4
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 +58 -11
- data/flattery.gemspec +2 -2
- data/lib/flattery/settings.rb +2 -0
- data/lib/flattery/value_cache.rb +12 -2
- data/lib/flattery/value_provider.rb +13 -3
- data/lib/flattery/value_provider/processor.rb +20 -6
- data/lib/flattery/value_provider/settings.rb +3 -1
- data/lib/flattery/version.rb +1 -1
- data/spec/support/active_record_fixtures.rb +15 -0
- data/spec/unit/settings_spec.rb +9 -2
- data/spec/unit/value_cache/settings_spec.rb +49 -0
- data/spec/unit/value_provider/processor_spec.rb +112 -0
- data/spec/unit/value_provider/settings_spec.rb +49 -1
- data/spec/unit/value_provider_spec.rb +3 -3
- metadata +5 -4
data/README.md
CHANGED
@@ -7,14 +7,14 @@ The two main reasons you might want to do this are probably:
|
|
7
7
|
|
8
8
|
Hence flattery - a gem that provides a simple declarative method for caching and maintaining such values.
|
9
9
|
|
10
|
-
Flattery is primarily intended for use with relational
|
10
|
+
Flattery is primarily intended for use with relational ActiveRecord storage, and is only tested with sqlite and PostgreSQL.
|
11
11
|
If you are using NoSQL, you probably wouldn't design your schema in a way for which flattery adds any value - but if you find a situation where this makes sense, then feel free to fork and add the support .. or lobby for it's inclusion!
|
12
12
|
|
13
13
|
## Requirements
|
14
14
|
|
15
15
|
* Ruby 1.9 or 2
|
16
16
|
* Rails 3.x/4.x
|
17
|
-
* ActiveRecord (only sqlite and
|
17
|
+
* ActiveRecord (only sqlite and PostgreSQL tested. Others _should_ work; raise an issue if you find problems)
|
18
18
|
|
19
19
|
## Installation
|
20
20
|
|
@@ -34,7 +34,7 @@ Or install it yourself as:
|
|
34
34
|
|
35
35
|
### How to cache values from a :belongs_to association
|
36
36
|
|
37
|
-
Given a model with a :belongs_to association, you want to store a (copy/cached) value from the associated record.
|
37
|
+
Given a model with a :belongs_to association, you want to store a (copy/cached) value from the associated record. The <tt>Flattery::ValueCache</tt> module is used to define the behaviour.
|
38
38
|
|
39
39
|
class Category < ActiveRecord::Base
|
40
40
|
has_many :notes, :inverse_of => :category
|
@@ -48,14 +48,14 @@ Given a model with a :belongs_to association, you want to store a (copy/cached)
|
|
48
48
|
end
|
49
49
|
|
50
50
|
In this case, when you save an instance of Note, it will store the instance.category.name value as instance.category_name.
|
51
|
-
The
|
52
|
-
So before you can use this, you must add a migration to add the
|
51
|
+
The <tt>:category_name</tt> attribute is inferred from the relationship, and is assumed to be present in the schema.
|
52
|
+
So before you can use this, you must add a migration to add the <tt>:category_name</tt> column to the notes table (with the same type as the <tt>:name</tt> column on the Category table).
|
53
53
|
|
54
54
|
|
55
55
|
### How to cache the value in a specific column name
|
56
56
|
|
57
|
-
In the usual case, the cache column name is inferred from the association (e.g. category_name in the example above).
|
58
|
-
If you want to store in another column name, use the
|
57
|
+
In the usual case, the cache column name is inferred from the association (e.g. <tt>:category_name</tt> in the example above).
|
58
|
+
If you want to store in another column name, use the <tt>:as</tt> option on the <tt>flatten_value</tt> call:
|
59
59
|
|
60
60
|
class Note < ActiveRecord::Base
|
61
61
|
belongs_to :category
|
@@ -68,8 +68,7 @@ Again, you must make sure the column is correctly defined in your schema.
|
|
68
68
|
|
69
69
|
### How to push updates to cached values from the source model
|
70
70
|
|
71
|
-
Given the example above, we have a problem if Category records are updated - the
|
72
|
-
The Flattery::ValueProvider module fixes this by propagating changes accordingly.
|
71
|
+
Given the example above, we have a problem if Category records are updated - the <tt>:category_name</tt> value stored in Notes gets out of sync. The <tt>Flattery::ValueProvider</tt> module fixes this by propagating changes accordingly.
|
73
72
|
|
74
73
|
class Category < ActiveRecord::Base
|
75
74
|
has_many :notes
|
@@ -78,13 +77,13 @@ The Flattery::ValueProvider module fixes this by propagating changes accordingly
|
|
78
77
|
push_flattened_values_for :name => :notes
|
79
78
|
end
|
80
79
|
|
81
|
-
This will push changes to Category
|
80
|
+
This will push changes to Category <tt>:name</tt> to Notes records (by inference, updating the <tt>:category_name</tt> value in Notes).
|
82
81
|
|
83
82
|
### How to push updates to cached values from the source model to a specific cache column name
|
84
83
|
|
85
84
|
If the cache column name cannot be inferred correctly, an error will be raised. Inference errors can occur if the inverse association relation cannot be determined.
|
86
85
|
|
87
|
-
To
|
86
|
+
To help flattery figure out the correct column name, specify the column name with an <tt>:as</tt> option:
|
88
87
|
|
89
88
|
class Category < ActiveRecord::Base
|
90
89
|
has_many :notes
|
@@ -93,6 +92,54 @@ To 'help' flattery figure out the correct column name, specify the column name w
|
|
93
92
|
push_flattened_values_for :name => :notes, :as => 'cat_name'
|
94
93
|
end
|
95
94
|
|
95
|
+
### How are cached values pushed from the source model?
|
96
|
+
|
97
|
+
The default mechanism for performing the update of cached values is with the standard ActiveRecord <tt>:update_all</tt> method (scoped to only the affected records). This is done in the <tt>after_update</tt> phase of the [ActiveRecord callback lifecycle](http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html), and does not background the processing by default.
|
98
|
+
|
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
|
+
|
101
|
+
### How to provide a custom method for updating cached values
|
102
|
+
|
103
|
+
Use the <tt>:method</tt> option to declare the instance method to be used:
|
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', :method => :my_custom_updater
|
110
|
+
|
111
|
+
# You custom update method definition. Parameters:
|
112
|
+
# * +attribute+ is the attribute name that the value is coming from e.g. :name
|
113
|
+
# * +new_value+ is the new value that has been set e.g. 'a value that was just set'
|
114
|
+
# * +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)
|
117
|
+
# 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:
|
119
|
+
self.send(association_name).update_all(target_attribute => new_value)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
### How can I get cached value updates pushed in the background?
|
124
|
+
|
125
|
+
Flattery has support for getting updates done in the background. This is not the default behaviour, and must be defined for each <tt>Flattery::ValueProvider</tt> declaration.
|
126
|
+
|
127
|
+
Currently only [Delayed::Job](https://github.com/collectiveidea/delayed_job) is supported. If you want to background with another queue technology, for now the best is to implement this inside a custom update method.
|
128
|
+
|
129
|
+
#### How to background with Delayed::Job
|
130
|
+
|
131
|
+
Use the <tt>:background_with</tt> option:
|
132
|
+
|
133
|
+
class Category < ActiveRecord::Base
|
134
|
+
has_many :notes
|
135
|
+
|
136
|
+
include Flattery::ValueProvider
|
137
|
+
push_flattened_values_for :name => :notes, :as => 'cat_name', :background_with => :delayed_job
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
Note that Delayed::Job is not an explicit dependency of Flattery, so to use Delayed::Job you must have separately added and set it up in your project. Flattery will try to use it if available, and fallback to foreground processing if not.
|
142
|
+
|
96
143
|
|
97
144
|
## Contributing
|
98
145
|
|
data/flattery.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Flattery::VERSION
|
9
9
|
spec.authors = ["Paul Gallagher"]
|
10
10
|
spec.email = ["paul@evendis.com"]
|
11
|
-
spec.description = %q{
|
12
|
-
spec.summary = %q{Flatter your nicely
|
11
|
+
spec.description = %q{a gem to denormalize (flatten) selected ActiveRecord association attributes and automatically keep them in sync with the normal form}
|
12
|
+
spec.summary = %q{Flatter your nicely normalized ActiveRecord models}
|
13
13
|
spec.homepage = "https://github.com/evendis/flattery"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
data/lib/flattery/settings.rb
CHANGED
@@ -27,6 +27,7 @@ class Flattery::Settings
|
|
27
27
|
opt = options.symbolize_keys
|
28
28
|
as_setting = opt.delete(:as).try(:to_s)
|
29
29
|
method_setting = opt.delete(:method).try(:to_sym)
|
30
|
+
background_setting = opt.delete(:background_with).try(:to_sym)
|
30
31
|
|
31
32
|
if from_entity = opt.keys.first
|
32
33
|
cache_options[:from_entity] = from_entity
|
@@ -34,6 +35,7 @@ class Flattery::Settings
|
|
34
35
|
end
|
35
36
|
cache_options[:as] = as_setting
|
36
37
|
cache_options[:method] = method_setting if method_setting
|
38
|
+
cache_options[:background_with] = background_setting if background_setting
|
37
39
|
|
38
40
|
cache_options
|
39
41
|
end
|
data/lib/flattery/value_cache.rb
CHANGED
@@ -2,8 +2,6 @@ module Flattery::ValueCache
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
included do
|
5
|
-
class_attribute :value_cache_options
|
6
|
-
self.value_cache_options = Settings.new(self)
|
7
5
|
before_save Processor.new
|
8
6
|
end
|
9
7
|
|
@@ -24,6 +22,18 @@ module Flattery::ValueCache
|
|
24
22
|
self.value_cache_options.add_setting(options)
|
25
23
|
end
|
26
24
|
|
25
|
+
# Returns the Flattery::ValueCache options value object.
|
26
|
+
# It will inherit settings from a parent class if a model hierarchy has been defined
|
27
|
+
def value_cache_options
|
28
|
+
@value_cache_options ||= if superclass.respond_to?(:value_cache_options)
|
29
|
+
my_settings = Settings.new(self)
|
30
|
+
my_settings.raw_settings = superclass.value_cache_options.raw_settings.dup
|
31
|
+
my_settings
|
32
|
+
else
|
33
|
+
Settings.new(self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
27
37
|
end
|
28
38
|
|
29
39
|
end
|
@@ -2,9 +2,7 @@ module Flattery::ValueProvider
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
included do
|
5
|
-
|
6
|
-
self.value_provider_options = Settings.new(self)
|
7
|
-
before_update Processor.new
|
5
|
+
after_update Processor.new
|
8
6
|
end
|
9
7
|
|
10
8
|
module ClassMethods
|
@@ -24,6 +22,18 @@ module Flattery::ValueProvider
|
|
24
22
|
self.value_provider_options.add_setting(options)
|
25
23
|
end
|
26
24
|
|
25
|
+
# Returns the Flattery::ValueProvider options value object.
|
26
|
+
# It will inherit settings from a parent class if a model hierarchy has been defined
|
27
|
+
def value_provider_options
|
28
|
+
@value_provider_options ||= if superclass.respond_to?(:value_provider_options)
|
29
|
+
my_settings = Settings.new(self)
|
30
|
+
my_settings.raw_settings = superclass.value_provider_options.raw_settings.dup
|
31
|
+
my_settings
|
32
|
+
else
|
33
|
+
Settings.new(self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
27
37
|
end
|
28
38
|
|
29
39
|
end
|
@@ -1,14 +1,18 @@
|
|
1
1
|
class Flattery::ValueProvider::Processor
|
2
2
|
|
3
3
|
# Command: pushes cache updates for related changed attributes
|
4
|
-
def
|
4
|
+
def after_update(record)
|
5
5
|
resolved_options!(record.class).each do |key,options|
|
6
6
|
if record.changed.include?(key)
|
7
|
-
if
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
if target_attribute = options[:as]
|
8
|
+
method = options[:method]
|
9
|
+
attribute = key.to_sym
|
10
|
+
new_value = record.send(key)
|
11
|
+
association_name = options[:to_entity]
|
12
|
+
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
|
+
else
|
15
|
+
apply_push(record,method,attribute,new_value,association_name,target_attribute)
|
12
16
|
end
|
13
17
|
else
|
14
18
|
raise Flattery::CacheColumnInflectionError.new("#{record.class.name} #{key}: #{options}")
|
@@ -18,6 +22,16 @@ class Flattery::ValueProvider::Processor
|
|
18
22
|
true
|
19
23
|
end
|
20
24
|
|
25
|
+
# Command: performs an update for a specific cache setting
|
26
|
+
def apply_push(record,method,attribute,new_value,association_name,target_attribute)
|
27
|
+
case method
|
28
|
+
when :update_all
|
29
|
+
record.send(association_name).update_all({target_attribute => new_value})
|
30
|
+
else # it is a custom update method
|
31
|
+
record.send(method,attribute,new_value,association_name,target_attribute)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
21
35
|
# Command: resolves value provider options for +klass+ if required, and returns resolved options
|
22
36
|
def resolved_options!(klass)
|
23
37
|
klass.value_provider_options.settings
|
@@ -26,6 +26,7 @@ class Flattery::ValueProvider::Settings < Flattery::Settings
|
|
26
26
|
to_entity = setting[:to_entity]
|
27
27
|
|
28
28
|
push_method = setting[:method]
|
29
|
+
background_with = setting[:background_with]
|
29
30
|
attribute_name = "#{from_entity}"
|
30
31
|
|
31
32
|
assoc = klass.reflect_on_association(to_entity)
|
@@ -50,7 +51,8 @@ class Flattery::ValueProvider::Settings < Flattery::Settings
|
|
50
51
|
{
|
51
52
|
to_entity: to_entity,
|
52
53
|
as: cached_attribute_name,
|
53
|
-
method: push_method
|
54
|
+
method: push_method,
|
55
|
+
background_with: background_with
|
54
56
|
}
|
55
57
|
end
|
56
58
|
|
data/lib/flattery/version.rb
CHANGED
@@ -8,7 +8,10 @@ ActiveRecord::Migration.suppress_messages do
|
|
8
8
|
t.string :name;
|
9
9
|
t.belongs_to :category;
|
10
10
|
t.string :category_name;
|
11
|
+
t.string :category_description;
|
11
12
|
t.string :cat_name;
|
13
|
+
t.belongs_to :country;
|
14
|
+
t.string :country_name;
|
12
15
|
t.string :person_name;
|
13
16
|
t.string :person_email;
|
14
17
|
t.string :user_email;
|
@@ -16,6 +19,11 @@ ActiveRecord::Migration.suppress_messages do
|
|
16
19
|
|
17
20
|
create_table(:categories, :force => true) do |t|
|
18
21
|
t.string :name;
|
22
|
+
t.string :description;
|
23
|
+
end
|
24
|
+
|
25
|
+
create_table(:countries, :force => true) do |t|
|
26
|
+
t.string :name;
|
19
27
|
end
|
20
28
|
|
21
29
|
create_table(:people, :force => true) do |t|
|
@@ -29,6 +37,7 @@ end
|
|
29
37
|
# Basic models for testing
|
30
38
|
class Note < ActiveRecord::Base
|
31
39
|
belongs_to :category
|
40
|
+
belongs_to :country
|
32
41
|
belongs_to :person, primary_key: "username", foreign_key: "person_name"
|
33
42
|
end
|
34
43
|
|
@@ -36,6 +45,10 @@ class Category < ActiveRecord::Base
|
|
36
45
|
has_many :notes
|
37
46
|
end
|
38
47
|
|
48
|
+
class Country < ActiveRecord::Base
|
49
|
+
has_many :notes
|
50
|
+
end
|
51
|
+
|
39
52
|
class Person < ActiveRecord::Base
|
40
53
|
has_many :notes, primary_key: "username", foreign_key: "person_name", inverse_of: :person
|
41
54
|
end
|
@@ -51,7 +64,9 @@ module ArHelper
|
|
51
64
|
|
52
65
|
def clear_harness_classes
|
53
66
|
Object.send(:remove_const, :ValueProviderHarness) if Object.constants.include?(:ValueProviderHarness)
|
67
|
+
Object.send(:remove_const, :ChildValueProviderHarness) if Object.constants.include?(:ChildValueProviderHarness)
|
54
68
|
Object.send(:remove_const, :ValueCacheHarness) if Object.constants.include?(:ValueCacheHarness)
|
69
|
+
Object.send(:remove_const, :ChildValueCacheHarness) if Object.constants.include?(:ChildValueCacheHarness)
|
55
70
|
end
|
56
71
|
|
57
72
|
end
|
data/spec/unit/settings_spec.rb
CHANGED
@@ -76,9 +76,16 @@ describe Flattery::Settings do
|
|
76
76
|
end
|
77
77
|
|
78
78
|
context "when optional :method specified as Symbols" do
|
79
|
-
before { settings.add_setting({category: :name, method: :
|
79
|
+
before { settings.add_setting({category: :name, method: :my_custom_updater}) }
|
80
80
|
its(:raw_settings) { should eql([
|
81
|
-
{ from_entity: :category, to_entity: :name, as: nil, method: :
|
81
|
+
{ from_entity: :category, to_entity: :name, as: nil, method: :my_custom_updater }
|
82
|
+
]) }
|
83
|
+
end
|
84
|
+
|
85
|
+
context "when optional :background_with specified as Symbols" do
|
86
|
+
before { settings.add_setting({category: :name, background_with: :delayed_job}) }
|
87
|
+
its(:raw_settings) { should eql([
|
88
|
+
{ from_entity: :category, to_entity: :name, as: nil, background_with: :delayed_job }
|
82
89
|
]) }
|
83
90
|
end
|
84
91
|
|
@@ -29,4 +29,53 @@ describe Flattery::ValueCache::Settings do
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
context "with inherited model definitions and ValueCache defined in the parent" do
|
33
|
+
let!(:parent_cache_class) do
|
34
|
+
class ::ValueCacheHarness < Note
|
35
|
+
include Flattery::ValueCache
|
36
|
+
flatten_value category: :name
|
37
|
+
end
|
38
|
+
ValueCacheHarness
|
39
|
+
end
|
40
|
+
let!(:child_cache_class) do
|
41
|
+
class ::ChildValueCacheHarness < ::ValueCacheHarness
|
42
|
+
flatten_value country: :name
|
43
|
+
end
|
44
|
+
ChildValueCacheHarness
|
45
|
+
end
|
46
|
+
context "before resolution" do
|
47
|
+
describe "parent" do
|
48
|
+
let(:settings) { parent_cache_class.value_cache_options }
|
49
|
+
its(:raw_settings) { should eql([
|
50
|
+
{from_entity: :category, to_entity: :name, as: nil}
|
51
|
+
]) }
|
52
|
+
end
|
53
|
+
describe "child" do
|
54
|
+
let(:settings) { child_cache_class.value_cache_options }
|
55
|
+
its(:raw_settings) { should eql([
|
56
|
+
{from_entity: :category, to_entity: :name, as: nil},
|
57
|
+
{from_entity: :country, to_entity: :name, as: nil}
|
58
|
+
]) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
context "after resolution" do
|
62
|
+
before { parent_cache_class.value_cache_options.settings && child_cache_class.value_cache_options.settings}
|
63
|
+
describe "parent" do
|
64
|
+
let(:settings) { parent_cache_class.value_cache_options }
|
65
|
+
its(:resolved) { should be_true }
|
66
|
+
its(:settings) { should eql({
|
67
|
+
"category_name"=>{from_entity: :category, to_entity: :name, changed_on: ["category_id"]}
|
68
|
+
}) }
|
69
|
+
end
|
70
|
+
describe "child" do
|
71
|
+
let(:settings) { child_cache_class.value_cache_options }
|
72
|
+
its(:resolved) { should be_true }
|
73
|
+
its(:settings) { should eql({
|
74
|
+
"category_name"=>{from_entity: :category, to_entity: :name, changed_on: ["category_id"]},
|
75
|
+
"country_name"=>{from_entity: :country, to_entity: :name, changed_on: ["country_id"]}
|
76
|
+
}) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
32
81
|
end
|
@@ -2,6 +2,7 @@ require 'spec_helper.rb'
|
|
2
2
|
|
3
3
|
# Test caching in a range of actual scenarios
|
4
4
|
describe Flattery::ValueProvider::Processor do
|
5
|
+
let(:processor_class) { Flattery::ValueProvider::Processor }
|
5
6
|
|
6
7
|
context "with provider having simple has_many association and explicit cache_column name" do
|
7
8
|
let(:provider_class) do
|
@@ -127,4 +128,115 @@ describe Flattery::ValueProvider::Processor do
|
|
127
128
|
end
|
128
129
|
end
|
129
130
|
|
131
|
+
context "with a custom update method" do
|
132
|
+
let(:provider_class) do
|
133
|
+
class ::ValueProviderHarness < Category
|
134
|
+
include Flattery::ValueProvider
|
135
|
+
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)
|
137
|
+
self.send(association_name).update_all(target_attribute => "#{new_value} (set by my_updater_method)")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
ValueProviderHarness
|
141
|
+
end
|
142
|
+
|
143
|
+
let(:cache_class) do
|
144
|
+
class ::ValueCacheHarness < Note
|
145
|
+
include Flattery::ValueCache
|
146
|
+
flatten_value category: :name
|
147
|
+
end
|
148
|
+
ValueCacheHarness
|
149
|
+
end
|
150
|
+
|
151
|
+
let!(:resource) { provider_class.create(name: 'category_a') }
|
152
|
+
let!(:target_a) { cache_class.create(category_id: resource.id) }
|
153
|
+
let!(:target_other_a) { cache_class.create }
|
154
|
+
context "when cached value is updated" do
|
155
|
+
it "should push the new cache value" do
|
156
|
+
expect {
|
157
|
+
resource.update_attributes(name: 'new category name')
|
158
|
+
}.to change {
|
159
|
+
target_a.reload.category_name
|
160
|
+
}.from('category_a').to('new category name (set by my_updater_method)')
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context "with delayed job support stubbed" do
|
166
|
+
before do
|
167
|
+
processor_class.any_instance.stub(:delay)
|
168
|
+
end
|
169
|
+
|
170
|
+
context "and background processing requested" do
|
171
|
+
|
172
|
+
let(:provider_class) do
|
173
|
+
class ::ValueProviderHarness < Category
|
174
|
+
include Flattery::ValueProvider
|
175
|
+
push_flattened_values_for name: :notes, as: :category_name, background_with: :delayed_job
|
176
|
+
end
|
177
|
+
ValueProviderHarness
|
178
|
+
end
|
179
|
+
|
180
|
+
let(:cache_class) do
|
181
|
+
class ::ValueCacheHarness < Note
|
182
|
+
include Flattery::ValueCache
|
183
|
+
flatten_value category: :name
|
184
|
+
end
|
185
|
+
ValueCacheHarness
|
186
|
+
end
|
187
|
+
|
188
|
+
let!(:resource) { provider_class.create(name: 'category_a') }
|
189
|
+
let!(:target_a) { cache_class.create(category_id: resource.id) }
|
190
|
+
let!(:target_other_a) { cache_class.create }
|
191
|
+
|
192
|
+
it "should update via delay" do
|
193
|
+
processor = double()
|
194
|
+
processor.should_receive(:apply_push)
|
195
|
+
processor_class.any_instance.should_receive(:delay).and_return(processor)
|
196
|
+
resource.update_attributes(name: 'new category name')
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context "with delayed job support mocked" do
|
202
|
+
before do
|
203
|
+
processor_class.send(:define_method,:delay) { self }
|
204
|
+
end
|
205
|
+
after do
|
206
|
+
processor_class.send(:undef_method,:delay)
|
207
|
+
end
|
208
|
+
|
209
|
+
context "and background processing requested" do
|
210
|
+
|
211
|
+
let(:provider_class) do
|
212
|
+
class ::ValueProviderHarness < Category
|
213
|
+
include Flattery::ValueProvider
|
214
|
+
push_flattened_values_for name: :notes, as: :category_name, background_with: :delayed_job
|
215
|
+
end
|
216
|
+
ValueProviderHarness
|
217
|
+
end
|
218
|
+
|
219
|
+
let(:cache_class) do
|
220
|
+
class ::ValueCacheHarness < Note
|
221
|
+
include Flattery::ValueCache
|
222
|
+
flatten_value category: :name
|
223
|
+
end
|
224
|
+
ValueCacheHarness
|
225
|
+
end
|
226
|
+
|
227
|
+
let!(:resource) { provider_class.create(name: 'category_a') }
|
228
|
+
let!(:target_a) { cache_class.create(category_id: resource.id) }
|
229
|
+
let!(:target_other_a) { cache_class.create }
|
230
|
+
|
231
|
+
it "should push the new cache value" do
|
232
|
+
expect {
|
233
|
+
resource.update_attributes(name: 'new category name')
|
234
|
+
}.to change {
|
235
|
+
target_a.reload.category_name
|
236
|
+
}.from('category_a').to('new category name')
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
130
242
|
end
|
@@ -25,10 +25,58 @@ 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}
|
28
|
+
"name"=>{to_entity: :notes, as: :category_name, method: :update_all, background_with: nil}
|
29
29
|
}) }
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
context "with inherited model definitions and ValueProvider defined in the parent" do
|
34
|
+
let!(:parent_provider_class) do
|
35
|
+
class ::ValueProviderHarness < Category
|
36
|
+
include Flattery::ValueProvider
|
37
|
+
push_flattened_values_for name: :notes, as: :category_name
|
38
|
+
end
|
39
|
+
ValueProviderHarness
|
40
|
+
end
|
41
|
+
let!(:child_provider_class) do
|
42
|
+
class ::ChildValueProviderHarness < ::ValueProviderHarness
|
43
|
+
push_flattened_values_for description: :notes, as: :category_description
|
44
|
+
end
|
45
|
+
ChildValueProviderHarness
|
46
|
+
end
|
47
|
+
context "before resolution" do
|
48
|
+
describe "parent" do
|
49
|
+
let(:settings) { parent_provider_class.value_provider_options }
|
50
|
+
its(:raw_settings) { should eql([
|
51
|
+
{from_entity: :name, to_entity: :notes, as: 'category_name', method: :update_all}
|
52
|
+
]) }
|
53
|
+
end
|
54
|
+
describe "child" do
|
55
|
+
let(:settings) { child_provider_class.value_provider_options }
|
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}
|
59
|
+
]) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
context "after resolution" do
|
63
|
+
before { parent_provider_class.value_provider_options.settings && child_provider_class.value_provider_options.settings}
|
64
|
+
describe "parent" do
|
65
|
+
let(:settings) { parent_provider_class.value_provider_options }
|
66
|
+
its(:resolved) { should be_true }
|
67
|
+
its(:settings) { should eql({
|
68
|
+
"name"=>{to_entity: :notes, as: :category_name, method: :update_all, background_with: nil}
|
69
|
+
}) }
|
70
|
+
end
|
71
|
+
describe "child" do
|
72
|
+
let(:settings) { child_provider_class.value_provider_options }
|
73
|
+
its(:resolved) { should be_true }
|
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}
|
77
|
+
}) }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
33
81
|
|
34
82
|
end
|
@@ -14,15 +14,15 @@ describe Flattery::ValueProvider do
|
|
14
14
|
its(:included_modules) { should include(Flattery::ValueProvider) }
|
15
15
|
its(:value_provider_options) { should be_a(Flattery::ValueProvider::Settings) }
|
16
16
|
|
17
|
-
describe "#
|
17
|
+
describe "#after_update" do
|
18
18
|
let(:processor_class) { Flattery::ValueProvider::Processor }
|
19
19
|
it "should not be called when record created" do
|
20
|
-
processor_class.any_instance.should_receive(:
|
20
|
+
processor_class.any_instance.should_receive(:after_update).never
|
21
21
|
provider_class.create!
|
22
22
|
end
|
23
23
|
it "should be called when record updated" do
|
24
24
|
instance = provider_class.create!
|
25
|
-
processor_class.any_instance.should_receive(:
|
25
|
+
processor_class.any_instance.should_receive(:after_update).and_return(true)
|
26
26
|
instance.save
|
27
27
|
end
|
28
28
|
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.0.4
|
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-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -155,7 +155,8 @@ dependencies:
|
|
155
155
|
- - ! '>='
|
156
156
|
- !ruby/object:Gem::Version
|
157
157
|
version: 1.3.2
|
158
|
-
description:
|
158
|
+
description: a gem to denormalize (flatten) selected ActiveRecord association attributes
|
159
|
+
and automatically keep them in sync with the normal form
|
159
160
|
email:
|
160
161
|
- paul@evendis.com
|
161
162
|
executables: []
|
@@ -215,7 +216,7 @@ rubyforge_project:
|
|
215
216
|
rubygems_version: 1.8.25
|
216
217
|
signing_key:
|
217
218
|
specification_version: 3
|
218
|
-
summary: Flatter your nicely
|
219
|
+
summary: Flatter your nicely normalized ActiveRecord models
|
219
220
|
test_files:
|
220
221
|
- spec/spec_helper.rb
|
221
222
|
- spec/support/active_record_fixtures.rb
|