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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/ERD.png +0 -0
- data/README.md +78 -16
- data/datamappify.gemspec +4 -3
- data/lib/datamappify/data/criteria/active_record/destroy.rb +1 -1
- data/lib/datamappify/data/criteria/active_record/exists.rb +2 -2
- data/lib/datamappify/data/criteria/active_record/transaction.rb +1 -1
- data/lib/datamappify/data/criteria/common.rb +21 -1
- data/lib/datamappify/data/criteria/relational/count.rb +1 -1
- data/lib/datamappify/data/criteria/relational/find.rb +1 -1
- data/lib/datamappify/data/criteria/relational/save.rb +1 -1
- data/lib/datamappify/data/criteria/sequel/destroy.rb +1 -1
- data/lib/datamappify/data/criteria/sequel/exists.rb +1 -1
- data/lib/datamappify/data/criteria/sequel/transaction.rb +1 -1
- data/lib/datamappify/data/mapper/attribute.rb +9 -0
- data/lib/datamappify/data/mapper.rb +7 -2
- data/lib/datamappify/data/provider/common_provider.rb +1 -1
- data/lib/datamappify/entity/lazy_checking.rb +12 -0
- data/lib/datamappify/entity.rb +4 -0
- data/lib/datamappify/lazy/attributes_handler.rb +123 -0
- data/lib/datamappify/lazy/source_attributes_walker.rb +49 -0
- data/lib/datamappify/lazy.rb +24 -0
- data/lib/datamappify/logger.rb +13 -0
- data/lib/datamappify/repository/lazy_checking.rb +19 -0
- data/lib/datamappify/repository/mapping_dsl.rb +7 -1
- data/lib/datamappify/repository/query_method/callbacks.rb +83 -0
- data/lib/datamappify/repository/query_method/count.rb +6 -1
- data/lib/datamappify/repository/query_method/create.rb +10 -0
- data/lib/datamappify/repository/query_method/destroy.rb +6 -14
- data/lib/datamappify/repository/query_method/exists.rb +17 -0
- data/lib/datamappify/repository/query_method/find.rb +12 -19
- data/lib/datamappify/repository/query_method/method/source_attributes_walker.rb +81 -0
- data/lib/datamappify/repository/query_method/method.rb +74 -25
- data/lib/datamappify/repository/query_method/save.rb +15 -14
- data/lib/datamappify/repository/query_method/update.rb +10 -0
- data/lib/datamappify/repository/query_methods.rb +123 -0
- data/lib/datamappify/repository/unit_of_work/persistent_states/object.rb +122 -0
- data/lib/datamappify/repository/unit_of_work/persistent_states.rb +54 -0
- data/lib/datamappify/repository/unit_of_work/transaction.rb +18 -0
- data/lib/datamappify/repository/unit_of_work.rb +1 -0
- data/lib/datamappify/repository.rb +16 -51
- data/lib/datamappify/version.rb +1 -1
- data/lib/datamappify.rb +3 -1
- data/spec/lazy_spec.rb +73 -0
- data/spec/repository/callbacks_spec.rb +140 -0
- data/spec/repository/dirty_persistence_spec.rb +44 -0
- data/spec/repository/dirty_tracking_spec.rb +82 -0
- data/spec/repository/persistence_spec.rb +41 -119
- data/spec/repository/transactions_spec.rb +25 -0
- data/spec/repository/validation_spec.rb +42 -0
- data/spec/repository_spec.rb +8 -6
- data/spec/spec_helper.rb +2 -2
- data/spec/support/entities/hero_user.rb +5 -0
- data/spec/support/repositories/callbacks_chaining_repository.rb +92 -0
- data/spec/support/repositories/hero_user_repository.rb +30 -0
- data/spec/support/shared/contexts.rb +10 -0
- data/spec/support/tables/sequel.rb +1 -0
- data/spec/unit/repository/query_method_spec.rb +55 -0
- metadata +57 -10
- data/lib/datamappify/repository/query_method/transaction.rb +0 -18
- 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/
|
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
|
24
|
+
include QueryMethods
|
18
25
|
end
|
19
26
|
end
|
20
27
|
|
21
|
-
|
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
|
-
|
31
|
-
|
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,
|
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
|
data/lib/datamappify/version.rb
CHANGED
data/lib/datamappify.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
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
|