datamappify 0.30.0 → 0.40.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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/ERD.png +0 -0
  4. data/README.md +78 -16
  5. data/datamappify.gemspec +4 -3
  6. data/lib/datamappify/data/criteria/active_record/destroy.rb +1 -1
  7. data/lib/datamappify/data/criteria/active_record/exists.rb +2 -2
  8. data/lib/datamappify/data/criteria/active_record/transaction.rb +1 -1
  9. data/lib/datamappify/data/criteria/common.rb +21 -1
  10. data/lib/datamappify/data/criteria/relational/count.rb +1 -1
  11. data/lib/datamappify/data/criteria/relational/find.rb +1 -1
  12. data/lib/datamappify/data/criteria/relational/save.rb +1 -1
  13. data/lib/datamappify/data/criteria/sequel/destroy.rb +1 -1
  14. data/lib/datamappify/data/criteria/sequel/exists.rb +1 -1
  15. data/lib/datamappify/data/criteria/sequel/transaction.rb +1 -1
  16. data/lib/datamappify/data/mapper/attribute.rb +9 -0
  17. data/lib/datamappify/data/mapper.rb +7 -2
  18. data/lib/datamappify/data/provider/common_provider.rb +1 -1
  19. data/lib/datamappify/entity/lazy_checking.rb +12 -0
  20. data/lib/datamappify/entity.rb +4 -0
  21. data/lib/datamappify/lazy/attributes_handler.rb +123 -0
  22. data/lib/datamappify/lazy/source_attributes_walker.rb +49 -0
  23. data/lib/datamappify/lazy.rb +24 -0
  24. data/lib/datamappify/logger.rb +13 -0
  25. data/lib/datamappify/repository/lazy_checking.rb +19 -0
  26. data/lib/datamappify/repository/mapping_dsl.rb +7 -1
  27. data/lib/datamappify/repository/query_method/callbacks.rb +83 -0
  28. data/lib/datamappify/repository/query_method/count.rb +6 -1
  29. data/lib/datamappify/repository/query_method/create.rb +10 -0
  30. data/lib/datamappify/repository/query_method/destroy.rb +6 -14
  31. data/lib/datamappify/repository/query_method/exists.rb +17 -0
  32. data/lib/datamappify/repository/query_method/find.rb +12 -19
  33. data/lib/datamappify/repository/query_method/method/source_attributes_walker.rb +81 -0
  34. data/lib/datamappify/repository/query_method/method.rb +74 -25
  35. data/lib/datamappify/repository/query_method/save.rb +15 -14
  36. data/lib/datamappify/repository/query_method/update.rb +10 -0
  37. data/lib/datamappify/repository/query_methods.rb +123 -0
  38. data/lib/datamappify/repository/unit_of_work/persistent_states/object.rb +122 -0
  39. data/lib/datamappify/repository/unit_of_work/persistent_states.rb +54 -0
  40. data/lib/datamappify/repository/unit_of_work/transaction.rb +18 -0
  41. data/lib/datamappify/repository/unit_of_work.rb +1 -0
  42. data/lib/datamappify/repository.rb +16 -51
  43. data/lib/datamappify/version.rb +1 -1
  44. data/lib/datamappify.rb +3 -1
  45. data/spec/lazy_spec.rb +73 -0
  46. data/spec/repository/callbacks_spec.rb +140 -0
  47. data/spec/repository/dirty_persistence_spec.rb +44 -0
  48. data/spec/repository/dirty_tracking_spec.rb +82 -0
  49. data/spec/repository/persistence_spec.rb +41 -119
  50. data/spec/repository/transactions_spec.rb +25 -0
  51. data/spec/repository/validation_spec.rb +42 -0
  52. data/spec/repository_spec.rb +8 -6
  53. data/spec/spec_helper.rb +2 -2
  54. data/spec/support/entities/hero_user.rb +5 -0
  55. data/spec/support/repositories/callbacks_chaining_repository.rb +92 -0
  56. data/spec/support/repositories/hero_user_repository.rb +30 -0
  57. data/spec/support/shared/contexts.rb +10 -0
  58. data/spec/support/tables/sequel.rb +1 -0
  59. data/spec/unit/repository/query_method_spec.rb +55 -0
  60. metadata +57 -10
  61. data/lib/datamappify/repository/query_method/transaction.rb +0 -18
  62. data/lib/datamappify/repository/query_method.rb +0 -3
@@ -0,0 +1,122 @@
1
+ module Datamappify
2
+ module Repository
3
+ module UnitOfWork
4
+ class PersistentStates
5
+ # an object that mirrors an entity's attributes and their initial (clean) values
6
+ class Object
7
+ include ActiveModel::Dirty
8
+
9
+ # @param entity [Entity]
10
+ def initialize(entity)
11
+ @entity = entity
12
+
13
+ attributes = attributes_for(@entity)
14
+ attributes.each do |name, value|
15
+ construct_attribute(name)
16
+ set_value(name, value)
17
+ end
18
+
19
+ self.class.define_attribute_methods(attributes.keys)
20
+
21
+ mark_as_dirty if new?
22
+ end
23
+
24
+ # Updates all the attribute values according to the entity
25
+ #
26
+ # @param entity [Entity]
27
+ #
28
+ # @return [void]
29
+ def update_values(entity)
30
+ attributes_for(entity).each do |name, value|
31
+ construct_attribute(name)
32
+ send("#{name}=", value)
33
+ end
34
+ end
35
+
36
+ # Is the object new (not persisted yet)?
37
+ #
38
+ # @return [Boolean]
39
+ def new?
40
+ @entity.id.nil?
41
+ end
42
+
43
+ private
44
+
45
+ # Constructs an attribute with a getter, setter and '_changed?' method
46
+ #
47
+ # @return [void]
48
+ def construct_attribute(name)
49
+ construct_getter(name)
50
+ construct_setter(name)
51
+ construct_changed(name)
52
+ end
53
+
54
+ # Constructs an attribute getter
55
+ #
56
+ # @param name [Symbol]
57
+ #
58
+ # @return [void]
59
+ def construct_getter(name)
60
+ define_singleton_method name do
61
+ instance_variable_get "@#{name}"
62
+ end
63
+ end
64
+
65
+ # Constructs an attribute setter, the setter itself does NOT need
66
+ # to set the value as the value is never going to be used.
67
+ #
68
+ # The setter sets the `attr_will_change!` flag when necessary.
69
+ #
70
+ # @param name [Symbol]
71
+ #
72
+ # @return [void]
73
+ def construct_setter(name)
74
+ define_singleton_method "#{name}=" do |value|
75
+ send(:attribute_will_change!, name) unless send(name) == value
76
+ end
77
+ end
78
+
79
+ # Constructs the `attr_changed?` method
80
+ #
81
+ # @param name [Symbol]
82
+ #
83
+ # @return [void]
84
+ def construct_changed(name)
85
+ define_singleton_method "#{name}_changed?" do
86
+ changed_attributes.include?(name)
87
+ end
88
+ end
89
+
90
+ # Sets an attribute value by making a copy of the data
91
+ #
92
+ # @param name [Symbol]
93
+ #
94
+ # @param value [any]
95
+ #
96
+ # @return [any]
97
+ def set_value(name, value)
98
+ instance_variable_set "@#{name}", Marshal.load(Marshal.dump(value))
99
+ end
100
+
101
+ # Mark all attributes as dirty, useful for a non-persisted object
102
+ #
103
+ # @return [void]
104
+ def mark_as_dirty
105
+ attributes_for(@entity).each do |name, _|
106
+ send(:attribute_will_change!, name)
107
+ end
108
+ end
109
+
110
+ # Entity attributes, based on whether the entity is lazy loaded
111
+ #
112
+ # @param entity [Entity]
113
+ #
114
+ # @return [Hash]
115
+ def attributes_for(entity)
116
+ entity.lazy_loaded? ? entity.cached_attributes : entity.attributes
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,54 @@
1
+ require 'datamappify/repository/unit_of_work/persistent_states/object'
2
+
3
+ module Datamappify
4
+ module Repository
5
+ module UnitOfWork
6
+ # Tracks dirty entity attributes
7
+ class PersistentStates
8
+ def initialize
9
+ @pool = {}
10
+ end
11
+
12
+ # Finds or attaches an entity
13
+ #
14
+ # @param entity [Entity]
15
+ #
16
+ # @return [Entity]
17
+ def find(entity)
18
+ @pool.has_key?(entity.object_id) ? refresh(entity) : attach(entity)
19
+ end
20
+
21
+ # Refreshes the states stored for an entity
22
+ #
23
+ # @param entity [Entity]
24
+ #
25
+ # @return [Entity]
26
+ def refresh(entity)
27
+ @pool[entity.object_id].tap { |o| o.update_values(entity) }
28
+ end
29
+
30
+ # Attaches an entity
31
+ #
32
+ # @param entity [Entity]
33
+ #
34
+ # @return [Entity]
35
+ def attach(entity)
36
+ @pool[entity.object_id] = Object.new(entity)
37
+ end
38
+
39
+ # Executes a block then reattaches the entity
40
+ #
41
+ # @param entity [Entity]
42
+ #
43
+ # @return [Entity]
44
+ def update(entity, &block)
45
+ find(entity)
46
+
47
+ block.call
48
+
49
+ attach(entity)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,18 @@
1
+ module Datamappify
2
+ module Repository
3
+ module UnitOfWork
4
+ class Transaction
5
+ # @param data_mapper (see QueryMethod::Method#initialize)
6
+ #
7
+ # @yield
8
+ # queries to be performed in the transaction
9
+ #
10
+ # @return [void]
11
+ def initialize(data_mapper, &block)
12
+ data_mapper.default_provider.build_criteria(:Transaction, data_mapper.default_source_class, &block)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1 @@
1
+ Dir[Datamappify.root.join('repository/unit_of_work/*')].each { |file| require file }
@@ -1,70 +1,34 @@
1
+ require 'datamappify/repository/lazy_checking'
1
2
  require 'datamappify/repository/mapping_dsl'
2
- require 'datamappify/repository/query_method'
3
+ require 'datamappify/repository/unit_of_work'
4
+ require 'datamappify/repository/query_methods'
3
5
  require 'datamappify/data'
4
6
 
5
7
  module Datamappify
6
8
  module Repository
9
+ # @return [Data::Mapper]
10
+ attr_accessor :data_mapper
11
+
12
+ # @return [UnitOfWork::PersistentStates]
13
+ attr_reader :states
14
+
7
15
  def self.included(klass)
8
16
  klass.class_eval do
9
17
  include Singleton
10
18
  extend SingletonWrapper
11
19
 
12
- cattr_accessor :data_mapper
13
-
14
20
  self.data_mapper = Data::Mapper.new
15
21
 
22
+ include LazyChecking
16
23
  extend MappingDSL
17
- include InstanceMethods
24
+ include QueryMethods
18
25
  end
19
26
  end
20
27
 
21
- module InstanceMethods
22
- # @param id_or_ids [Integer, Array<Integer>]
23
- # an entity id or a collection of entity ids
24
- #
25
- # @return [Entity, Array<Entity>, nil]
26
- def find(id_or_ids)
27
- QueryMethod::Find.new(data_mapper, id_or_ids).result
28
- end
28
+ private
29
29
 
30
- # @param entity_or_entities [Entity, Array<Entity>]
31
- # an entity or a collection of entities
32
- #
33
- # @return [Entity, Array<Entity>, false]
34
- def save(entity_or_entities)
35
- QueryMethod::Save.new(data_mapper, entity_or_entities).result
36
- end
37
-
38
- # @param (see #save)
39
- #
40
- # @raise [Data::EntityNotSaved]
41
- #
42
- # @return [Entity, Array<Entity>]
43
- def save!(entity_or_entities)
44
- save(entity_or_entities) || raise(Data::EntityNotSaved)
45
- end
46
-
47
- # @param id_or_ids_or_entity_or_entities [Entity, Array<Entity>]
48
- # an entity or a collection of ids or entities
49
- #
50
- # @return [void, false]
51
- def destroy(id_or_ids_or_entity_or_entities)
52
- QueryMethod::Destroy.new(data_mapper, id_or_ids_or_entity_or_entities).result
53
- end
54
-
55
- # @param (see #destroy)
56
- #
57
- # @raise [Data::EntityNotDestroyed]
58
- #
59
- # @return [void]
60
- def destroy!(id_or_ids_or_entity_or_entities)
61
- destroy(id_or_ids_or_entity_or_entities) || raise(Data::EntityNotDestroyed)
62
- end
63
-
64
- # @return [Integer]
65
- def count
66
- QueryMethod::Count.new(data_mapper).result
67
- end
30
+ def initialize
31
+ @states = UnitOfWork::PersistentStates.new
68
32
  end
69
33
 
70
34
  # Wraps a ruby Singleton class so that calling `instance` is no longer necessary.
@@ -78,7 +42,8 @@ module Datamappify
78
42
  def self.extended(klass)
79
43
  class << klass
80
44
  extend Forwardable
81
- def_delegators :instance, *InstanceMethods.instance_methods
45
+ def_delegators :instance, :data_mapper, :data_mapper=, :states
46
+ def_delegators :instance, *QueryMethods.instance_methods
82
47
  end
83
48
  end
84
49
  end
@@ -1,3 +1,3 @@
1
1
  module Datamappify
2
- VERSION = '0.30.0'
2
+ VERSION = '0.40.0'
3
3
  end
data/lib/datamappify.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'active_support'
1
+ require 'active_model'
2
2
  require 'datamappify/version'
3
3
 
4
4
  module Datamappify
@@ -8,6 +8,8 @@ module Datamappify
8
8
  end
9
9
  end
10
10
 
11
+ require 'datamappify/logger'
11
12
  require 'datamappify/entity'
12
13
  require 'datamappify/data'
13
14
  require 'datamappify/repository'
15
+ require 'datamappify/lazy'
data/spec/lazy_spec.rb ADDED
@@ -0,0 +1,73 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Datamappify::Lazy do
4
+ let!(:user_repository) { HeroUserRepository }
5
+ let!(:existing_user) { user_repository.save(HeroUser.new(:first_name => 'Fred', :last_name => 'Wu', :gender => 'm')) }
6
+ let(:user) { user_repository.find(existing_user.id) }
7
+
8
+ it "eager loads default attributes" do
9
+ Datamappify::Logger.should_not_receive(:performed).with(:override_attribute, :last_name)
10
+ Datamappify::Logger.should_not_receive(:performed).with(:override_attribute, :gender)
11
+
12
+ user.id.should == 1
13
+ user.first_name.should == 'Fred'
14
+ end
15
+
16
+ describe "loader" do
17
+ before do
18
+ Datamappify::Logger.should_receive(:performed).with(:override_attribute, :last_name).once
19
+ Datamappify::Logger.should_not_receive(:performed).with(:override_attribute, :gender)
20
+ end
21
+
22
+ it "loads lazy attribute" do
23
+ user.last_name.should == 'Wu'
24
+ end
25
+
26
+ it "loads lazy attribute with eager attribute" do
27
+ user.first_name.should == 'Fred'
28
+ user.last_name.should == 'Wu'
29
+ end
30
+
31
+ it "loads lazy attribute twice with eager attribute" do
32
+ user.first_name.should == 'Fred'
33
+ user.last_name.should == 'Wu'
34
+ user.last_name.should == 'Wu'
35
+ end
36
+
37
+ it "loads lazy attributes from the same source" do
38
+ user.first_name.should == 'Fred'
39
+ user.last_name.should == 'Wu'
40
+ user.gender.should == 'm'
41
+ end
42
+ end
43
+
44
+ describe "simple setter" do
45
+ before do
46
+ Datamappify::Logger.should_not_receive(:performed).with(:override_attribute, :last_name)
47
+ end
48
+
49
+ it "doesn't need lazy loading when the attribute is being set" do
50
+ user.first_name.should == 'Fred'
51
+ user.last_name = 'Cooper'
52
+ user.last_name.should == 'Cooper'
53
+ end
54
+
55
+ it "loads only the non-set attribute" do
56
+ Datamappify::Logger.should_receive(:performed).with(:override_attribute, :gender).once
57
+
58
+ user.first_name.should == 'Fred'
59
+ user.last_name = 'Cooper'
60
+ user.last_name.should == 'Cooper'
61
+ user.gender.should == 'm'
62
+ end
63
+ end
64
+
65
+ describe "complex setter" do
66
+ it "handles complex values" do
67
+ user.first_name.should == 'Fred'
68
+ user.last_name << 'Cooper'
69
+ user.last_name.should == 'WuCooper'
70
+ end
71
+ end
72
+ end
73
+
@@ -0,0 +1,140 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "callbacks" do |query_method, *performed_callbacks|
4
+ subject { HeroUserRepository.instance }
5
+
6
+ after do
7
+ subject.send(query_method, entity)
8
+ end
9
+
10
+ all_callbacks = [
11
+ :before_create, :after_create,
12
+ :before_update, :after_update,
13
+ :before_save, :after_save,
14
+ :before_destroy, :after_destroy
15
+ ]
16
+
17
+ non_performed_callbacks = all_callbacks - performed_callbacks
18
+
19
+ it "doesn't perform callbacks on #{query_method}" do
20
+ non_performed_callbacks.each do |callback|
21
+ subject.should_not_receive(:performed).with(callback, entity)
22
+ end
23
+ end
24
+
25
+ it "performs callbacks on #{query_method} in order" do
26
+ performed_callbacks.each do |callback|
27
+ subject.should_receive(:performed).with(callback, entity).ordered
28
+ end
29
+ end
30
+ end
31
+
32
+ describe Datamappify::Repository do
33
+ let(:valid_user) { HeroUser.new(:first_name => 'Fred', :last_name => 'Wu', :gender => 'm') }
34
+ let(:invalid_user) { HeroUser.new(:first_name => 'F') }
35
+
36
+ it { valid_user.valid?.should == true }
37
+ it { invalid_user.valid?.should == false }
38
+
39
+ context "non-persisted" do
40
+ context "on valid entity" do
41
+ let(:entity) { valid_user }
42
+
43
+ it_behaves_like "callbacks", :create, :before_save, :before_create, :before_create_2, :before_create_block, :after_create, :after_save
44
+ it_behaves_like "callbacks", :update, :before_save, :before_update, :after_update, :after_save
45
+ it_behaves_like "callbacks", :save, :before_save, :before_create, :before_create_2, :before_create_block, :after_create, :after_save
46
+ end
47
+
48
+ context "on invalid entity" do
49
+ let(:entity) { invalid_user }
50
+
51
+ it_behaves_like "callbacks", :create, :before_save, :before_create, :before_create_2, :before_create_block
52
+ it_behaves_like "callbacks", :update, :before_save, :before_update
53
+ it_behaves_like "callbacks", :save, :before_save, :before_create, :before_create_2, :before_create_block
54
+ end
55
+ end
56
+
57
+ context "persisted" do
58
+ let!(:persisted_user) { HeroUserRepository.save(valid_user.dup) }
59
+
60
+ context "on valid entity" do
61
+ let(:entity) { persisted_user }
62
+
63
+ it_behaves_like "callbacks", :create, :before_save, :before_create, :before_create_2, :before_create_block, :after_create, :after_save
64
+ it_behaves_like "callbacks", :update, :before_save, :before_update, :after_update, :after_save
65
+ it_behaves_like "callbacks", :save, :before_save, :before_update, :after_update, :after_save
66
+ it_behaves_like "callbacks", :destroy, :before_destroy, :after_destroy
67
+ end
68
+
69
+ context "on invalid entity" do
70
+ let(:entity) { persisted_user.tap { |u| u.first_name = 'f' } }
71
+
72
+ it_behaves_like "callbacks", :create, :before_save, :before_create, :before_create_2, :before_create_block
73
+ it_behaves_like "callbacks", :update, :before_save, :before_update
74
+ it_behaves_like "callbacks", :save, :before_save, :before_update
75
+ it_behaves_like "callbacks", :destroy, :before_destroy, :after_destroy
76
+ end
77
+ end
78
+
79
+ describe "callbacks chaining" do
80
+ subject { repository.instance }
81
+
82
+ describe "chaining breaks before or during saving an entity" do
83
+ after do
84
+ repository.save(entity)
85
+
86
+ entity.id.should be_nil
87
+ end
88
+
89
+ context "pause at action" do
90
+ let(:entity) { invalid_user }
91
+ let(:repository) { CallbacksChainingRepository }
92
+
93
+ it do
94
+ subject.should_receive(:performed).with(:before_save_1, entity).ordered
95
+ subject.should_receive(:performed).with(:before_save_2, entity).ordered
96
+ subject.should_receive(:performed).with(:before_save_3, entity).ordered
97
+ subject.should_not_receive(:performed).with(:after_save_1, entity)
98
+ subject.should_not_receive(:performed).with(:after_save_2, entity)
99
+ subject.should_not_receive(:performed).with(:after_save_3, entity)
100
+ end
101
+ end
102
+
103
+ context "pause at before callbacks" do
104
+ let(:entity) { valid_user }
105
+ let(:repository) { CallbacksChainingPauseBeforeRepository }
106
+
107
+ it do
108
+ subject.should_receive(:performed).with(:before_save_1, entity).ordered
109
+ subject.should_receive(:performed).with(:before_save_2, entity).ordered
110
+ subject.should_not_receive(:performed).with(:before_save_3, entity)
111
+ subject.should_not_receive(:performed).with(:after_save_1, entity)
112
+ subject.should_not_receive(:performed).with(:after_save_2, entity)
113
+ subject.should_not_receive(:performed).with(:after_save_3, entity)
114
+ end
115
+ end
116
+ end
117
+
118
+ describe "chaining breaks after saving an entity" do
119
+ after do
120
+ repository.save(entity)
121
+
122
+ entity.id.should_not be_nil
123
+ end
124
+
125
+ context "pause at after callbacks" do
126
+ let(:entity) { valid_user }
127
+ let(:repository) { CallbacksChainingPauseAfterRepository }
128
+
129
+ it do
130
+ subject.should_receive(:performed).with(:before_save_1, entity).ordered
131
+ subject.should_receive(:performed).with(:before_save_2, entity).ordered
132
+ subject.should_receive(:performed).with(:before_save_3, entity).ordered
133
+ subject.should_receive(:performed).with(:after_save_1, entity).ordered
134
+ subject.should_receive(:performed).with(:after_save_2, entity).ordered
135
+ subject.should_not_receive(:performed).with(:after_save_3, entity)
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "dirty persistence" do |data_provider|
4
+ include_context "user repository", data_provider
5
+
6
+ context "#{data_provider}" do
7
+ describe "#find" do
8
+ it "finds a persisted entity" do
9
+ persisted_user = user_repository.find(existing_user.id)
10
+ persisted_user.first_name.should == existing_user.first_name
11
+ end
12
+ end
13
+
14
+ describe "#save" do
15
+ let(:create_method) { Datamappify::Repository::QueryMethod::Create }
16
+ let(:update_method) { Datamappify::Repository::QueryMethod::Update }
17
+
18
+ it "does not perform when there are no dirty attributes" do
19
+ Datamappify::Logger.should_not_receive(:performed).with(update_method)
20
+
21
+ user_repository.save(existing_user)
22
+ end
23
+
24
+ it "performs when there are dirty attributes" do
25
+ Datamappify::Logger.should_receive(:performed).with(update_method).once
26
+
27
+ existing_user.first_name = 'Dirty'
28
+ user_repository.save(existing_user)
29
+ end
30
+
31
+ it "performs when the entity is new" do
32
+ Datamappify::Logger.should_receive(:performed).with(create_method).at_least(:twice)
33
+
34
+ user_repository.save(new_valid_user)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ describe Datamappify::Repository do
41
+ DATA_PROVIDERS.each do |data_provider|
42
+ it_behaves_like "dirty persistence", data_provider
43
+ end
44
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "dirty tracking" do |data_provider|
4
+ include_context "user repository", data_provider
5
+
6
+ context "#{data_provider}" do
7
+ describe "entity attribute (simple)" do
8
+ after do
9
+ user_repository.save(existing_user)
10
+ user_repository.states.find(existing_user).first_name_changed?.should == false
11
+ end
12
+
13
+ it "clean slate" do
14
+ user_repository.states.find(existing_user).first_name_changed?.should == false
15
+ end
16
+
17
+ it "changed" do
18
+ existing_user.first_name = 'ChangedName'
19
+ user_repository.states.find(existing_user).first_name_changed?.should == true
20
+ user_repository.states.find(existing_user).last_name_changed?.should == false
21
+ end
22
+
23
+ it "not changed" do
24
+ first_name = existing_user.first_name
25
+ existing_user.first_name = 'ChangedName'
26
+ existing_user.first_name = first_name
27
+ user_repository.states.find(existing_user).first_name_changed?.should == false
28
+ user_repository.states.find(existing_user).last_name_changed?.should == false
29
+ end
30
+ end
31
+
32
+ describe "entity attribute (complex)" do
33
+ after do
34
+ user_repository.save(existing_user)
35
+ user_repository.states.find(existing_user).first_name_changed?.should == false
36
+ end
37
+
38
+ it "changed" do
39
+ existing_user.first_name << 'super'
40
+ user_repository.states.find(existing_user).first_name_changed?.should == true
41
+ user_repository.states.find(existing_user).last_name_changed?.should == false
42
+ end
43
+
44
+ it "not changed" do
45
+ first_name = existing_user.first_name.dup
46
+ existing_user.first_name << 'super'
47
+ existing_user.first_name = first_name
48
+ user_repository.states.find(existing_user).first_name_changed?.should == false
49
+ user_repository.states.find(existing_user).last_name_changed?.should == false
50
+ end
51
+ end
52
+
53
+ describe "entity" do
54
+ after do
55
+ user_repository.save(existing_user)
56
+ user_repository.states.find(existing_user).changed?.should == false
57
+ end
58
+
59
+ it "clean slate" do
60
+ user_repository.states.find(existing_user).changed?.should == false
61
+ end
62
+
63
+ it "changed" do
64
+ existing_user.first_name = 'ChangedName'
65
+ user_repository.states.find(existing_user).changed?.should == true
66
+ end
67
+
68
+ it "not changed" do
69
+ first_name = existing_user.first_name
70
+ existing_user.first_name = 'ChangedName'
71
+ existing_user.first_name = first_name
72
+ user_repository.states.find(existing_user).changed?.should == false
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ describe Datamappify::Repository do
79
+ DATA_PROVIDERS.each do |data_provider|
80
+ it_behaves_like "dirty tracking", data_provider
81
+ end
82
+ end