datamappify 0.20.1 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
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 }