datamappify 0.70.0.beta1 → 0.70.0.beta2

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +55 -3
  4. data/datamappify.gemspec +4 -2
  5. data/lib/datamappify/data/criteria/active_record/limit.rb +7 -1
  6. data/lib/datamappify/data/criteria/active_record/save.rb +7 -1
  7. data/lib/datamappify/data/criteria/common.rb +4 -4
  8. data/lib/datamappify/data/criteria/relational/count.rb +4 -2
  9. data/lib/datamappify/data/criteria/sequel/limit.rb +4 -1
  10. data/lib/datamappify/data/criteria/sequel/save.rb +7 -1
  11. data/lib/datamappify/entity/association.rb +11 -0
  12. data/lib/datamappify/entity/association/validation.rb +1 -1
  13. data/lib/datamappify/entity/compatibility/active_record.rb +13 -0
  14. data/lib/datamappify/entity/compatibility/association/active_record.rb +19 -3
  15. data/lib/datamappify/entity/composable/attributes.rb +1 -1
  16. data/lib/datamappify/extensions/kaminari/collection_methods.rb +16 -0
  17. data/lib/datamappify/extensions/kaminari/criteria_processor.rb +36 -0
  18. data/lib/datamappify/lazy/attributes_handler.rb +2 -2
  19. data/lib/datamappify/repository.rb +9 -0
  20. data/lib/datamappify/repository/query_method/callbacks.rb +33 -6
  21. data/lib/datamappify/repository/query_method/count.rb +3 -1
  22. data/lib/datamappify/repository/query_method/method.rb +8 -5
  23. data/lib/datamappify/repository/query_method/method/reference_handler.rb +27 -4
  24. data/lib/datamappify/repository/query_method/method/source_attributes_walker.rb +3 -1
  25. data/lib/datamappify/repository/query_method/save.rb +1 -6
  26. data/lib/datamappify/repository/query_method/update.rb +4 -0
  27. data/lib/datamappify/repository/query_methods.rb +42 -11
  28. data/lib/datamappify/repository/unit_of_work/persistent_states/object.rb +2 -2
  29. data/lib/datamappify/version.rb +1 -1
  30. data/spec/extensions/kaminari_spec.rb +27 -0
  31. data/spec/repository/associations/has_many_spec.rb +9 -42
  32. data/spec/repository/associations/has_one_spec.rb +185 -0
  33. data/spec/repository/callbacks_spec.rb +55 -14
  34. data/spec/repository/dirty_tracking_spec.rb +0 -1
  35. data/spec/repository/finders/criteria_spec.rb +1 -0
  36. data/spec/repository/persistence_spec.rb +21 -0
  37. data/spec/repository_spec.rb +18 -2
  38. data/spec/spec_helper.rb +0 -2
  39. data/spec/support/entities/group.rb +2 -1
  40. data/spec/support/repositories/active_record/group_repository.rb +2 -1
  41. data/spec/support/repositories/hero_user_repository.rb +12 -0
  42. data/spec/support/repositories/sequel/group_repository.rb +2 -1
  43. data/spec/support/shared/contexts.rb +1 -1
  44. data/spec/support/shared/examples/data/association_data_records.rb +28 -0
  45. data/spec/unit/entity/association/reference_spec.rb +4 -4
  46. data/spec/unit/entity/compatibility/active_record_spec.rb +19 -0
  47. metadata +46 -17
  48. data/lib/datamappify/data/criteria/relational/limit.rb +0 -13
  49. data/spec/support/monkey_patches/database_cleaner.rb +0 -12
@@ -8,7 +8,10 @@ module Datamappify
8
8
  klass.class_eval do
9
9
  include Hooks
10
10
 
11
- define_hooks :before_create, :after_create,
11
+ define_hooks :before_init, :after_init,
12
+ :before_load, :after_load,
13
+ :before_find, :after_find,
14
+ :before_create, :after_create,
12
15
  :before_update, :after_update,
13
16
  :before_save, :after_save,
14
17
  :before_destroy, :after_destroy,
@@ -18,21 +21,45 @@ module Datamappify
18
21
 
19
22
  # @param entity [Entity]
20
23
  #
24
+ # @param types (see #_run_callbacks)
25
+ #
26
+ # @yield (see #_run_callbacks)
27
+ #
28
+ # @return [void]
29
+ def run_callbacks(entity, *types, &block)
30
+ _run_callbacks(false, entity, *types, &block)
31
+ end
32
+
33
+ # @param criteria [any]
34
+ #
35
+ # @param types (see #_run_callbacks)
36
+ #
37
+ # @yield (see #_run_callbacks)
38
+ #
39
+ # @return [void]
40
+ def run_callbacks_and_return_entity(criteria, *types, &block)
41
+ _run_callbacks(true, criteria, *types, &block)
42
+ end
43
+
44
+ private
45
+
46
+ # @param return_yield_value [Boolean]
47
+ #
48
+ # @param input [any]
49
+ #
21
50
  # @param types [Symbol]
22
51
  # e.g. :create, :update, :save or :destroy
23
52
  #
24
53
  # @yield callback
25
54
  #
26
55
  # @return [void]
27
- def run_callbacks(entity, *types, &block)
28
- run_hooks(types, :before, entity) &&
56
+ def _run_callbacks(return_yield_value, input, *types, &block)
57
+ run_hooks(types, :before, input) &&
29
58
  (yield_value = block.call) &&
30
- run_hooks(types.reverse, :after, entity) &&
59
+ run_hooks(types.reverse, :after, (return_yield_value ? yield_value : input)) &&
31
60
  yield_value
32
61
  end
33
62
 
34
- private
35
-
36
63
  # @param types [Array<Symbol]
37
64
  # an array of types (e.g. :create, :update, :save or :destroy)
38
65
  #
@@ -4,7 +4,9 @@ module Datamappify
4
4
  class Count < Method
5
5
  # @return [Integer]
6
6
  def perform
7
- dispatch_criteria_to_default_source(:Count)
7
+ dispatch_criteria_to_default_source(
8
+ :Count, data_mapper.entity_class, criteria, data_mapper.attributes
9
+ )
8
10
  end
9
11
 
10
12
  # @see Method#reader?
@@ -29,6 +29,8 @@ module Datamappify
29
29
  @lazy_load = options[:lazy_load?]
30
30
 
31
31
  @entity = @criteria = entity_or_criteria
32
+
33
+ @criteria.symbolize_keys! if @criteria.is_a?(Hash)
32
34
  end
33
35
 
34
36
  # Should the method be aware of the dirty state?
@@ -93,11 +95,11 @@ module Datamappify
93
95
  def walk_attributes(criteria_name, entity)
94
96
  _primary_record = nil
95
97
 
96
- attributes_walker(entity) do |provider_name, source_class, attributes|
98
+ attributes_walker(entity) do |provider_name, source_class, options|
97
99
  walk_path = if reader?
98
- { nil => attributes }
100
+ { nil => options[:attributes] }
99
101
  else
100
- attributes.classify { |attr| attr.options[:via] }
102
+ options[:attributes].classify { |attr| attr.options[:via] }
101
103
  end
102
104
 
103
105
  walk_path.each do |via, attrs|
@@ -106,8 +108,9 @@ module Datamappify
106
108
  source_class,
107
109
  entity,
108
110
  attrs,
109
- :via => via,
110
- :primary_record => _primary_record
111
+ :via => via,
112
+ :primary_record => _primary_record,
113
+ :dirty_attributes => options[:dirty_attributes]
111
114
  )
112
115
 
113
116
  _primary_record ||= record
@@ -21,7 +21,12 @@ module Datamappify
21
21
 
22
22
  # @return [void]
23
23
  def perform_read
24
- @entity.send("#{@reference_name}=", fetch_referenced_entities)
24
+ case reference_type
25
+ when :one
26
+ @entity.send("#{@reference_name}=", fetch_referenced_entity)
27
+ when :many
28
+ @entity.send("#{@reference_name}=", fetch_referenced_entities)
29
+ end
25
30
  end
26
31
 
27
32
  # @return [void]
@@ -39,14 +44,32 @@ module Datamappify
39
44
  end
40
45
  end
41
46
 
42
- # @return [Array]
47
+ # @return [Symbol]
48
+ # :one or :many
49
+ def reference_type
50
+ reference = @entity.send(:attribute_set).detect do |attr|
51
+ attr.name == @reference_name
52
+ end
53
+
54
+ case reference
55
+ when Virtus::Attribute::EmbeddedValue then :one
56
+ when Virtus::Attribute::Collection then :many
57
+ end
58
+ end
59
+
60
+ # @return [Entity]
61
+ def fetch_referenced_entity
62
+ @repository.where(reference_key => @entity.id).first
63
+ end
64
+
65
+ # @return [Array<Entity>]
43
66
  def fetch_referenced_entities
44
67
  @repository.where(reference_key => @entity.id)
45
68
  end
46
69
 
47
- # @return [Array]
70
+ # @return [Array<Entity>]
48
71
  def referenced_entities
49
- @entity.send(@reference_name)
72
+ Array.wrap(@entity.send(@reference_name))
50
73
  end
51
74
 
52
75
  # @return (see Data::Mapper::Attribute#reference_key)
@@ -12,6 +12,8 @@ module Datamappify
12
12
  @dirty_aware = options[:dirty_aware?]
13
13
  @dirty_attributes = options[:dirty_attributes]
14
14
  @query_method = options[:query_method]
15
+
16
+ @options = options
15
17
  end
16
18
 
17
19
  # @yield [provider_name, source_class, attributes]
@@ -47,7 +49,7 @@ module Datamappify
47
49
  # @return [void]
48
50
  def perform_walk(source_class, attributes, &block)
49
51
  if do_walk?(source_class, attributes)
50
- block.call(@provider_name, source_class, attributes)
52
+ block.call(@provider_name, source_class, @options.merge(:attributes => attributes))
51
53
  walk_performed(attributes)
52
54
  end
53
55
  end
@@ -13,11 +13,6 @@ module Datamappify
13
13
  false
14
14
  end
15
15
 
16
- # @see Method#dirty_aware?
17
- def dirty_aware?
18
- true
19
- end
20
-
21
16
  # @see Method#writer?
22
17
  def writer?
23
18
  true
@@ -39,7 +34,7 @@ module Datamappify
39
34
  end
40
35
 
41
36
  def context
42
- self.class.name.demodulize.underscore.to_sym
37
+ self.class.name.demodulize.downcase.to_sym
43
38
  end
44
39
  end
45
40
  end
@@ -4,6 +4,10 @@ module Datamappify
4
4
  module Repository
5
5
  module QueryMethod
6
6
  class Update < Save
7
+ # @see Method#dirty_aware?
8
+ def dirty_aware?
9
+ true
10
+ end
7
11
  end
8
12
  end
9
13
  end
@@ -8,6 +8,22 @@ module Datamappify
8
8
  def self.included(klass)
9
9
  klass.class_eval do
10
10
  include QueryMethod::Callbacks
11
+
12
+ if defined?(Kaminari)
13
+ require 'datamappify/extensions/kaminari/criteria_processor'
14
+ include Extensions::Kaminari::CriteriaProcessor
15
+ end
16
+ end
17
+ end
18
+
19
+ # Initialises the entity so hooks can be attached
20
+ #
21
+ # @param entity_class [Class]
22
+ #
23
+ # @return [Entity]
24
+ def init(entity_class)
25
+ run_callbacks_and_return_entity entity_class, :init do
26
+ entity_class.new
11
27
  end
12
28
  end
13
29
 
@@ -17,7 +33,9 @@ module Datamappify
17
33
  #
18
34
  # @return [Boolean]
19
35
  def exists?(entity)
20
- QueryMethod::Exists.new(query_options, entity).perform
36
+ run_callbacks entity, :load, :find do
37
+ QueryMethod::Exists.new(query_options, entity).perform
38
+ end
21
39
  end
22
40
 
23
41
  # @param criteria [Integer, String]
@@ -25,7 +43,9 @@ module Datamappify
25
43
  #
26
44
  # @return [Entity, nil]
27
45
  def find(criteria)
28
- QueryMethod::Find.new(query_options, criteria).perform
46
+ run_callbacks_and_return_entity criteria, :load, :find do
47
+ QueryMethod::Find.new(query_options, criteria).perform
48
+ end
29
49
  end
30
50
 
31
51
  # @param criteria [Hash]
@@ -33,7 +53,9 @@ module Datamappify
33
53
  #
34
54
  # @return [Entity]
35
55
  def where(criteria)
36
- QueryMethod::Where.new(query_options, criteria).perform
56
+ QueryMethod::Where.new(query_options, criteria).perform.map do |entity|
57
+ run_callbacks(entity, :load, :find) { entity }
58
+ end
37
59
  end
38
60
 
39
61
  # @param criteria [Hash]
@@ -41,14 +63,18 @@ module Datamappify
41
63
  #
42
64
  # @return [Entity]
43
65
  def match(criteria)
44
- QueryMethod::Match.new(query_options, criteria).perform
66
+ QueryMethod::Match.new(query_options, criteria).perform.map do |entity|
67
+ run_callbacks(entity, :load, :find) { entity }
68
+ end
45
69
  end
46
70
 
47
71
  # Returns a collection of all the entities in the repository
48
72
  #
49
73
  # @return [Array<Entity>]
50
74
  def all
51
- QueryMethod::Where.new(query_options, {}).perform
75
+ QueryMethod::Where.new(query_options, {}).perform.map do |entity|
76
+ run_callbacks(entity, :load, :find) { entity }
77
+ end
52
78
  end
53
79
 
54
80
  # @param criteria [Hash]
@@ -56,7 +82,9 @@ module Datamappify
56
82
  #
57
83
  # @return [Array<Entity>]
58
84
  def criteria(criteria)
59
- QueryMethod::Criteria.new(query_options, criteria).perform
85
+ QueryMethod::Criteria.new(query_options, criteria).perform.map do |entity|
86
+ run_callbacks(entity, :load, :find) { entity }
87
+ end
60
88
  end
61
89
 
62
90
  # @param entity [Entity]
@@ -64,7 +92,7 @@ module Datamappify
64
92
  #
65
93
  # @return [Entity, false]
66
94
  def create(entity)
67
- run_callbacks entity, :save, :create do
95
+ run_callbacks entity, :load, :save, :create do
68
96
  QueryMethod::Create.new(query_options, entity).perform
69
97
  end
70
98
  end
@@ -83,7 +111,7 @@ module Datamappify
83
111
  #
84
112
  # @return [Entity, false]
85
113
  def update(entity)
86
- run_callbacks entity, :save, :update do
114
+ run_callbacks entity, :load, :save, :update do
87
115
  QueryMethod::Update.new(query_options, entity).perform
88
116
  end
89
117
  end
@@ -118,7 +146,7 @@ module Datamappify
118
146
  #
119
147
  # @return [void, false]
120
148
  def destroy(entity)
121
- run_callbacks entity, :destroy do
149
+ run_callbacks entity, :load, :destroy do
122
150
  QueryMethod::Destroy.new(query_options, entity).perform
123
151
  end
124
152
  end
@@ -132,9 +160,12 @@ module Datamappify
132
160
  destroy(entity) || raise(Data::EntityNotDestroyed.new(entity.errors))
133
161
  end
134
162
 
163
+ # @param criteria [Hash]
164
+ # a hash containing criteria
165
+ #
135
166
  # @return [Integer]
136
- def count
137
- QueryMethod::Count.new(query_options).perform
167
+ def count(criteria = {})
168
+ QueryMethod::Count.new(query_options, criteria).perform
138
169
  end
139
170
 
140
171
  private
@@ -51,7 +51,7 @@ module Datamappify
51
51
  #
52
52
  # @return [Boolean]
53
53
  def new?
54
- @entity.id.nil?
54
+ !@entity.persisted?
55
55
  end
56
56
 
57
57
  private
@@ -85,7 +85,7 @@ module Datamappify
85
85
  # @return [void]
86
86
  def construct_setter(name)
87
87
  define_singleton_method "#{name}=" do |value|
88
- send(:attribute_will_change!, name) unless send(name) == value
88
+ mark_as_dirty(name) unless send(name) == value
89
89
  set_value(name, value)
90
90
  end
91
91
  end
@@ -1,3 +1,3 @@
1
1
  module Datamappify
2
- VERSION = '0.70.0.beta1'
2
+ VERSION = '0.70.0.beta2'
3
3
  end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "extensions - Kaminari" do |data_provider|
4
+ include_context "user repository for finders", data_provider
5
+
6
+ context "#{data_provider}" do
7
+ describe "pagination" do
8
+ let(:records) { user_repository.criteria(page: 2, per: 1) }
9
+ subject { records }
10
+
11
+ it { should have(1).user }
12
+
13
+ its(:current_page) { should == 2 }
14
+ its(:limit_value) { should == 1 }
15
+ its(:offset_value) { should == 1 }
16
+ its(:total_count) { should == 3 }
17
+
18
+ its(:first) { should == existing_user_1 }
19
+ end
20
+ end
21
+ end
22
+
23
+ describe Datamappify::Repository do
24
+ DATA_PROVIDERS.each do |data_provider|
25
+ it_behaves_like "extensions - Kaminari", data_provider
26
+ end
27
+ end
@@ -108,18 +108,6 @@ shared_examples_for "has_many records from nested form attributes with invalid d
108
108
  )
109
109
  end
110
110
 
111
- context "before saving" do
112
- subject { new_group }
113
-
114
- its(:name) { should == 'New People' }
115
- its(:users) { should have(3).items }
116
- its(:valid?) { should be_false }
117
-
118
- it "does not save" do
119
- -> { group_repository.save!(new_group) }.should raise_error(Datamappify::Data::EntityNotSaved)
120
- end
121
- end
122
-
123
111
  context "non-saved dirty new group" do
124
112
  before do
125
113
  group_repository.save(new_group)
@@ -130,6 +118,10 @@ shared_examples_for "has_many records from nested form attributes with invalid d
130
118
  its(:name) { should == 'New People' }
131
119
  its(:users) { should have(3).items }
132
120
  its(:valid?) { should be_false }
121
+
122
+ it "does not save" do
123
+ -> { group_repository.save!(new_group) }.should raise_error(Datamappify::Data::EntityNotSaved)
124
+ end
133
125
  end
134
126
 
135
127
  context "fresh copy of the non-saved new group" do
@@ -163,35 +155,6 @@ shared_examples_for "has_many records destroy from nested form attributes" do
163
155
  its(:users) { should have(1).item }
164
156
  end
165
157
 
166
- shared_examples_for "has_many data records" do |data_provider|
167
- let(:data_groups) { "Datamappify::Data::Record::#{data_provider}::Group".constantize }
168
- let(:data_super_users) { "Datamappify::Data::Record::#{data_provider}::SuperUser".constantize }
169
- let(:data_users) { "Datamappify::Data::Record::#{data_provider}::User".constantize }
170
- let(:data_user_driver_licenses) { "Datamappify::Data::Record::#{data_provider}::UserDriverLicense".constantize }
171
-
172
- before do
173
- saved_group
174
- end
175
-
176
- describe "data records" do
177
- it "does not create extra primary data records" do
178
- expect { saved_group }.to change { data_groups.count }.by(0)
179
- end
180
-
181
- it "does not create extra associated primary data records" do
182
- expect { saved_group }.to change { data_super_users.count }.by(0)
183
- end
184
-
185
- it "does not create extra associated secondary data records" do
186
- expect { saved_group }.to change { data_users.count }.by(0)
187
- end
188
-
189
- it "does not create extra secondary data records" do
190
- expect { saved_group }.to change { data_user_driver_licenses.count }.by(0)
191
- end
192
- end
193
- end
194
-
195
158
  shared_examples_for "has_many" do |data_provider|
196
159
  let(:user_repository) { "SuperUserRepository#{data_provider}".constantize }
197
160
  let(:group_repository) { "GroupRepository#{data_provider}".constantize }
@@ -223,25 +186,29 @@ shared_examples_for "has_many" do |data_provider|
223
186
  it_behaves_like "has_many new records created from nested form attributes"
224
187
  it_behaves_like "has_many records created from nested form attributes"
225
188
  it_behaves_like "has_many records from nested form attributes with invalid data"
226
- it_behaves_like "has_many data records", data_provider
189
+ it_behaves_like "association data records", data_provider
227
190
  end
228
191
 
229
192
  context "reloaded return" do
230
193
  let(:saved_group) { group_repository.save!(group); group_repository.find(group.id) }
231
194
 
232
195
  it_behaves_like "has_many records"
196
+ it_behaves_like "has_many new records created from nested form attributes"
233
197
  it_behaves_like "has_many records created from nested form attributes"
234
198
  it_behaves_like "has_many records from nested form attributes with invalid data"
235
199
  it_behaves_like "has_many records destroy from nested form attributes"
200
+ it_behaves_like "association data records", data_provider
236
201
  end
237
202
 
238
203
  context "collection return" do
239
204
  let(:saved_group) { group_repository.save!(group); group_repository.all.detect { |g| g.id == group.id } }
240
205
 
241
206
  it_behaves_like "has_many records"
207
+ it_behaves_like "has_many new records created from nested form attributes"
242
208
  it_behaves_like "has_many records created from nested form attributes"
243
209
  it_behaves_like "has_many records from nested form attributes with invalid data"
244
210
  it_behaves_like "has_many records destroy from nested form attributes"
211
+ it_behaves_like "association data records", data_provider
245
212
  end
246
213
 
247
214
  context "criteria return" do