flattery 0.0.2 → 0.0.3

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 CHANGED
@@ -32,20 +32,26 @@ Or install it yourself as:
32
32
 
33
33
  ## Usage
34
34
 
35
- ### How to define a model that has cached values from a :belongs_to association
35
+ ### How to cache values from a :belongs_to association
36
36
 
37
- Given a model with a :category assoociation, and you want to cache instance.category.name as instance.category_name.
37
+ Given a model with a :belongs_to association, you want to store a (copy/cached) value from the associated record.
38
38
 
39
- First, add a migration to add :category_name column to your table with the same type as category.name.
40
- Then just include Flattery::ValueCache in your model and define flatten_values like this:
39
+ class Category < ActiveRecord::Base
40
+ has_many :notes, :inverse_of => :category
41
+ end
41
42
 
42
43
  class Note < ActiveRecord::Base
43
- belongs_to :category
44
+ belongs_to :category, :inverse_of => :notes
44
45
 
45
46
  include Flattery::ValueCache
46
47
  flatten_value :category => :name
47
48
  end
48
49
 
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 :category_name 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 :category_name column to the notes table (with the same type as the :name column on the Category table).
53
+
54
+
49
55
  ### How to cache the value in a specific column name
50
56
 
51
57
  In the usual case, the cache column name is inferred from the association (e.g. category_name in the example above).
@@ -58,12 +64,12 @@ If you want to store in another column name, use the :as option on the +flatten_
58
64
  flatten_value :category => :name, :as => 'cat_name'
59
65
  end
60
66
 
61
- ### How to push updates to cached values from the source model
67
+ Again, you must make sure the column is correctly defined in your schema.
62
68
 
63
- Given a model with a :category assoociation, and a flattery config that caches instance.category.name to instance.category_name,
64
- you want the category_name cached value updated if the category.name changes.
69
+ ### How to push updates to cached values from the source model
65
70
 
66
- This is achieved by adding the Flattery::ValueProvider to the source model and defining push_flattened_values_for like this:
71
+ Given the example above, we have a problem if Category records are updated - the :category_name value stored in Notes gets out of sync.
72
+ The Flattery::ValueProvider module fixes this by propagating changes accordingly.
67
73
 
68
74
  class Category < ActiveRecord::Base
69
75
  has_many :notes
@@ -72,7 +78,7 @@ This is achieved by adding the Flattery::ValueProvider to the source model and d
72
78
  push_flattened_values_for :name => :notes
73
79
  end
74
80
 
75
- This will respect the flatten_value settings defined in that target mode (Note in this example).
81
+ This will push changes to Category :name to Notes records (by inference, updating the :category_name value in Notes).
76
82
 
77
83
  ### How to push updates to cached values from the source model to a specific cache column name
78
84
 
@@ -88,7 +94,6 @@ To 'help' flattery figure out the correct column name, specify the column name w
88
94
  end
89
95
 
90
96
 
91
-
92
97
  ## Contributing
93
98
 
94
99
  1. Fork it
@@ -2,5 +2,6 @@ require "active_support/core_ext"
2
2
  require "active_record"
3
3
  require "flattery/version"
4
4
  require "flattery/exception"
5
+ require "flattery/settings"
5
6
  require "flattery/value_cache"
6
7
  require "flattery/value_provider"
@@ -0,0 +1,71 @@
1
+ class Flattery::Settings
2
+
3
+ attr_accessor :klass
4
+ attr_accessor :raw_settings
5
+ attr_accessor :resolved_settings
6
+ attr_accessor :resolved
7
+
8
+ def initialize(klass=nil)
9
+ self.klass = klass
10
+ reset!
11
+ end
12
+
13
+ def add_setting(options={})
14
+ if options.nil?
15
+ reset!
16
+ return
17
+ end
18
+ return if options.empty?
19
+ unresolved!
20
+ self.raw_settings << parse_option_setting(options)
21
+ end
22
+
23
+ # Process +options+ and return a standardised raw_setting hash
24
+ def parse_option_setting(options)
25
+ cache_options = setting_template
26
+
27
+ opt = options.symbolize_keys
28
+ as_setting = opt.delete(:as).try(:to_s)
29
+ method_setting = opt.delete(:method).try(:to_sym)
30
+
31
+ if from_entity = opt.keys.first
32
+ cache_options[:from_entity] = from_entity
33
+ cache_options[:to_entity] = opt[from_entity].try(:to_sym)
34
+ end
35
+ cache_options[:as] = as_setting
36
+ cache_options[:method] = method_setting if method_setting
37
+
38
+ cache_options
39
+ end
40
+
41
+ # Returns the basic settings template
42
+ def setting_template
43
+ {}
44
+ end
45
+
46
+ # Command: mark settings as unresolved
47
+ def unresolved!
48
+ self.resolved = false
49
+ end
50
+
51
+ # Command: clear/reset all settings
52
+ def reset!
53
+ self.raw_settings = []
54
+ self.resolved_settings = {}
55
+ self.resolved = false
56
+ end
57
+
58
+ # Returns resolved settings
59
+ def settings
60
+ unless resolved
61
+ self.resolved = resolve_settings!
62
+ end
63
+ self.resolved_settings
64
+ end
65
+
66
+ # Command: sets resolved_settings. Returns true if resolution was success (which will set the resolution status)
67
+ def resolve_settings!
68
+ true
69
+ end
70
+
71
+ end
@@ -3,9 +3,8 @@ module Flattery::ValueCache
3
3
 
4
4
  included do
5
5
  class_attribute :value_cache_options
6
- self.value_cache_options = {}
7
-
8
- before_save :resolve_value_cache
6
+ self.value_cache_options = Settings.new(self)
7
+ before_save Processor.new
9
8
  end
10
9
 
11
10
  module ClassMethods
@@ -22,49 +21,12 @@ module Flattery::ValueCache
22
21
  # When explicitly passed nil, it clears all existing settings
23
22
  #
24
23
  def flatten_value(options={})
25
- if options.nil?
26
- self.value_cache_options = {}
27
- return
28
- end
29
-
30
- self.value_cache_options ||= {}
31
- opt = options.symbolize_keys
32
- as_setting = opt.delete(:as)
33
- association_name = opt.keys.first
34
- association_method = opt[association_name].try(:to_sym)
35
- cache_attribute = (as_setting || "#{association_name}_#{association_method}").to_s
36
-
37
- assoc = reflect_on_association(association_name)
38
- cache_options = if assoc && assoc.belongs_to? && assoc.klass.column_names.include?("#{association_method}")
39
- {
40
- association_name: association_name,
41
- association_method: association_method,
42
- changed_on: [assoc.foreign_key]
43
- }
44
- end
45
-
46
- if cache_options
47
- self.value_cache_options[cache_attribute] = cache_options
48
- else
49
- self.value_cache_options.delete(cache_attribute)
50
- end
51
- end
52
-
53
- # Returns the cache_column name given +association_name+ and +association_method+
54
- def cache_attribute_for_association(association_name,association_method)
55
- value_cache_options.detect{|k,v| v[:association_name] == association_name.to_sym && v[:association_method] == association_method.to_sym }.first
24
+ self.value_cache_options.add_setting(options)
56
25
  end
57
26
 
58
27
  end
59
28
 
60
- # Command: updates cached values for related changed attributes
61
- def resolve_value_cache
62
- self.class.value_cache_options.each do |key,options|
63
- if changed & options[:changed_on]
64
- self.send("#{key}=", self.send(options[:association_name]).try(:send,options[:association_method]))
65
- end
66
- end
67
- true
68
- end
69
-
70
29
  end
30
+
31
+ require "flattery/value_cache/settings"
32
+ require "flattery/value_cache/processor"
@@ -0,0 +1,18 @@
1
+ class Flattery::ValueCache::Processor
2
+
3
+ # Command: updates cached values for related changed attributes
4
+ def before_save(record)
5
+ resolved_options!(record.class).each do |key,options|
6
+ if record.changed & options[:changed_on]
7
+ record.send("#{key}=", record.send(options[:from_entity]).try(:send,options[:to_entity]))
8
+ end
9
+ end
10
+ true
11
+ end
12
+
13
+ # Command: resolves value cache options for +klass+ if required, and returns resolved options
14
+ def resolved_options!(klass)
15
+ klass.value_cache_options.settings
16
+ end
17
+
18
+ end
@@ -0,0 +1,44 @@
1
+ class Flattery::ValueCache::Settings < Flattery::Settings
2
+
3
+ # Command: sets resolved_settings. Returns true if resolution was success (which will set the resolution status)
4
+ #
5
+ # Given raw settings: [{ from_entity: :category, to_entity: :name, as: 'cat_name' }]
6
+ # Resolved settings: { 'cat_name' => { from_entity: :category, to_entity: :name, changed_on: ['category_id'] } }
7
+ #
8
+ # In the ValueCache context:
9
+ # * +from_entity+ is the association from which the cache value is obtained
10
+ # * +to_entity+ is the column that the cache value will be stored in
11
+ # * +as+ is the column name on +from_entity+ from which the cache value is to be read
12
+ #
13
+ # Validations/transformations performed:
14
+ # * from_entity is a valid association
15
+ # * cache attribute name derived from :as or inferred from association/attribute names
16
+ # * cache attribute is a valid column
17
+ #
18
+ # If any of these fail, the setting is excluded from the resolved options.
19
+ #
20
+ def resolve_settings!
21
+ self.resolved_settings = raw_settings.each_with_object({}) do |setting,memo|
22
+ from_entity = setting[:from_entity]
23
+ to_entity = setting[:to_entity]
24
+ cache_attribute = (setting[:as] || "#{from_entity}_#{to_entity}").to_s
25
+
26
+ assoc = klass.reflect_on_association(from_entity)
27
+ cache_options = if assoc && assoc.belongs_to? && klass.column_names.include?("#{cache_attribute}")
28
+ {
29
+ from_entity: from_entity,
30
+ to_entity: to_entity,
31
+ changed_on: [assoc.foreign_key]
32
+ }
33
+ end
34
+
35
+ if cache_options
36
+ memo[cache_attribute] = cache_options
37
+ else
38
+ memo.delete(cache_attribute)
39
+ end
40
+ end
41
+ true
42
+ end
43
+
44
+ end
@@ -3,9 +3,8 @@ module Flattery::ValueProvider
3
3
 
4
4
  included do
5
5
  class_attribute :value_provider_options
6
- self.value_provider_options = {}
7
-
8
- before_update :resolve_value_provision
6
+ self.value_provider_options = Settings.new(self)
7
+ before_update Processor.new
9
8
  end
10
9
 
11
10
  module ClassMethods
@@ -22,78 +21,12 @@ module Flattery::ValueProvider
22
21
  # When explicitly passed nil, it clears all existing settings
23
22
  #
24
23
  def push_flattened_values_for(options={})
25
- if options.nil?
26
- self.value_provider_options = {}
27
- return
28
- end
29
-
30
- self.value_provider_options ||= {}
31
- opt = options.symbolize_keys
32
- as_setting = opt.delete(:as)
33
-
34
- attribute_key = opt.keys.first
35
- association_name = opt[attribute_key]
36
- attribute_name = "#{attribute_key}"
37
-
38
- cached_attribute_name = (as_setting || "inflect").to_sym
39
-
40
- assoc = reflect_on_association(association_name)
41
- cache_options = if assoc && assoc.macro == :has_many
42
- {
43
- association_name: association_name,
44
- cached_attribute_name: cached_attribute_name,
45
- method: :update_all
46
- }
47
- end
48
-
49
- if cache_options
50
- self.value_provider_options[attribute_name] = cache_options
51
- else
52
- self.value_provider_options.delete(attribute_name)
53
- end
24
+ self.value_provider_options.add_setting(options)
54
25
  end
55
26
 
56
27
  end
57
28
 
58
- # Command: pushes cache updates for related changed attributes
59
- def resolve_value_provision
60
- self.class.value_provider_options.each do |key,options|
61
- if changed.include?(key)
62
-
63
- association_name = options[:association_name]
64
-
65
- cache_column = if options[:cached_attribute_name] == :inflect
66
- name = nil
67
- if assoc = self.class.reflect_on_association(association_name)
68
- other_assoc_name = if assoc.inverse_of
69
- assoc.inverse_of.name
70
- else
71
- end
72
- if other_assoc_name
73
- if assoc.klass.respond_to?(:cache_attribute_for_association)
74
- name = assoc.klass.cache_attribute_for_association(other_assoc_name,key)
75
- end
76
- name ||= "#{other_assoc_name}_#{key}"
77
- end
78
- name = nil unless name && assoc.klass.column_names.include?(name)
79
- end
80
- name
81
- else
82
- options[:cached_attribute_name]
83
- end
84
-
85
- if cache_column
86
- case options[:method]
87
- when :update_all
88
- new_value = self.send(key)
89
- self.send(association_name).update_all({cache_column => new_value})
90
- end
91
- else
92
- raise Flattery::CacheColumnInflectionError.new("#{self.class.name} #{key}: #{options}")
93
- end
94
- end
95
- end
96
- true
97
- end
98
-
99
29
  end
30
+
31
+ require "flattery/value_provider/settings"
32
+ require "flattery/value_provider/processor"
@@ -0,0 +1,26 @@
1
+ class Flattery::ValueProvider::Processor
2
+
3
+ # Command: pushes cache updates for related changed attributes
4
+ def before_update(record)
5
+ resolved_options!(record.class).each do |key,options|
6
+ if record.changed.include?(key)
7
+ if cache_column = options[:as]
8
+ case options[:method]
9
+ when :update_all
10
+ new_value = record.send(key)
11
+ record.send(options[:to_entity]).update_all({cache_column => new_value})
12
+ end
13
+ else
14
+ raise Flattery::CacheColumnInflectionError.new("#{record.class.name} #{key}: #{options}")
15
+ end
16
+ end
17
+ end
18
+ true
19
+ end
20
+
21
+ # Command: resolves value provider options for +klass+ if required, and returns resolved options
22
+ def resolved_options!(klass)
23
+ klass.value_provider_options.settings
24
+ end
25
+
26
+ end
@@ -0,0 +1,74 @@
1
+ class Flattery::ValueProvider::Settings < Flattery::Settings
2
+
3
+ # Returns the basic settings template
4
+ def setting_template
5
+ {method: :update_all}
6
+ end
7
+
8
+ # Command: sets resolved_settings. Returns true if resolution was success (which will set the resolution status)
9
+ #
10
+ # Given raw settings: [{ from_entity: :name, to_entity: :notes, as: 'cat_name', method: :update_all }]
11
+ # Resolved settings: { 'name' => { to_entity: :notes, as: 'cat_name', method: :update_all } }
12
+ #
13
+ # In the ValueProvider context:
14
+ # * +from_entity+ is the column that provides the cache value
15
+ # * +to_entity+ is the association to which the cache value is pushed
16
+ # * +as+ is the column name on +to_entity+ from which the cache value is to be stored
17
+ #
18
+ # Validations/transformations performed:
19
+ # * to_entity is a valid association
20
+ #
21
+ # If any of these fail, the setting is excluded from the resolved options.
22
+ #
23
+ def resolve_settings!
24
+ self.resolved_settings = raw_settings.each_with_object({}) do |setting,memo|
25
+ from_entity = setting[:from_entity]
26
+ to_entity = setting[:to_entity]
27
+
28
+ push_method = setting[:method]
29
+ attribute_name = "#{from_entity}"
30
+
31
+ assoc = klass.reflect_on_association(to_entity)
32
+ cache_options = if assoc && assoc.macro == :has_many
33
+
34
+ cached_attribute_name = if setting[:as].present?
35
+ setting[:as].to_sym
36
+ else
37
+ name = nil
38
+ other_assoc_name = if assoc.inverse_of
39
+ assoc.inverse_of.name
40
+ else
41
+ end
42
+ if other_assoc_name
43
+ name = cache_attribute_for_association(assoc.klass,other_assoc_name,attribute_name)
44
+ name ||= "#{other_assoc_name}_#{attribute_name}"
45
+ end
46
+ name = nil unless name && assoc.klass.column_names.include?(name)
47
+ name
48
+ end
49
+
50
+ {
51
+ to_entity: to_entity,
52
+ as: cached_attribute_name,
53
+ method: push_method
54
+ }
55
+ end
56
+
57
+ if cache_options
58
+ memo[attribute_name] = cache_options
59
+ else
60
+ memo.delete(attribute_name)
61
+ end
62
+
63
+ end
64
+ true
65
+ end
66
+
67
+ # Returns the cache_column name given +association_name+ and +association_method+
68
+ def cache_attribute_for_association(klass,association_name,association_method)
69
+ if klass.respond_to?(:value_cache_options)
70
+ klass.value_cache_options.settings.detect{|k,v| v[:from_entity] == association_name.to_sym && v[:to_entity] == association_method.to_sym }.first
71
+ end
72
+ end
73
+
74
+ end
@@ -1,3 +1,3 @@
1
1
  module Flattery
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -25,4 +25,8 @@ RSpec.configure do |config|
25
25
  config.before do
26
26
  truncate_records
27
27
  end
28
+
29
+ config.after do
30
+ clear_harness_classes
31
+ end
28
32
  end
@@ -49,6 +49,11 @@ module ArHelper
49
49
  Person.delete_all
50
50
  end
51
51
 
52
+ def clear_harness_classes
53
+ Object.send(:remove_const, :ValueProviderHarness) if Object.constants.include?(:ValueProviderHarness)
54
+ Object.send(:remove_const, :ValueCacheHarness) if Object.constants.include?(:ValueCacheHarness)
55
+ end
56
+
52
57
  end
53
58
 
54
59
  RSpec.configure do |conf|
@@ -0,0 +1,123 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe Flattery::Settings do
4
+ let(:settings_class) { Flattery::Settings }
5
+ let(:settings) { settings_class.new }
6
+ subject { settings }
7
+
8
+ describe "#initialize" do
9
+ its(:klass) { should be_nil }
10
+ its(:raw_settings) { should eql([]) }
11
+ its(:resolved_settings) { should eql({}) }
12
+ its(:resolved) { should be_false}
13
+ context "when given class parameter" do
14
+ let(:klass) { String }
15
+ let(:settings) { settings_class.new(klass) }
16
+ its(:klass) { should eql(klass) }
17
+ end
18
+ end
19
+
20
+ def add_dummy_settings_values
21
+ settings.raw_settings = [1,2,3,4]
22
+ settings.resolved_settings = {a: :b}
23
+ settings.resolved = true
24
+ settings
25
+ end
26
+
27
+ describe "#reset!" do
28
+ before do
29
+ add_dummy_settings_values
30
+ settings.reset!
31
+ end
32
+ its(:raw_settings) { should eql([]) }
33
+ its(:resolved_settings) { should eql({}) }
34
+ its(:resolved) { should be_false}
35
+ end
36
+
37
+ describe "#add_setting" do
38
+
39
+ context "when given empty hash" do
40
+ before { add_dummy_settings_values }
41
+ it "should not do anything" do
42
+ expect { settings.add_setting({}) }.to_not change { settings.raw_settings }.from([1,2,3,4])
43
+ end
44
+ end
45
+
46
+ context "when given nil" do
47
+ it "should cause a reset!" do
48
+ settings.should_receive(:reset!)
49
+ settings.add_setting(nil)
50
+ end
51
+ end
52
+
53
+ context "when given options as Symbols" do
54
+ before { settings.add_setting({category: :name}) }
55
+ its(:raw_settings) { should eql([
56
+ { from_entity: :category, to_entity: :name, as: nil }
57
+ ]) }
58
+ context "and then given another definition" do
59
+ before { settings.add_setting({person: :email}) }
60
+ its(:raw_settings) { should eql([
61
+ { from_entity: :category, to_entity: :name, as: nil },
62
+ { from_entity: :person, to_entity: :email, as: nil }
63
+ ]) }
64
+ context "and then reset with nil" do
65
+ before { settings.add_setting nil }
66
+ its(:raw_settings) { should eql([]) }
67
+ end
68
+ end
69
+ end
70
+
71
+ context "when optional :as specified as Symbols" do
72
+ before { settings.add_setting({category: :name, as: :cat_name}) }
73
+ its(:raw_settings) { should eql([
74
+ { from_entity: :category, to_entity: :name, as: 'cat_name' }
75
+ ]) }
76
+ end
77
+
78
+ context "when optional :method specified as Symbols" do
79
+ before { settings.add_setting({category: :name, method: :update_all}) }
80
+ its(:raw_settings) { should eql([
81
+ { from_entity: :category, to_entity: :name, as: nil, method: :update_all }
82
+ ]) }
83
+ end
84
+
85
+ context "when given options as String" do
86
+ before { settings.add_setting({'category' => 'name'}) }
87
+ its(:raw_settings) { should eql([
88
+ { from_entity: :category, to_entity: :name, as: nil }
89
+ ]) }
90
+ end
91
+
92
+ context "when optional :as specified as String" do
93
+ before { settings.add_setting({'category' => 'name', 'as' => 'cat_name'}) }
94
+ its(:raw_settings) { should eql([
95
+ { from_entity: :category, to_entity: :name, as: 'cat_name' }
96
+ ]) }
97
+ end
98
+
99
+ end
100
+
101
+ describe "#settings" do
102
+ context "when initialially not resolved" do
103
+ it "should invoke resolve_settings!" do
104
+ settings.should_receive(:resolve_settings!)
105
+ settings.settings
106
+ end
107
+ it "should mark as resolved" do
108
+ expect { settings.settings }.to change { settings.resolved }.from(false).to(true)
109
+ end
110
+ end
111
+ context "when initialially resolved" do
112
+ before { add_dummy_settings_values }
113
+ it "should not invoke resolve_settings!" do
114
+ settings.should_receive(:resolve_settings!).never
115
+ settings.settings
116
+ end
117
+ it "should not change resolved status" do
118
+ expect { settings.settings }.to_not change { settings.resolved }.from(true)
119
+ end
120
+ end
121
+ end
122
+
123
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper.rb'
2
+
3
+ # Now test caching in a range of actual scenarios...
4
+ describe Flattery::ValueCache::Processor do
5
+
6
+ context "with simple belongs_to associations with cached values" do
7
+ let(:cache_class) do
8
+ class ::ValueCacheHarness < Note
9
+ include Flattery::ValueCache
10
+ flatten_value category: :name
11
+ end
12
+ ValueCacheHarness
13
+ end
14
+ let!(:resource) { cache_class.create }
15
+
16
+ let!(:category) { Category.create(name: 'category_a') }
17
+
18
+ context "when association is changed by id" do
19
+ it "should cache the new value" do
20
+ expect {
21
+ resource.update_attributes(category_id: category.id)
22
+ }.to change {
23
+ resource.category_name
24
+ }.from(nil).to(category.name)
25
+ end
26
+ end
27
+ context "when already set" do
28
+ before { resource.update_attributes(category_id: category.id) }
29
+ context "then set to nil" do
30
+ it "should cache the new value" do
31
+ expect {
32
+ resource.update_attributes(category_id: nil)
33
+ }.to change {
34
+ resource.category_name
35
+ }.from(category.name).to(nil)
36
+ end
37
+ end
38
+ context "and associated record is destroyed" do
39
+ before { category.destroy }
40
+ it "should not recache the value when other values updated" do
41
+ expect {
42
+ resource.update_attributes(name: 'a new name')
43
+ }.to_not change {
44
+ resource.category_name
45
+ }.from(category.name)
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe Flattery::ValueCache::Settings do
4
+ let(:settings_class) { Flattery::ValueCache::Settings }
5
+ let(:settings) { cache_class.value_cache_options }
6
+ subject { settings }
7
+
8
+ context "with a standard belongs_to association" do
9
+ let(:cache_class) do
10
+ class ::ValueCacheHarness < Note
11
+ include Flattery::ValueCache
12
+ flatten_value category: :name
13
+ end
14
+ ValueCacheHarness
15
+ end
16
+ context "before resolution" do
17
+ it { should be_a(settings_class) }
18
+ its(:raw_settings) { should eql([
19
+ {from_entity: :category, to_entity: :name, as: nil}
20
+ ]) }
21
+ its(:resolved) { should be_false }
22
+ end
23
+ context "after resolution" do
24
+ before { settings.settings }
25
+ its(:resolved) { should be_true }
26
+ its(:settings) { should eql({
27
+ "category_name"=>{from_entity: :category, to_entity: :name, changed_on: ["category_id"]}
28
+ }) }
29
+ end
30
+ end
31
+
32
+ end
@@ -1,156 +1,30 @@
1
1
  require 'spec_helper.rb'
2
2
 
3
- class FlatteryValueCacheTestHarness < Note
4
- include Flattery::ValueCache
5
- end
6
-
7
3
  describe Flattery::ValueCache do
8
4
 
9
- let(:resource_class) { FlatteryValueCacheTestHarness }
10
- after { resource_class.value_cache_options = {} }
11
-
12
- describe "##included_modules" do
13
- subject { resource_class.included_modules }
14
- it { should include(Flattery::ValueCache) }
15
- end
16
-
17
- describe "##value_cache_options" do
18
- before { resource_class.flatten_value flatten_value_options }
19
- subject { resource_class.value_cache_options }
20
-
21
- context "when set to empty" do
22
- let(:flatten_value_options) { {} }
23
- it { should be_empty }
24
- end
25
-
26
- context "when reset with nil" do
27
- let(:flatten_value_options) { {category: :name} }
28
- it "should clear all settings" do
29
- expect {
30
- resource_class.flatten_value nil
31
- }.to change {
32
- resource_class.value_cache_options
33
- }.to({})
34
- end
35
- end
36
-
37
- context "with simple belongs_to association" do
38
-
39
- context "when set by association name and attribute value" do
40
- let(:flatten_value_options) { {category: :name} }
41
- it { should eql({
42
- "category_name" => {
43
- association_name: :category,
44
- association_method: :name,
45
- changed_on: ["category_id"]
46
- }
47
- }) }
48
- end
49
-
50
- context "when given a cache column override" do
51
- let(:flatten_value_options) { {category: :name, as: :cat_name} }
52
- it { should eql({
53
- "cat_name" => {
54
- association_name: :category,
55
- association_method: :name,
56
- changed_on: ["category_id"]
57
- }
58
- }) }
59
- end
60
-
61
- context "when set using Strings" do
62
- let(:flatten_value_options) { {'category' => 'name', 'as' => 'cat_name'} }
63
- it { should eql({
64
- "cat_name" => {
65
- association_name: :category,
66
- association_method: :name,
67
- changed_on: ["category_id"]
68
- }
69
- }) }
70
- end
71
-
72
- context "when set by association name and invalid attribute value" do
73
- let(:flatten_value_options) { {category: :bogative} }
74
- it { should be_empty }
75
- end
76
-
77
- end
78
-
79
- context "with a belongs_to association having non-standard primary and foreign keys" do
80
-
81
- context "when set by association name and attribute value" do
82
- let(:flatten_value_options) { {person: :email} }
83
- it { should eql({
84
- "person_email" => {
85
- association_name: :person,
86
- association_method: :email,
87
- changed_on: ["person_name"]
88
- }
89
- }) }
90
- end
91
-
92
- context "when set by association name and invalid attribute value" do
93
- let(:flatten_value_options) { {person: :bogative} }
94
- it { should be_empty }
95
- end
96
-
5
+ let(:cache_class) do
6
+ class ::ValueCacheHarness < Note
7
+ include Flattery::ValueCache
97
8
  end
9
+ ValueCacheHarness
10
+ end
98
11
 
12
+ subject { cache_class }
99
13
 
100
- end
14
+ its(:included_modules) { should include(Flattery::ValueCache) }
15
+ its(:value_cache_options) { should be_a(Flattery::ValueCache::Settings) }
101
16
 
102
- describe "#resolve_value_cache" do
17
+ describe "#before_save" do
18
+ let(:processor_class) { Flattery::ValueCache::Processor }
103
19
  it "should be called when record created" do
104
- resource_class.any_instance.should_receive(:resolve_value_cache).and_return(true)
105
- resource_class.create!
20
+ processor_class.any_instance.should_receive(:before_save).and_return(true)
21
+ subject.create!
106
22
  end
107
23
  it "should be called when record updated" do
108
- instance = resource_class.create!
109
- instance.should_receive(:resolve_value_cache).and_return(true)
24
+ instance = subject.create!
25
+ processor_class.any_instance.should_receive(:before_save).and_return(true)
110
26
  instance.save
111
27
  end
112
28
  end
113
29
 
114
- describe "#before_save" do
115
- let!(:resource) { resource_class.create }
116
-
117
- context "with simple belongs_to associations with cached values" do
118
- before { resource_class.flatten_value category: :name }
119
- let!(:category) { Category.create(name: 'category_a') }
120
-
121
- context "when association is changed by id" do
122
- it "should cache the new value" do
123
- expect {
124
- resource.update_attributes(category_id: category.id)
125
- }.to change {
126
- resource.category_name
127
- }.from(nil).to(category.name)
128
- end
129
- end
130
- context "when already set" do
131
- before { resource.update_attributes(category_id: category.id) }
132
- context "then set to nil" do
133
- it "should cache the new value" do
134
- expect {
135
- resource.update_attributes(category_id: nil)
136
- }.to change {
137
- resource.category_name
138
- }.from(category.name).to(nil)
139
- end
140
- end
141
- context "and associated record is destroyed" do
142
- before { category.destroy }
143
- it "should not recache the value when other values updated" do
144
- expect {
145
- resource.update_attributes(name: 'a new name')
146
- }.to_not change {
147
- resource.category_name
148
- }.from(category.name)
149
- end
150
- end
151
- end
152
-
153
- end
154
- end
155
-
156
30
  end
@@ -0,0 +1,130 @@
1
+ require 'spec_helper.rb'
2
+
3
+ # Test caching in a range of actual scenarios
4
+ describe Flattery::ValueProvider::Processor do
5
+
6
+ context "with provider having simple has_many association and explicit cache_column name" do
7
+ let(:provider_class) do
8
+ class ::ValueProviderHarness < Category
9
+ include Flattery::ValueProvider
10
+ push_flattened_values_for name: :notes, as: :category_name
11
+ end
12
+ ValueProviderHarness
13
+ end
14
+
15
+ let(:cache_class) do
16
+ class ::ValueCacheHarness < Note
17
+ include Flattery::ValueCache
18
+ flatten_value category: :name
19
+ end
20
+ ValueCacheHarness
21
+ end
22
+
23
+ let!(:resource) { provider_class.create(name: 'category_a') }
24
+ let!(:target_a) { cache_class.create(category_id: resource.id) }
25
+ let!(:target_other_a) { cache_class.create }
26
+ context "when cached value is updated" do
27
+ it "should push the new cache value" do
28
+ expect {
29
+ resource.update_attributes(name: 'new category name')
30
+ }.to change {
31
+ target_a.reload.category_name
32
+ }.from('category_a').to('new category name')
33
+ end
34
+ end
35
+ end
36
+
37
+ context "with provider that cannot correctly infer the cache column name" do
38
+ let(:provider_class) do
39
+ class ::ValueProviderHarness < Category
40
+ include Flattery::ValueProvider
41
+ push_flattened_values_for name: :notes
42
+ end
43
+ ValueProviderHarness
44
+ end
45
+
46
+ let(:cache_class) do
47
+ class ::ValueCacheHarness < Note
48
+ include Flattery::ValueCache
49
+ flatten_value category: :name
50
+ end
51
+ ValueCacheHarness
52
+ end
53
+
54
+ let!(:resource) { provider_class.create(name: 'category_a') }
55
+ let!(:target_a) { cache_class.create(category_id: resource.id) }
56
+ let!(:target_other_a) { cache_class.create }
57
+ context "when cached value is updated" do
58
+ it "should push the new cache value" do
59
+ expect {
60
+ resource.update_attributes(name: 'new category name')
61
+ }.to raise_error(Flattery::CacheColumnInflectionError)
62
+ end
63
+ end
64
+ end
65
+
66
+ context "with provider having has_many association with cache name inflected via inverse relation" do
67
+ let(:provider_class) do
68
+ class ::ValueProviderHarness < Person
69
+ include Flattery::ValueProvider
70
+ has_many :harness_notes, class_name: 'NoteTestHarness', primary_key: "username", foreign_key: "person_name", inverse_of: :person
71
+ push_flattened_values_for email: :notes
72
+ end
73
+ ValueProviderHarness
74
+ end
75
+
76
+ let(:cache_class) do
77
+ class ::ValueCacheHarness < Note
78
+ include Flattery::ValueCache
79
+ flatten_value person: :email
80
+ end
81
+ ValueCacheHarness
82
+ end
83
+
84
+ let!(:resource) { provider_class.create(username: 'user_a', email: 'email1') }
85
+ let!(:target_a) { cache_class.create(person_name: resource.username) }
86
+ let!(:target_other_a) { cache_class.create }
87
+ context "when cached value is updated" do
88
+ it "should push the new cache value" do
89
+ expect {
90
+ resource.update_attributes(email: 'email2')
91
+ }.to change {
92
+ target_a.reload.person_email
93
+ }.from('email1').to('email2')
94
+ end
95
+ end
96
+ end
97
+
98
+ context "with provider having has_many association with cache name inflected via inverse relation with custom cache column name" do
99
+ let(:provider_class) do
100
+ class ::ValueProviderHarness < Person
101
+ include Flattery::ValueProvider
102
+ has_many :harness_notes, class_name: 'ValueCacheHarness', primary_key: "username", foreign_key: "person_name", inverse_of: :person
103
+ push_flattened_values_for email: :harness_notes
104
+ end
105
+ ValueProviderHarness
106
+ end
107
+
108
+ let(:cache_class) do
109
+ class ::ValueCacheHarness < Note
110
+ include Flattery::ValueCache
111
+ flatten_value person: :email, as: :user_email
112
+ end
113
+ ValueCacheHarness
114
+ end
115
+
116
+ let!(:resource) { provider_class.create(username: 'user_a', email: 'email1') }
117
+ let!(:target_a) { cache_class.create(person_name: resource.username) }
118
+ let!(:target_other_a) { cache_class.create }
119
+ context "when cached value is updated" do
120
+ it "should push the new cache value" do
121
+ expect {
122
+ resource.update_attributes(email: 'email2')
123
+ }.to change {
124
+ target_a.reload.user_email
125
+ }.from('email1').to('email2')
126
+ end
127
+ end
128
+ end
129
+
130
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe Flattery::ValueProvider::Settings do
4
+ let(:settings_class) { Flattery::ValueProvider::Settings }
5
+ let(:settings) { provider_class.value_provider_options }
6
+
7
+ subject { settings }
8
+
9
+ context "with a standard has_many association" do
10
+ let(:provider_class) do
11
+ class ::ValueProviderHarness < Category
12
+ include Flattery::ValueProvider
13
+ push_flattened_values_for name: :notes, as: :category_name
14
+ end
15
+ ValueProviderHarness
16
+ end
17
+ context "before resolution" do
18
+ it { should be_a(settings_class) }
19
+ its(:raw_settings) { should eql([
20
+ {from_entity: :name, to_entity: :notes, as: 'category_name', method: :update_all}
21
+ ]) }
22
+ its(:resolved) { should be_false }
23
+ end
24
+ context "after resolution" do
25
+ before { settings.settings }
26
+ its(:resolved) { should be_true }
27
+ its(:settings) { should eql({
28
+ "name"=>{to_entity: :notes, as: :category_name, method: :update_all}
29
+ }) }
30
+ end
31
+ end
32
+
33
+
34
+ end
@@ -1,195 +1,30 @@
1
1
  require 'spec_helper.rb'
2
2
 
3
- class FlatteryValueProviderTestHarness < Category
4
- include Flattery::ValueProvider
5
- end
6
-
7
- class NoteTestHarness < Note
8
- include Flattery::ValueCache
9
- end
10
-
11
- class CategoryTestHarness < Category
12
- include Flattery::ValueProvider
13
- end
14
-
15
- class PersonTestHarness < Person
16
- include Flattery::ValueProvider
17
- has_many :harness_notes, class_name: 'NoteTestHarness', primary_key: "username", foreign_key: "person_name", inverse_of: :person
18
- end
19
-
20
3
  describe Flattery::ValueProvider do
21
4
 
22
- let(:resource_class) { FlatteryValueProviderTestHarness }
23
- after { resource_class.value_provider_options = {} }
24
-
25
- describe "##included_modules" do
26
- subject { resource_class.included_modules }
27
- it { should include(Flattery::ValueProvider) }
28
- end
29
-
30
- describe "##value_provider_options" do
31
- before { resource_class.push_flattened_values_for push_flattened_values_for_options }
32
- subject { resource_class.value_provider_options }
33
-
34
- context "when set to empty" do
35
- let(:push_flattened_values_for_options) { {} }
36
- it { should be_empty }
5
+ let(:provider_class) do
6
+ class ::ValueProviderHarness < Category
7
+ include Flattery::ValueProvider
37
8
  end
9
+ ValueProviderHarness
10
+ end
38
11
 
39
- context "when reset with nil" do
40
- let(:push_flattened_values_for_options) { {name: :notes} }
41
- it "should clear all settings" do
42
- expect {
43
- resource_class.push_flattened_values_for nil
44
- }.to change {
45
- resource_class.value_provider_options
46
- }.to({})
47
- end
48
- end
49
-
50
- context "with simple has_many association" do
51
-
52
- context "when set by association name and attribute value" do
53
- let(:push_flattened_values_for_options) { {name: :notes} }
54
- it { should eql({
55
- "name" => {
56
- association_name: :notes,
57
- cached_attribute_name: :inflect,
58
- method: :update_all
59
- }
60
- }) }
61
- end
62
-
63
- context "when given a cache column override" do
64
- let(:push_flattened_values_for_options) { {name: :notes, as: :category_name} }
65
- it { should eql({
66
- "name" => {
67
- association_name: :notes,
68
- cached_attribute_name: :category_name,
69
- method: :update_all
70
- }
71
- }) }
72
- end
73
-
74
- context "when set by association name and invalid attribute value" do
75
- let(:push_flattened_values_for_options) { {name: :bogative} }
76
- it { should be_empty }
77
- end
12
+ subject { provider_class }
78
13
 
79
- end
80
- end
14
+ its(:included_modules) { should include(Flattery::ValueProvider) }
15
+ its(:value_provider_options) { should be_a(Flattery::ValueProvider::Settings) }
81
16
 
82
- describe "#resolve_value_provision" do
17
+ describe "#before_update" do
18
+ let(:processor_class) { Flattery::ValueProvider::Processor }
83
19
  it "should not be called when record created" do
84
- resource_class.any_instance.should_receive(:resolve_value_provision).never
85
- resource_class.create!
20
+ processor_class.any_instance.should_receive(:before_update).never
21
+ provider_class.create!
86
22
  end
87
23
  it "should be called when record updated" do
88
- instance = resource_class.create!
89
- instance.should_receive(:resolve_value_provision).and_return(true)
24
+ instance = provider_class.create!
25
+ processor_class.any_instance.should_receive(:before_update).and_return(true)
90
26
  instance.save
91
27
  end
92
28
  end
93
29
 
94
- describe "#before_update" do
95
-
96
- context "with provider having simple has_many association and explicit cache_column name" do
97
- let(:provider_class) { CategoryTestHarness }
98
- let(:cache_class) { NoteTestHarness }
99
- before do
100
- provider_class.push_flattened_values_for name: :notes, as: :category_name
101
- cache_class.flatten_value category: :name
102
- end
103
- after do
104
- provider_class.value_provider_options = {}
105
- cache_class.value_cache_options = {}
106
- end
107
- let!(:resource) { provider_class.create(name: 'category_a') }
108
- let!(:target_a) { cache_class.create(category_id: resource.id) }
109
- let!(:target_other_a) { cache_class.create }
110
- context "when cached value is updated" do
111
- it "should push the new cache value" do
112
- expect {
113
- resource.update_attributes(name: 'new category name')
114
- }.to change {
115
- target_a.reload.category_name
116
- }.from('category_a').to('new category name')
117
- end
118
- end
119
- end
120
-
121
- context "with provider that cannot correctly infer the cache column name" do
122
- let(:provider_class) { CategoryTestHarness }
123
- let(:cache_class) { NoteTestHarness }
124
- before do
125
- provider_class.push_flattened_values_for name: :notes
126
- cache_class.flatten_value category: :name
127
- end
128
- after do
129
- provider_class.value_provider_options = {}
130
- cache_class.value_cache_options = {}
131
- end
132
- let!(:resource) { provider_class.create(name: 'category_a') }
133
- let!(:target_a) { cache_class.create(category_id: resource.id) }
134
- let!(:target_other_a) { cache_class.create }
135
- context "when cached value is updated" do
136
- it "should push the new cache value" do
137
- expect {
138
- resource.update_attributes(name: 'new category name')
139
- }.to raise_error(Flattery::CacheColumnInflectionError)
140
- end
141
- end
142
- end
143
-
144
- context "with provider having has_many association with cache name inflected via inverse relation" do
145
- let(:provider_class) { PersonTestHarness }
146
- let(:cache_class) { NoteTestHarness }
147
- before do
148
- provider_class.push_flattened_values_for email: :notes
149
- cache_class.flatten_value person: :email
150
- end
151
- after do
152
- provider_class.value_provider_options = {}
153
- cache_class.value_cache_options = {}
154
- end
155
- let!(:resource) { provider_class.create(username: 'user_a', email: 'email1') }
156
- let!(:target_a) { cache_class.create(person_name: resource.username) }
157
- let!(:target_other_a) { cache_class.create }
158
- context "when cached value is updated" do
159
- it "should push the new cache value" do
160
- expect {
161
- resource.update_attributes(email: 'email2')
162
- }.to change {
163
- target_a.reload.person_email
164
- }.from('email1').to('email2')
165
- end
166
- end
167
- end
168
-
169
- context "with provider having has_many association with cache name inflected via inverse relation with custom cache column name" do
170
- let(:provider_class) { PersonTestHarness }
171
- let(:cache_class) { NoteTestHarness }
172
- before do
173
- provider_class.push_flattened_values_for email: :harness_notes
174
- cache_class.flatten_value person: :email, as: :user_email
175
- end
176
- after do
177
- provider_class.value_provider_options = {}
178
- cache_class.value_cache_options = {}
179
- end
180
- let!(:resource) { provider_class.create(username: 'user_a', email: 'email1') }
181
- let!(:target_a) { cache_class.create(person_name: resource.username) }
182
- let!(:target_other_a) { cache_class.create }
183
- context "when cached value is updated" do
184
- it "should push the new cache value" do
185
- expect {
186
- resource.update_attributes(email: 'email2')
187
- }.to change {
188
- target_a.reload.user_email
189
- }.from('email1').to('email2')
190
- end
191
- end
192
- end
193
-
194
- end
195
30
  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.2
4
+ version: 0.0.3
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-09 00:00:00.000000000 Z
12
+ date: 2013-09-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -173,13 +173,23 @@ files:
173
173
  - flattery.gemspec
174
174
  - lib/flattery.rb
175
175
  - lib/flattery/exception.rb
176
+ - lib/flattery/settings.rb
176
177
  - lib/flattery/value_cache.rb
178
+ - lib/flattery/value_cache/processor.rb
179
+ - lib/flattery/value_cache/settings.rb
177
180
  - lib/flattery/value_provider.rb
181
+ - lib/flattery/value_provider/processor.rb
182
+ - lib/flattery/value_provider/settings.rb
178
183
  - lib/flattery/version.rb
179
184
  - spec/spec_helper.rb
180
185
  - spec/support/active_record_fixtures.rb
181
186
  - spec/unit/exception_spec.rb
187
+ - spec/unit/settings_spec.rb
188
+ - spec/unit/value_cache/processor_spec.rb
189
+ - spec/unit/value_cache/settings_spec.rb
182
190
  - spec/unit/value_cache_spec.rb
191
+ - spec/unit/value_provider/processor_spec.rb
192
+ - spec/unit/value_provider/settings_spec.rb
183
193
  - spec/unit/value_provider_spec.rb
184
194
  homepage: https://github.com/evendis/flattery
185
195
  licenses:
@@ -210,5 +220,10 @@ test_files:
210
220
  - spec/spec_helper.rb
211
221
  - spec/support/active_record_fixtures.rb
212
222
  - spec/unit/exception_spec.rb
223
+ - spec/unit/settings_spec.rb
224
+ - spec/unit/value_cache/processor_spec.rb
225
+ - spec/unit/value_cache/settings_spec.rb
213
226
  - spec/unit/value_cache_spec.rb
227
+ - spec/unit/value_provider/processor_spec.rb
228
+ - spec/unit/value_provider/settings_spec.rb
214
229
  - spec/unit/value_provider_spec.rb