datamappify 0.60.0 → 0.70.0.beta1

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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -1
  3. data/CHANGELOG.md +20 -0
  4. data/README.md +120 -3
  5. data/datamappify.gemspec +3 -3
  6. data/lib/datamappify/data/criteria/active_record/count.rb +0 -2
  7. data/lib/datamappify/data/criteria/active_record/criteria.rb +10 -0
  8. data/lib/datamappify/data/criteria/active_record/criteria_method.rb +65 -0
  9. data/lib/datamappify/data/criteria/active_record/find.rb +10 -2
  10. data/lib/datamappify/data/criteria/active_record/find_by_key.rb +4 -2
  11. data/lib/datamappify/data/criteria/active_record/limit.rb +10 -0
  12. data/lib/datamappify/data/criteria/active_record/match.rb +40 -0
  13. data/lib/datamappify/data/criteria/active_record/order.rb +41 -0
  14. data/lib/datamappify/data/criteria/active_record/rollback.rb +13 -0
  15. data/lib/datamappify/data/criteria/active_record/save.rb +1 -4
  16. data/lib/datamappify/data/criteria/active_record/save_by_key.rb +0 -1
  17. data/lib/datamappify/data/criteria/active_record/where.rb +18 -0
  18. data/lib/datamappify/data/criteria/common.rb +5 -2
  19. data/lib/datamappify/data/criteria/concerns/update_primary_record.rb +2 -0
  20. data/lib/datamappify/data/criteria/relational/concerns/find.rb +35 -0
  21. data/lib/datamappify/data/criteria/relational/concerns/find_by_key.rb +19 -0
  22. data/lib/datamappify/data/criteria/relational/criteria.rb +29 -0
  23. data/lib/datamappify/data/criteria/relational/{find_multiple.rb → criteria_base_method.rb} +2 -28
  24. data/lib/datamappify/data/criteria/relational/criteria_method.rb +44 -0
  25. data/lib/datamappify/data/criteria/relational/limit.rb +13 -0
  26. data/lib/datamappify/data/criteria/relational/save.rb +12 -0
  27. data/lib/datamappify/data/criteria/sequel/count.rb +0 -2
  28. data/lib/datamappify/data/criteria/sequel/criteria.rb +10 -0
  29. data/lib/datamappify/data/criteria/sequel/criteria_method.rb +51 -0
  30. data/lib/datamappify/data/criteria/sequel/find.rb +10 -2
  31. data/lib/datamappify/data/criteria/sequel/find_by_key.rb +4 -2
  32. data/lib/datamappify/data/criteria/sequel/limit.rb +10 -0
  33. data/lib/datamappify/data/criteria/sequel/match.rb +22 -0
  34. data/lib/datamappify/data/criteria/sequel/order.rb +22 -0
  35. data/lib/datamappify/data/criteria/sequel/rollback.rb +13 -0
  36. data/lib/datamappify/data/criteria/sequel/save.rb +1 -4
  37. data/lib/datamappify/data/criteria/sequel/save_by_key.rb +0 -1
  38. data/lib/datamappify/data/criteria/sequel/where.rb +18 -0
  39. data/lib/datamappify/data/errors.rb +8 -9
  40. data/lib/datamappify/data/mapper/attribute.rb +13 -33
  41. data/lib/datamappify/data/mapper.rb +13 -2
  42. data/lib/datamappify/data/provider/active_record.rb +8 -6
  43. data/lib/datamappify/data/provider/common_provider.rb +2 -2
  44. data/lib/datamappify/data/provider/sequel.rb +9 -6
  45. data/lib/datamappify/entity/association/reference.rb +63 -0
  46. data/lib/datamappify/entity/association/validation.rb +59 -0
  47. data/lib/datamappify/entity/association.rb +44 -0
  48. data/lib/datamappify/entity/{active_model/compatibility.rb → compatibility/active_model.rb} +2 -2
  49. data/lib/datamappify/entity/compatibility/active_record.rb +22 -0
  50. data/lib/datamappify/entity/compatibility/association/active_record.rb +43 -0
  51. data/lib/datamappify/entity/composable/attributes.rb +8 -16
  52. data/lib/datamappify/entity.rb +8 -6
  53. data/lib/datamappify/lazy/attributes_handler.rb +1 -1
  54. data/lib/datamappify/lazy.rb +0 -1
  55. data/lib/datamappify/repository/mapping_dsl.rb +9 -0
  56. data/lib/datamappify/repository/query_method/criteria.rb +21 -0
  57. data/lib/datamappify/repository/query_method/destroy.rb +1 -1
  58. data/lib/datamappify/repository/query_method/exists.rb +1 -1
  59. data/lib/datamappify/repository/query_method/find.rb +1 -9
  60. data/lib/datamappify/repository/query_method/method/multi_result_blender.rb +24 -0
  61. data/lib/datamappify/repository/query_method/method/reference_handler.rb +60 -0
  62. data/lib/datamappify/repository/query_method/method/source_attributes_walker.rb +19 -4
  63. data/lib/datamappify/repository/query_method/method.rb +58 -17
  64. data/lib/datamappify/repository/query_method/save.rb +4 -4
  65. data/lib/datamappify/repository/query_method/where_or_match.rb +24 -0
  66. data/lib/datamappify/repository/query_methods.rb +21 -5
  67. data/lib/datamappify/repository/unit_of_work/persistent_states/object.rb +2 -2
  68. data/lib/datamappify/version.rb +1 -1
  69. data/spec/entity/{relation_spec.rb → association/reference_spec.rb} +2 -2
  70. data/spec/repository/associations/has_many_spec.rb +259 -0
  71. data/spec/repository/dirty_tracking_spec.rb +68 -57
  72. data/spec/repository/finders/all_spec.rb +29 -0
  73. data/spec/repository/finders/criteria_spec.rb +61 -0
  74. data/spec/repository/finders/different_providers_spec.rb +81 -0
  75. data/spec/repository/finders/find_spec.rb +21 -0
  76. data/spec/repository/finders/match_spec.rb +28 -0
  77. data/spec/repository/finders/where_spec.rb +18 -0
  78. data/spec/repository/reverse_mapped_spec.rb +20 -4
  79. data/spec/support/entities/group.rb +4 -0
  80. data/spec/support/entities/super_user.rb +9 -0
  81. data/spec/support/entities/user.rb +5 -0
  82. data/spec/support/entities/user_info.rb +5 -0
  83. data/spec/support/repositories/active_record/group_repository.rb +4 -0
  84. data/spec/support/repositories/active_record/super_user_repository.rb +23 -0
  85. data/spec/support/repositories/active_record/user_repository.rb +6 -4
  86. data/spec/support/repositories/sequel/group_repository.rb +4 -0
  87. data/spec/support/repositories/sequel/super_user_repository.rb +23 -0
  88. data/spec/support/repositories/sequel/user_repository.rb +6 -4
  89. data/spec/support/shared/contexts.rb +8 -1
  90. data/spec/support/shared/examples/repository/finders/where_or_match.rb +130 -0
  91. data/spec/support/tables/active_record.rb +20 -10
  92. data/spec/support/tables/sequel.rb +20 -7
  93. data/spec/unit/entity/{relation_spec.rb → association/reference_spec.rb} +5 -5
  94. data/spec/unit/repository/query_method_spec.rb +9 -2
  95. metadata +74 -42
  96. data/lib/datamappify/data/criteria/active_record/find_multiple.rb +0 -40
  97. data/lib/datamappify/data/criteria/relational/find.rb +0 -25
  98. data/lib/datamappify/data/criteria/relational/find_by_key.rb +0 -18
  99. data/lib/datamappify/data/criteria/sequel/find_multiple.rb +0 -43
  100. data/lib/datamappify/entity/relation.rb +0 -61
  101. data/lib/datamappify/repository/query_method/find_multiple.rb +0 -28
  102. data/spec/repository/finders_spec.rb +0 -211
@@ -0,0 +1,59 @@
1
+ module Datamappify
2
+ module Entity
3
+ module Association
4
+ # Passes referenced validation errors onto the main entity
5
+ module Validation
6
+ def self.included(klass)
7
+ klass.class_eval do
8
+ attr_accessor :referenced_errors
9
+
10
+ validate :association_validation
11
+
12
+ def referenced_errors
13
+ @referenced_errors ||= {}
14
+ end
15
+ end
16
+ end
17
+
18
+ # @param context [Symbol]
19
+ # e.g. :create or :update
20
+ #
21
+ # @return [Boolean]
22
+ def valid?(context = nil)
23
+ aggregate_validation_errors(context)
24
+
25
+ super
26
+ end
27
+
28
+ private
29
+
30
+ # @param (see #valid?)
31
+ #
32
+ # @return [void]
33
+ def aggregate_validation_errors(context)
34
+ self.associations.each do |association|
35
+ self.send(association).reject(&:destroy?).each do |entity|
36
+ entity.invalid?(context) && referenced_entity_validation_errors(entity)
37
+ end
38
+ end
39
+ end
40
+
41
+ # @param (see #valid?)
42
+ #
43
+ # @return [void]
44
+ def referenced_entity_validation_errors(entity)
45
+ entity.errors.messages.each do |attr, msgs|
46
+ self.referenced_errors["#{entity.class.model_name.element}__#{attr}"] = msgs
47
+ end
48
+ end
49
+
50
+ # @return [void]
51
+ def association_validation
52
+ self.referenced_errors.each do |name, msgs|
53
+ self.errors.add name, msgs.join(', ')
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,44 @@
1
+ require 'datamappify/entity/compatibility/association/active_record'
2
+ require 'datamappify/entity/association/reference'
3
+ require 'datamappify/entity/association/validation'
4
+
5
+ module Datamappify
6
+ module Entity
7
+ module Association
8
+ def self.included(klass)
9
+ klass.class_eval do
10
+ cattr_accessor :associations
11
+ self.associations = []
12
+
13
+ include Reference
14
+ include Validation
15
+ extend DSL
16
+ end
17
+ end
18
+
19
+ module DSL
20
+ prepend Compatibility::Association::ActiveRecord
21
+
22
+ # @param name [Symbol, String]
23
+ #
24
+ # @param options [Hash]
25
+ #
26
+ # @return [void]
27
+ def belongs_to(name, options = {})
28
+ references name
29
+ end
30
+
31
+ # @param name [Symbol, String]
32
+ #
33
+ # @param options [Hash]
34
+ #
35
+ # @return [void]
36
+ def has_many(name, options = {})
37
+ attribute name, Array[options[:via]]
38
+
39
+ self.associations << name
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,8 +1,8 @@
1
1
  module Datamappify
2
2
  module Entity
3
- module ActiveModel
3
+ module Compatibility
4
4
  # Add non-entity related stuff so that an entity can be used in say, forms
5
- module Compatibility
5
+ module ActiveModel
6
6
  # @return [Boolean]
7
7
  def persisted?
8
8
  !id.blank?
@@ -0,0 +1,22 @@
1
+ module Datamappify
2
+ module Entity
3
+ module Compatibility
4
+ # Add non-entity related stuff so that an entity can be used in say, forms
5
+ module ActiveRecord
6
+ # @return [Array<Symbol>]
7
+ IGNORED_ATTRIBUTE_NAMES = [:_destroy]
8
+
9
+ def self.included(klass)
10
+ klass.class_eval do
11
+ attribute :_destroy, Virtus::Attribute::Boolean, :default => false
12
+ end
13
+ end
14
+
15
+ # @return [Boolean]
16
+ def destroy?
17
+ !!_destroy
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ module Datamappify
2
+ module Entity
3
+ module Compatibility
4
+ module Association
5
+ # Compatibility layer for ActionRecord
6
+ module ActiveRecord
7
+ # Adds `association_name_attributes` as required by
8
+ # nested attributes assignment
9
+ #
10
+ # @param (see DSL#has_many)
11
+ def has_many(name, options = {})
12
+ super
13
+
14
+ self.class_eval <<-CODE, __FILE__, __LINE__ + 1
15
+ def #{name}_attributes=(params = {})
16
+ new_entites = params.map do |index, attributes|
17
+ attributes.delete('id') if attributes['id'].blank?
18
+ #{options[:via]}.new(attributes)
19
+ end
20
+
21
+ # resets the association attribute if nil,
22
+ # this will reset it back to an empty array
23
+ self.#{name} ||= nil
24
+
25
+ self.#{name} = self.#{name}.map do |entity|
26
+ new_entites.detect { |e| e.id == entity.id } || entity
27
+ end
28
+
29
+ new_entites.each do |entity|
30
+ entity_is_already_added = entity.id && self.#{name}.map(&:id).include?(entity.id)
31
+
32
+ unless entity_is_already_added
33
+ self.#{name} << entity
34
+ end
35
+ end
36
+ end
37
+ CODE
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -17,35 +17,27 @@ module Datamappify
17
17
  def build_attributes
18
18
  @entity_class.attribute_set.each do |attribute|
19
19
  unless excluded_attributes.include?(attribute.name)
20
- @entity.attribute_set << build_attribute(attribute.name, attribute.writer)
20
+ @entity.attribute_set << build_attribute(attribute)
21
21
  end
22
22
  end
23
23
  end
24
24
 
25
25
  # @return [Array]
26
26
  def excluded_attributes
27
- @excluded_attributes ||= @entity_class.reference_keys << :id
27
+ @excluded_attributes ||= @entity_class.reference_keys + [:id, *@entity_class::IGNORED_ATTRIBUTE_NAMES]
28
28
  end
29
29
 
30
- # @param attribute_name [Symbol]
31
- #
32
- # @param attribute_writer [Virtus::Attribute::Writer]
30
+ # @param attribute [Virtus::Attribute]
33
31
  #
34
32
  # @return [Virtus::Attribute]
35
- def build_attribute(attribute_name, attribute_writer)
36
- name = if @options[:prefix_with]
37
- Attribute.prefix(attribute_name, @options[:prefix_with])
33
+ def build_attribute(attribute)
34
+ attribute_name = if @options[:prefix_with]
35
+ Attribute.prefix(attribute.name, @options[:prefix_with])
38
36
  else
39
- attribute_name
37
+ attribute.name
40
38
  end
41
39
 
42
- Virtus::Attribute.build(
43
- name,
44
- attribute_writer.primitive,
45
- :default => attribute_writer.default_value,
46
- :visibility => attribute_writer.visibility,
47
- :coercer => attribute_writer.coercer
48
- )
40
+ Virtus::Attribute.build(attribute.type, attribute.options.merge(:name => attribute_name))
49
41
  end
50
42
  end
51
43
  end
@@ -1,9 +1,10 @@
1
1
  require 'observer'
2
2
  require 'virtus'
3
- require 'datamappify/entity/active_model/compatibility'
3
+ require 'datamappify/entity/compatibility/active_model'
4
+ require 'datamappify/entity/compatibility/active_record'
4
5
  require 'datamappify/entity/lazy_checking'
5
6
  require 'datamappify/entity/composable'
6
- require 'datamappify/entity/relation'
7
+ require 'datamappify/entity/association'
7
8
  require 'datamappify/entity/inspectable'
8
9
 
9
10
  module Datamappify
@@ -11,13 +12,14 @@ module Datamappify
11
12
  def self.included(klass)
12
13
  klass.class_eval do
13
14
  include Observable
15
+ include Virtus.model
16
+ include Equalizer.new(*klass.attribute_set)
14
17
  include ::ActiveModel::Model
15
- include ActiveModel::Compatibility
16
- include Virtus
17
- include Virtus::Equalizer.new(inspect)
18
+ include Compatibility::ActiveModel
19
+ include Compatibility::ActiveRecord
18
20
  include LazyChecking
19
21
  include Composable
20
- include Relation
22
+ include Association
21
23
  include Inspectable
22
24
 
23
25
  attribute :id, Integer
@@ -8,7 +8,7 @@ module Datamappify
8
8
  # @param entity [Entity]
9
9
  def initialize(entity)
10
10
  @entity = entity
11
- @uncached_attributes = entity.attributes.keys
11
+ @uncached_attributes = entity.attributes.keys - entity.class::IGNORED_ATTRIBUTE_NAMES
12
12
 
13
13
  entity.add_observer(self)
14
14
  end
@@ -1,5 +1,4 @@
1
1
  require 'datamappify/lazy/source_attributes_walker'
2
- require 'datamappify/lazy/source_attributes_walker'
3
2
  require 'datamappify/lazy/attributes_handler'
4
3
 
5
4
  module Datamappify
@@ -43,6 +43,15 @@ module Datamappify
43
43
 
44
44
  self.current_group_options = {}
45
45
  end
46
+
47
+ # @param name [Symbol, String]
48
+ #
49
+ # @param options [Hash]
50
+ #
51
+ # @return [void]
52
+ def references(name, options = {})
53
+ data_mapper.references[name.to_sym] = options
54
+ end
46
55
  end
47
56
  end
48
57
  end
@@ -0,0 +1,21 @@
1
+ module Datamappify
2
+ module Repository
3
+ module QueryMethod
4
+ class Criteria < Method
5
+ # @return [Array<Entity>]
6
+ def perform
7
+ results = dispatch_criteria_to_default_source(
8
+ :Criteria, data_mapper.entity_class, criteria, data_mapper.attributes
9
+ )
10
+
11
+ MultiResultBlender.new(self).blend(results)
12
+ end
13
+
14
+ # @see Method#reader?
15
+ def reader?
16
+ true
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -4,7 +4,7 @@ module Datamappify
4
4
  class Destroy < Method
5
5
  # @return [void, false]
6
6
  def perform
7
- dispatch_criteria_to_default_source(:Destroy, @entity.id)
7
+ dispatch_criteria_to_default_source(:Destroy, entity.id)
8
8
  end
9
9
 
10
10
  # @see Method#writer?
@@ -4,7 +4,7 @@ module Datamappify
4
4
  class Exists < Method
5
5
  # @return [Boolean]
6
6
  def perform
7
- dispatch_criteria_to_default_source(:Exists, @entity)
7
+ dispatch_criteria_to_default_source(:Exists, entity)
8
8
  end
9
9
 
10
10
  # @see Method#reader?
@@ -2,18 +2,10 @@ module Datamappify
2
2
  module Repository
3
3
  module QueryMethod
4
4
  class Find < Method
5
- # @param options (see Method#initialize)
6
- #
7
- # @param id [Integer]
8
- def initialize(options, id)
9
- super
10
- @id = id
11
- end
12
-
13
5
  # @return [Entity, nil]
14
6
  def perform
15
7
  entity = data_mapper.entity_class.new
16
- entity.id = @id
8
+ entity.id = criteria
17
9
 
18
10
  if dispatch_criteria_to_default_source(:Exists, entity)
19
11
  dispatch_criteria_to_providers(:FindByKey, entity)
@@ -0,0 +1,24 @@
1
+ module Datamappify
2
+ module Repository
3
+ module QueryMethod
4
+ class Method
5
+ class MultiResultBlender
6
+ # @param method_instance [Method]
7
+ def initialize(method_instance)
8
+ @method = method_instance
9
+ end
10
+
11
+ # @param results [Array<Entity>]
12
+ #
13
+ # @return [Array<Entity>]
14
+ def blend(results)
15
+ results.each do |entity|
16
+ @method.states.find(entity)
17
+ @method.walk_references(:Find, entity)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,60 @@
1
+ module Datamappify
2
+ module Repository
3
+ module QueryMethod
4
+ class Method
5
+ class ReferenceHandler
6
+ def initialize(options = {})
7
+ @entity = options[:entity]
8
+ @criteria_name = options[:criteria_name]
9
+ @reference_name = options[:reference_name]
10
+ @repository = options[:reference_options][:via]
11
+ @query_method = options[:query_method]
12
+ end
13
+
14
+ # @return [void]
15
+ def execute
16
+ perform_read if @query_method.reader?
17
+ perform_write if @query_method.writer?
18
+ end
19
+
20
+ private
21
+
22
+ # @return [void]
23
+ def perform_read
24
+ @entity.send("#{@reference_name}=", fetch_referenced_entities)
25
+ end
26
+
27
+ # @return [void]
28
+ def perform_write
29
+ referenced_entities.each do |entity|
30
+ if entity.destroy?
31
+ if entity.persisted?
32
+ @repository.destroy!(entity)
33
+ end
34
+ else
35
+ entity.send("#{reference_key}=", @entity.id)
36
+ @repository.states.mark_as_dirty(entity)
37
+ @repository.save!(entity)
38
+ end
39
+ end
40
+ end
41
+
42
+ # @return [Array]
43
+ def fetch_referenced_entities
44
+ @repository.where(reference_key => @entity.id)
45
+ end
46
+
47
+ # @return [Array]
48
+ def referenced_entities
49
+ @entity.send(@reference_name)
50
+ end
51
+
52
+ # @return (see Data::Mapper::Attribute#reference_key)
53
+ def reference_key
54
+ @query_method.data_mapper.attributes.first.reference_key
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -27,16 +27,31 @@ module Datamappify
27
27
  #
28
28
  # @return [void]
29
29
  def execute(&block)
30
- @attributes.classify(&:source_class).each do |source_class, attributes|
31
- if do_walk?(source_class, attributes)
32
- block.call(@provider_name, source_class, attributes)
33
- walk_performed(attributes)
30
+ if @query_method && @query_method.writer?
31
+ @attributes.classify(&:source_class).each do |source_class, attributes|
32
+ perform_walk(source_class, attributes, &block)
34
33
  end
34
+ else
35
+ perform_walk(@attributes.first.source_class, @attributes, &block)
35
36
  end
36
37
  end
37
38
 
38
39
  private
39
40
 
41
+ # @param source_class [Class]
42
+ #
43
+ # @param attributes [Set<Data::Mapper::Attribute>]
44
+ #
45
+ # @yield (see #execute)
46
+ #
47
+ # @return [void]
48
+ def perform_walk(source_class, attributes, &block)
49
+ if do_walk?(source_class, attributes)
50
+ block.call(@provider_name, source_class, attributes)
51
+ walk_performed(attributes)
52
+ end
53
+ end
54
+
40
55
  # Whether it is necessary to do the walk
41
56
  #
42
57
  # @param source_class [Class]
@@ -11,17 +11,24 @@ module Datamappify
11
11
  # @return [UnitOfWork::PersistentStates]
12
12
  attr_reader :states
13
13
 
14
+ # @return [Entity, Hash, nil]
15
+ attr_reader :entity
16
+
17
+ # @return [Entity, Hash, nil]
18
+ attr_reader :criteria
19
+
14
20
  # @param options [Hash]
15
21
  # a hash containing required items like data_mapper and states
16
22
  #
17
- # @param entity [Entity]
23
+ # @param entity_or_criteria [Entity, Hash]
18
24
  #
19
25
  # @param args [any]
20
- def initialize(options, entity = nil, *args)
26
+ def initialize(options, entity_or_criteria = nil, *args)
21
27
  @data_mapper = options[:data_mapper]
22
28
  @states = options[:states]
23
29
  @lazy_load = options[:lazy_load?]
24
- @entity = entity
30
+
31
+ @entity = @criteria = entity_or_criteria
25
32
  end
26
33
 
27
34
  # Should the method be aware of the dirty state?
@@ -52,8 +59,6 @@ module Datamappify
52
59
  false
53
60
  end
54
61
 
55
- private
56
-
57
62
  # Dispatches a {Criteria} according to
58
63
  # the {Data::Mapper data mapper}'s default provider and default source class
59
64
  #
@@ -74,10 +79,28 @@ module Datamappify
74
79
  #
75
80
  # @return [void]
76
81
  def dispatch_criteria_to_providers(criteria_name, entity)
82
+ UnitOfWork::Transaction.new(data_mapper) do
83
+ walk_attributes(criteria_name, entity)
84
+ walk_references(criteria_name, entity)
85
+ end
86
+ end
87
+
88
+ # Walks through the references and performs actions on them
89
+ #
90
+ # @param (see #dispatch_criteria_to_providers)
91
+ #
92
+ # @return [void]
93
+ def walk_attributes(criteria_name, entity)
77
94
  _primary_record = nil
78
95
 
79
96
  attributes_walker(entity) do |provider_name, source_class, attributes|
80
- attributes.classify { |attr| attr.options[:via] }.each do |via, attrs|
97
+ walk_path = if reader?
98
+ { nil => attributes }
99
+ else
100
+ attributes.classify { |attr| attr.options[:via] }
101
+ end
102
+
103
+ walk_path.each do |via, attrs|
81
104
  record = data_mapper.provider(provider_name).build_criteria(
82
105
  criteria_name,
83
106
  source_class,
@@ -106,20 +129,38 @@ module Datamappify
106
129
  #
107
130
  # @see SourceAttributesWalker#execute
108
131
  def attributes_walker(entity, &block)
109
- UnitOfWork::Transaction.new(data_mapper) do
110
- data_mapper.classified_attributes.each do |provider_name, attributes|
111
- source_attributes_walker.new({
112
- :entity => entity,
113
- :provider_name => provider_name,
114
- :attributes => attributes,
115
- :dirty_aware? => dirty_aware?,
116
- :dirty_attributes => states.find(entity).changed,
117
- :query_method => self
118
- }).execute(&block)
119
- end
132
+ data_mapper.classified_attributes.each do |provider_name, attributes|
133
+ source_attributes_walker.new({
134
+ :entity => entity,
135
+ :provider_name => provider_name,
136
+ :attributes => attributes,
137
+ :dirty_aware? => dirty_aware?,
138
+ :dirty_attributes => states.find(entity).changed,
139
+ :query_method => self
140
+ }).execute(&block)
141
+ end
142
+ end
143
+
144
+ # Walks through the references and performs actions on them
145
+ #
146
+ # @param (see #dispatch_criteria_to_providers)
147
+ #
148
+ # @return [void]
149
+ def walk_references(criteria_name, entity)
150
+ data_mapper.references.each do |reference_name, options|
151
+ ReferenceHandler.new({
152
+ :entity => entity,
153
+ :criteria_name => criteria_name,
154
+ :reference_name => reference_name,
155
+ :reference_options => options,
156
+ :query_method => self
157
+ }).execute
120
158
  end
121
159
  end
122
160
 
161
+ private
162
+
163
+ # return [Class]
123
164
  def source_attributes_walker
124
165
  if @lazy_load
125
166
  Lazy::SourceAttributesWalker
@@ -4,11 +4,11 @@ module Datamappify
4
4
  class Save < Method
5
5
  # @return [Entity, false]
6
6
  def perform
7
- states.update(@entity) do
8
- create_or_update(@entity)
7
+ states.update(entity) do
8
+ create_or_update(entity)
9
9
  end
10
10
 
11
- @entity
11
+ entity
12
12
  rescue Data::EntityInvalid
13
13
  false
14
14
  end
@@ -31,7 +31,7 @@ module Datamappify
31
31
  #
32
32
  # @return [Entity]
33
33
  def create_or_update(entity)
34
- raise Data::EntityInvalid.new(entity) if entity.invalid?(context)
34
+ raise Data::EntityInvalid.new(entity.errors) if entity.invalid?(context)
35
35
 
36
36
  dispatch_criteria_to_providers(:SaveByKey, entity)
37
37
 
@@ -0,0 +1,24 @@
1
+ module Datamappify
2
+ module Repository
3
+ module QueryMethod
4
+ class WhereOrMatch < Method
5
+ # @return [Array<Entity>]
6
+ def perform
7
+ results = dispatch_criteria_to_default_source(
8
+ self.class.name.demodulize.to_sym, data_mapper.entity_class, criteria, data_mapper.attributes
9
+ )
10
+
11
+ MultiResultBlender.new(self).blend(results)
12
+ end
13
+
14
+ # @see Method#reader?
15
+ def reader?
16
+ true
17
+ end
18
+ end
19
+
20
+ class Where < WhereOrMatch; end
21
+ class Match < WhereOrMatch; end
22
+ end
23
+ end
24
+ end