datamappify 0.20.1 → 0.30.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.yardopts +1 -0
  4. data/CHANGELOG.md +10 -4
  5. data/README.md +28 -33
  6. data/datamappify.gemspec +3 -1
  7. data/lib/datamappify/data/criteria/active_record/count.rb +12 -0
  8. data/lib/datamappify/data/criteria/active_record/destroy.rb +17 -0
  9. data/lib/datamappify/data/criteria/active_record/exists.rb +13 -0
  10. data/lib/datamappify/data/criteria/active_record/find.rb +12 -0
  11. data/lib/datamappify/data/criteria/active_record/find_by_key.rb +12 -0
  12. data/lib/datamappify/data/criteria/active_record/save.rb +23 -0
  13. data/lib/datamappify/data/criteria/active_record/save_by_key.rb +14 -0
  14. data/lib/datamappify/data/criteria/active_record/transaction.rb +13 -0
  15. data/lib/datamappify/data/criteria/common.rb +96 -0
  16. data/lib/datamappify/data/criteria/relational/count.rb +13 -0
  17. data/lib/datamappify/data/criteria/relational/find.rb +23 -0
  18. data/lib/datamappify/data/criteria/relational/find_by_key.rb +17 -0
  19. data/lib/datamappify/data/criteria/relational/save.rb +26 -0
  20. data/lib/datamappify/data/criteria/relational/save_by_key.rb +15 -0
  21. data/lib/datamappify/data/criteria/sequel/count.rb +12 -0
  22. data/lib/datamappify/data/criteria/sequel/destroy.rb +17 -0
  23. data/lib/datamappify/data/criteria/sequel/exists.rb +13 -0
  24. data/lib/datamappify/data/criteria/sequel/find.rb +12 -0
  25. data/lib/datamappify/data/criteria/sequel/find_by_key.rb +12 -0
  26. data/lib/datamappify/data/criteria/sequel/save.rb +23 -0
  27. data/lib/datamappify/data/criteria/sequel/save_by_key.rb +14 -0
  28. data/lib/datamappify/data/criteria/sequel/transaction.rb +13 -0
  29. data/lib/datamappify/data/criteria.rb +8 -0
  30. data/lib/datamappify/data/errors.rb +1 -0
  31. data/lib/datamappify/data/mapper/attribute.rb +52 -0
  32. data/lib/datamappify/data/mapper.rb +95 -0
  33. data/lib/datamappify/data/provider/active_record.rb +7 -7
  34. data/lib/datamappify/data/provider/common_provider.rb +67 -0
  35. data/lib/datamappify/data/provider/sequel.rb +9 -9
  36. data/lib/datamappify/data/provider.rb +12 -0
  37. data/lib/datamappify/data/record.rb +13 -0
  38. data/lib/datamappify/data.rb +4 -0
  39. data/lib/datamappify/repository/mapping_dsl.rb +28 -0
  40. data/lib/datamappify/repository/query_method/count.rb +12 -0
  41. data/lib/datamappify/repository/query_method/destroy.rb +25 -0
  42. data/lib/datamappify/repository/query_method/find.rb +41 -0
  43. data/lib/datamappify/repository/query_method/method.rb +73 -0
  44. data/lib/datamappify/repository/query_method/save.rb +42 -0
  45. data/lib/datamappify/repository/query_method/transaction.rb +18 -0
  46. data/lib/datamappify/repository/query_method.rb +3 -0
  47. data/lib/datamappify/repository.rb +58 -92
  48. data/lib/datamappify/version.rb +1 -1
  49. data/lib/datamappify.rb +10 -5
  50. data/spec/repository/persistence_spec.rb +15 -23
  51. data/spec/repository_spec.rb +9 -5
  52. metadata +70 -15
  53. data/lib/datamappify/data/provider/active_record/persistence.rb +0 -31
  54. data/lib/datamappify/data/provider/common/persistence.rb +0 -57
  55. data/lib/datamappify/data/provider/common/relational/persistence.rb +0 -85
  56. data/lib/datamappify/data/provider/common/relational/record/mapper.rb +0 -24
  57. data/lib/datamappify/data/provider/common/relational/record/writer.rb +0 -67
  58. data/lib/datamappify/data/provider/sequel/persistence.rb +0 -31
  59. data/lib/datamappify/repository/attribute_source_data_class_builder.rb +0 -28
  60. data/lib/datamappify/repository/attributes_mapper.rb +0 -55
  61. data/lib/datamappify/repository/dsl.rb +0 -22
  62. data/lib/datamappify/repository/mapping_hash.rb +0 -8
  63. data/lib/datamappify/util.rb +0 -13
@@ -0,0 +1,52 @@
1
+ module Datamappify
2
+ module Data
3
+ class Mapper
4
+ # Represents an entity attribute and its associated data source
5
+ class Attribute
6
+ # @return [String]
7
+ attr_reader :name
8
+
9
+ # @return [String]
10
+ attr_reader :provider_name
11
+
12
+ # @return [String]
13
+ attr_reader :source_class_name
14
+
15
+ # @return [String]
16
+ attr_reader :source_attribute_name
17
+
18
+ # @param name [Symbol]
19
+ # name of the attribute
20
+ #
21
+ # @param source [String]
22
+ # data provider, class and attribute,
23
+ # e.g. "ActiveRecord::User#surname"
24
+ def initialize(name, source)
25
+ @name = name.to_s
26
+
27
+ @provider_name, @source_class_name, @source_attribute_name = parse_source(source)
28
+ end
29
+
30
+ # @return [Class]
31
+ def source_class
32
+ @source_class ||= Record.find_or_build(provider_name, source_class_name)
33
+ end
34
+
35
+ # @return [Boolean]
36
+ def primary_key?
37
+ source_attribute_name == 'id'
38
+ end
39
+
40
+ private
41
+
42
+ # @return [Array<String>]
43
+ # an array with provider name, source class name and source attribute name
44
+ def parse_source(source)
45
+ provider_name, source_class_and_attribute = source.split('::')
46
+
47
+ [provider_name, *source_class_and_attribute.split('#')]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,95 @@
1
+ require 'set'
2
+ require 'datamappify/data/mapper/attribute'
3
+
4
+ module Datamappify
5
+ module Data
6
+ class Mapper
7
+ # @return [Class]
8
+ attr_accessor :entity_class
9
+
10
+ # @return [String]
11
+ attr_accessor :default_provider_name
12
+
13
+ # @return [Hash]
14
+ # attribute name to source mapping as specified in {Repository::MappingDSL#map_attribute}
15
+ attr_accessor :custom_mapping
16
+
17
+ def initialize
18
+ @custom_mapping = {}
19
+ @custom_attribute_names = []
20
+ end
21
+
22
+ # @return [Module]
23
+ def default_provider
24
+ @default_provider ||= Provider.const_get(default_provider_name)
25
+ end
26
+
27
+ # @return [Module]
28
+ def provider(provider_name)
29
+ Provider.const_get(provider_name)
30
+ end
31
+
32
+ # @return [Class]
33
+ def default_source_class
34
+ @default_source_class ||= default_provider.find_or_build_record_class(entity_class.name)
35
+ end
36
+
37
+ # @return [Hash]
38
+ # attribute sets classified by the names of their data provider
39
+ def classified_attributes
40
+ @classified_attributes ||= Set.new(custom_attributes + default_attributes).classify(&:provider_name)
41
+ end
42
+
43
+ private
44
+
45
+ # @return [Array<Symbol>]
46
+ def all_attribute_names
47
+ entity_class.attribute_set.entries.collect(&:name)
48
+ end
49
+
50
+ # @return [Array<Symbol>]
51
+ def default_attribute_names
52
+ all_attribute_names - custom_attribute_names
53
+ end
54
+
55
+ # @return [Array<Symbol>]
56
+ def custom_attribute_names
57
+ # make sure custom attributes are always processed
58
+ custom_attributes
59
+
60
+ @custom_attribute_names
61
+ end
62
+
63
+ # @return [Array<Attribute>]
64
+ def default_attributes
65
+ @default_attributes ||= default_attribute_names.collect do |attribute|
66
+ Attribute.new(attribute, default_source_for(attribute))
67
+ end
68
+ end
69
+
70
+ # @return [Array<Attribute>]
71
+ def custom_attributes
72
+ @custom_attributes ||= custom_mapping.collect do |attribute, source|
73
+ map_attribute(attribute, source)
74
+ end
75
+ end
76
+
77
+ # @param (see Data::Mapper::Attribute#initialize)
78
+ #
79
+ # @return [Attribute]
80
+ def map_attribute(name, source)
81
+ @custom_attribute_names << name
82
+
83
+ Attribute.new(name, source)
84
+ end
85
+
86
+ # @param attribute [Symbol]
87
+ # name of the attribute
88
+ #
89
+ # @return [String]
90
+ def default_source_for(attribute)
91
+ "#{default_provider_name}::#{entity_class.name}##{attribute}"
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,14 +1,14 @@
1
- require 'datamappify/data/provider/active_record/persistence'
2
-
3
1
  module Datamappify
4
2
  module Data
5
- module ActiveRecord
6
- end
7
-
8
3
  module Provider
9
4
  module ActiveRecord
10
- def self.build_data_class(data_class_name)
11
- Datamappify::Data::ActiveRecord.const_set(data_class_name, Class.new(::ActiveRecord::Base))
5
+ extend CommonProvider
6
+
7
+ # @return [ActiveRecord::Base]
8
+ def self.build_record_class(source_class_name)
9
+ Datamappify::Data::Record::ActiveRecord.const_set(
10
+ source_class_name, Class.new(::ActiveRecord::Base)
11
+ )
12
12
  end
13
13
  end
14
14
  end
@@ -0,0 +1,67 @@
1
+ module Datamappify
2
+ module Data
3
+ module Provider
4
+ module CommonProvider
5
+ def self.extended(klass)
6
+ klass.extend ModuleMethods
7
+
8
+ klass.load_criterias
9
+ end
10
+
11
+ module ModuleMethods
12
+ # Loads all the criteria files from the data provider
13
+ #
14
+ # @return [void]
15
+ def load_criterias
16
+ Dir[Datamappify.root.join("data/criteria/#{path_name}/*.rb")].each { |file| require file }
17
+ end
18
+
19
+ # Non-namespaced class name
20
+ #
21
+ # @return [String]
22
+ def class_name
23
+ @class_name ||= name.demodulize
24
+ end
25
+
26
+ # @return [String]
27
+ def path_name
28
+ @path_name ||= class_name.underscore
29
+ end
30
+
31
+ # Finds or builds a data record class from the data provider
32
+ #
33
+ # @return [Class]
34
+ # the data record class
35
+ def find_or_build_record_class(source_class_name)
36
+ if records_namespace.const_defined?(source_class_name, false)
37
+ records_namespace.const_get(source_class_name)
38
+ else
39
+ build_record_class(source_class_name)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # The namespace for the data records, e.g. +Datamappify::Data::Record::ActiveRecord+
46
+ #
47
+ # @return [Module]
48
+ def records_namespace
49
+ @records_namespace ||= Data::Record.const_set(class_name, Module.new)
50
+ end
51
+ end
52
+
53
+ # Builds a {Criteria}
54
+ #
55
+ # @param name [Symbol]
56
+ #
57
+ # @param args [any]
58
+ #
59
+ # @yield
60
+ # an optional block passed to the +Criteria+ {Criteria::Common#initialize initialiser}
61
+ def build_criteria(name, *args, &block)
62
+ Data::Criteria.const_get(class_name).const_get(name).new(*args, &block).result
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,16 +1,16 @@
1
- require 'datamappify/data/provider/sequel/persistence'
2
-
3
1
  module Datamappify
4
2
  module Data
5
- module Sequel
6
- end
7
-
8
3
  module Provider
9
4
  module Sequel
10
- def self.build_data_class(data_class_name)
11
- Datamappify::Data::Sequel.const_set(
12
- data_class_name, Class.new(::Sequel::Model(data_class_name.pluralize.underscore.to_sym))
13
- )
5
+ extend CommonProvider
6
+
7
+ # @return [Sequel::Model]
8
+ def self.build_record_class(source_class_name)
9
+ Record::Sequel.const_set(
10
+ source_class_name, Class.new(::Sequel::Model(source_class_name.pluralize.underscore.to_sym))
11
+ ).tap do |klass|
12
+ klass.raise_on_save_failure = true
13
+ end
14
14
  end
15
15
  end
16
16
  end
@@ -0,0 +1,12 @@
1
+ require 'datamappify/data/provider/common_provider'
2
+
3
+ module Datamappify
4
+ module Data
5
+ module Provider
6
+ extend ActiveSupport::Autoload
7
+
8
+ autoload :ActiveRecord
9
+ autoload :Sequel
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module Datamappify
2
+ module Data
3
+ # A convenient class for finding or building a data record
4
+ module Record
5
+ # @param provider_name [String]
6
+ #
7
+ # @param source_class_name [String]
8
+ def self.find_or_build(provider_name, source_class_name)
9
+ Provider.const_get(provider_name).find_or_build_record_class(source_class_name)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,4 +1,8 @@
1
1
  require 'datamappify/data/errors'
2
+ require 'datamappify/data/criteria'
3
+ require 'datamappify/data/mapper'
4
+ require 'datamappify/data/provider'
5
+ require 'datamappify/data/record'
2
6
 
3
7
  module Datamappify
4
8
  module Data
@@ -0,0 +1,28 @@
1
+ module Datamappify
2
+ module Repository
3
+ module MappingDSL
4
+ # @param entity_class [Class]
5
+ # entity class
6
+ #
7
+ # @return [void]
8
+ def for_entity(entity_class)
9
+ data_mapper.entity_class = entity_class
10
+ end
11
+
12
+ # @param provider_name [String]
13
+ # name of data provider
14
+ #
15
+ # @return [void]
16
+ def default_provider(provider_name)
17
+ data_mapper.default_provider_name = provider_name.to_s
18
+ end
19
+
20
+ # @param (see Data::Mapper::Attribute#initialize)
21
+ #
22
+ # @return [void]
23
+ def map_attribute(name, source)
24
+ data_mapper.custom_mapping[name] = source
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,12 @@
1
+ module Datamappify
2
+ module Repository
3
+ module QueryMethod
4
+ class Count < Method
5
+ # @return [Integer]
6
+ def result
7
+ dispatch_criteria_to_default_source(:Count)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,25 @@
1
+ module Datamappify
2
+ module Repository
3
+ module QueryMethod
4
+ class Destroy < Method
5
+ # @param mapper (see Method#initialize)
6
+ #
7
+ # @param id_or_ids_or_entity_or_entities [Entity, Array<Entity>]
8
+ # an entity or a collection of ids or entities
9
+ def initialize(mapper, id_or_ids_or_entity_or_entities)
10
+ super
11
+ @id_or_ids_or_entity_or_entities = id_or_ids_or_entity_or_entities
12
+ end
13
+
14
+ # @return [void, false]
15
+ def result
16
+ entities = Array.wrap(@id_or_ids_or_entity_or_entities).map do |id_or_entity|
17
+ dispatch_criteria_to_default_source(:Destroy, extract_entity_id(id_or_entity))
18
+ end
19
+
20
+ @id_or_ids_or_entity_or_entities.is_a?(Array) ? entities : entities[0]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ module Datamappify
2
+ module Repository
3
+ module QueryMethod
4
+ class Find < Method
5
+ # @param mapper (see Method#initialize)
6
+ #
7
+ # @param id_or_ids [Integer, Array<Integer>]
8
+ # an entity id or a collection of entity ids
9
+ def initialize(mapper, id_or_ids)
10
+ super
11
+ @id_or_ids = id_or_ids
12
+ end
13
+
14
+ # @return [Entity, Array<Entity>, nil]
15
+ def result
16
+ entities = Array.wrap(@id_or_ids).map { |id| setup_new_entity(id) }.compact
17
+
18
+ @id_or_ids.is_a?(Array) ? entities : entities[0]
19
+ end
20
+
21
+ private
22
+
23
+ # @param id [Integer]
24
+ #
25
+ # @return [Entity, nil]
26
+ def setup_new_entity(id)
27
+ entity = @mapper.entity_class.new
28
+ entity.id = id
29
+
30
+ if dispatch_criteria_to_default_source(:Exists, entity)
31
+ dispatch_criteria_to_providers(:FindByKey, entity)
32
+ else
33
+ entity = nil
34
+ end
35
+
36
+ entity
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,73 @@
1
+ module Datamappify
2
+ module Repository
3
+ module QueryMethod
4
+ # Provides a default set of methods to the varies {QueryMethod} classes
5
+ class Method
6
+ # @param mapper [Data::Mapper]
7
+ #
8
+ # @param args [any]
9
+ def initialize(mapper, *args)
10
+ @mapper = mapper
11
+ end
12
+
13
+ protected
14
+
15
+ # Dispatches a {Criteria} according to
16
+ # the {Data::Mapper data mapper}'s default provider and default source class
17
+ #
18
+ # @param criteria_name [Symbol]
19
+ #
20
+ # @param args [any]
21
+ def dispatch_criteria_to_default_source(criteria_name, *args)
22
+ @mapper.default_provider.build_criteria(criteria_name, @mapper.default_source_class, *args)
23
+ end
24
+
25
+ # Dispatches a {Criteria} via {#attributes_walker}
26
+ #
27
+ # @param criteria_name [Symbol]
28
+ #
29
+ # @param entity [Entity]
30
+ #
31
+ # @return [void]
32
+ def dispatch_criteria_to_providers(criteria_name, entity)
33
+ attributes_walker do |provider_name, source_class, attributes|
34
+ @mapper.provider(provider_name).build_criteria(
35
+ criteria_name, source_class, entity, attributes
36
+ )
37
+ end
38
+ end
39
+
40
+ # Walks through the attributes and performs actions on them
41
+ #
42
+ # @yield [provider_name, source_class, attributes]
43
+ # action to be performed on the attributes grouped by their source class
44
+ #
45
+ # @yieldparam provider_name [String]
46
+ #
47
+ # @yieldparam source_class [Class]
48
+ #
49
+ # @yieldparam attributes [Set]
50
+ #
51
+ # @return [void]
52
+ def attributes_walker(&block)
53
+ Transaction.new(@mapper) do
54
+ @mapper.classified_attributes.each do |provider_name, attributes|
55
+ attributes.classify(&:source_class).each do |source_class, attrs|
56
+ block.call(provider_name, source_class, attrs)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ # Extract the id out of an entity, unless the argument is already an id
63
+ #
64
+ # @param id_or_entity [Entity, Integer]
65
+ #
66
+ # @return [Integer]
67
+ def extract_entity_id(id_or_entity)
68
+ id_or_entity.is_a?(Integer) ? id_or_entity : id_or_entity.id
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,42 @@
1
+ module Datamappify
2
+ module Repository
3
+ module QueryMethod
4
+ class Save < Method
5
+ # @param mapper (see Method#initialize)
6
+ #
7
+ # @param entity_or_entities [Entity, Array<Entity>]
8
+ # an entity or a collection of entities
9
+ def initialize(mapper, entity_or_entities)
10
+ super
11
+ @entity_or_entities = entity_or_entities
12
+ end
13
+
14
+ # @return [Entity, Array<Entity>, false]
15
+ def result
16
+ Array.wrap(@entity_or_entities).each do |entity|
17
+ create_or_update(entity)
18
+ end
19
+
20
+ @entity_or_entities
21
+ rescue Data::EntityInvalid
22
+ false
23
+ end
24
+
25
+ private
26
+
27
+ # @param entity [Entity]
28
+ #
29
+ # @raise [Data::EntityInvalid]
30
+ #
31
+ # @return [Entity]
32
+ def create_or_update(entity)
33
+ raise Data::EntityInvalid.new(entity) if entity.invalid?
34
+
35
+ dispatch_criteria_to_providers(:SaveByKey, entity)
36
+
37
+ entity
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ module Datamappify
2
+ module Repository
3
+ module QueryMethod
4
+ class Transaction < Method
5
+ # @param mapper (see Method#initialize)
6
+ #
7
+ # @yield
8
+ # queries to be performed in the transaction
9
+ #
10
+ # @return [void]
11
+ def initialize(mapper, &block)
12
+ mapper.default_provider.build_criteria(:Transaction, mapper.default_source_class, &block)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,3 @@
1
+ require 'datamappify/repository/query_method/method'
2
+
3
+ Dir[Datamappify.root.join('repository/query_method/*')].each { |file| require file }