rom 0.4.2 → 0.5.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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +81 -0
  3. data/.travis.yml +2 -1
  4. data/CHANGELOG.md +41 -0
  5. data/Gemfile +12 -8
  6. data/Guardfile +17 -11
  7. data/README.md +7 -7
  8. data/Rakefile +29 -0
  9. data/lib/rom.rb +9 -66
  10. data/lib/rom/adapter.rb +45 -12
  11. data/lib/rom/adapter/memory.rb +0 -4
  12. data/lib/rom/adapter/memory/commands.rb +0 -10
  13. data/lib/rom/adapter/memory/dataset.rb +18 -6
  14. data/lib/rom/adapter/memory/storage.rb +0 -3
  15. data/lib/rom/command_registry.rb +24 -43
  16. data/lib/rom/commands.rb +5 -6
  17. data/lib/rom/commands/create.rb +5 -5
  18. data/lib/rom/commands/delete.rb +8 -6
  19. data/lib/rom/commands/result.rb +82 -0
  20. data/lib/rom/commands/update.rb +5 -4
  21. data/lib/rom/commands/with_options.rb +1 -4
  22. data/lib/rom/config.rb +70 -0
  23. data/lib/rom/env.rb +11 -3
  24. data/lib/rom/global.rb +107 -0
  25. data/lib/rom/header.rb +122 -89
  26. data/lib/rom/header/attribute.rb +148 -0
  27. data/lib/rom/mapper.rb +46 -67
  28. data/lib/rom/mapper_builder.rb +20 -73
  29. data/lib/rom/mapper_builder/mapper_dsl.rb +114 -0
  30. data/lib/rom/mapper_builder/model_dsl.rb +29 -0
  31. data/lib/rom/mapper_registry.rb +21 -0
  32. data/lib/rom/model_builder.rb +11 -17
  33. data/lib/rom/processor.rb +28 -0
  34. data/lib/rom/processor/transproc.rb +105 -0
  35. data/lib/rom/reader.rb +81 -21
  36. data/lib/rom/reader_builder.rb +14 -4
  37. data/lib/rom/relation.rb +19 -5
  38. data/lib/rom/relation_builder.rb +20 -6
  39. data/lib/rom/repository.rb +0 -2
  40. data/lib/rom/setup.rb +156 -0
  41. data/lib/rom/{boot → setup}/base_relation_dsl.rb +4 -8
  42. data/lib/rom/setup/command_dsl.rb +46 -0
  43. data/lib/rom/setup/finalize.rb +125 -0
  44. data/lib/rom/setup/mapper_dsl.rb +19 -0
  45. data/lib/rom/{boot → setup}/relation_dsl.rb +1 -4
  46. data/lib/rom/setup/schema_dsl.rb +33 -0
  47. data/lib/rom/support/registry.rb +10 -6
  48. data/lib/rom/version.rb +1 -1
  49. data/rom.gemspec +3 -1
  50. data/spec/integration/adapters/extending_relations_spec.rb +0 -2
  51. data/spec/integration/commands/create_spec.rb +2 -9
  52. data/spec/integration/commands/delete_spec.rb +4 -5
  53. data/spec/integration/commands/error_handling_spec.rb +4 -3
  54. data/spec/integration/commands/update_spec.rb +3 -8
  55. data/spec/integration/mappers/deep_embedded_spec.rb +52 -0
  56. data/spec/integration/mappers/definition_dsl_spec.rb +0 -118
  57. data/spec/integration/mappers/embedded_spec.rb +82 -0
  58. data/spec/integration/mappers/group_spec.rb +170 -0
  59. data/spec/integration/mappers/prefixing_attributes_spec.rb +2 -2
  60. data/spec/integration/mappers/renaming_attributes_spec.rb +8 -6
  61. data/spec/integration/mappers/symbolizing_attributes_spec.rb +80 -0
  62. data/spec/integration/mappers/wrap_spec.rb +162 -0
  63. data/spec/integration/multi_repo_spec.rb +64 -0
  64. data/spec/integration/relations/reading_spec.rb +12 -8
  65. data/spec/integration/relations/registry_dsl_spec.rb +1 -3
  66. data/spec/integration/schema_spec.rb +10 -0
  67. data/spec/integration/setup_spec.rb +57 -6
  68. data/spec/spec_helper.rb +2 -1
  69. data/spec/unit/config_spec.rb +60 -0
  70. data/spec/unit/rom/adapter/memory/dataset_spec.rb +52 -0
  71. data/spec/unit/rom/adapter_spec.rb +31 -11
  72. data/spec/unit/rom/header_spec.rb +60 -16
  73. data/spec/unit/rom/mapper_builder_spec.rb +311 -0
  74. data/spec/unit/rom/mapper_registry_spec.rb +25 -0
  75. data/spec/unit/rom/mapper_spec.rb +4 -5
  76. data/spec/unit/rom/model_builder_spec.rb +15 -13
  77. data/spec/unit/rom/processor/transproc_spec.rb +331 -0
  78. data/spec/unit/rom/reader_spec.rb +73 -0
  79. data/spec/unit/rom/registry_spec.rb +38 -0
  80. data/spec/unit/rom/relation_spec.rb +0 -1
  81. data/spec/unit/rom/setup_spec.rb +55 -0
  82. data/spec/unit/rom_spec.rb +14 -0
  83. metadata +62 -22
  84. data/Gemfile.devtools +0 -71
  85. data/lib/rom/boot.rb +0 -197
  86. data/lib/rom/boot/command_dsl.rb +0 -48
  87. data/lib/rom/boot/dsl.rb +0 -37
  88. data/lib/rom/boot/mapper_dsl.rb +0 -23
  89. data/lib/rom/boot/schema_dsl.rb +0 -27
  90. data/lib/rom/ra.rb +0 -172
  91. data/lib/rom/ra/operation/group.rb +0 -47
  92. data/lib/rom/ra/operation/join.rb +0 -39
  93. data/lib/rom/ra/operation/wrap.rb +0 -45
  94. data/lib/rom/transformer.rb +0 -77
  95. data/spec/integration/ra/group_spec.rb +0 -46
  96. data/spec/integration/ra/join_spec.rb +0 -50
  97. data/spec/integration/ra/wrap_spec.rb +0 -37
  98. data/spec/unit/rom/ra/operation/group_spec.rb +0 -55
  99. data/spec/unit/rom/ra/operation/wrap_spec.rb +0 -29
  100. data/spec/unit/rom/transformer_spec.rb +0 -41
@@ -0,0 +1,148 @@
1
+ module ROM
2
+ class Header
3
+ # An attribute provides information about a specific attribute in a tuple
4
+ #
5
+ # This may include information about how an attribute should be renamed,
6
+ # or how its value should coerced.
7
+ #
8
+ # More complex attributes describe how an attribute should be transformed.
9
+ #
10
+ # @private
11
+ class Attribute
12
+ include Equalizer.new(:name, :key, :type)
13
+
14
+ # @return [Symbol] name of an attribute
15
+ #
16
+ # @api private
17
+ attr_reader :name
18
+
19
+ # @return [Symbol] key of an attribute that corresponds to tuple attribute
20
+ #
21
+ # @api private
22
+ attr_reader :key
23
+
24
+ # @return [Symbol] type identifier (defaults to :object)
25
+ #
26
+ # @api private
27
+ attr_reader :type
28
+
29
+ # @return [Hash] additional meta information
30
+ #
31
+ # @api private
32
+ attr_reader :meta
33
+
34
+ # Return attribute class for a give meta hash
35
+ #
36
+ # @param [Hash] hash with type information and optional transformation info
37
+ #
38
+ # @return [Class]
39
+ #
40
+ # @api private
41
+ def self.[](meta)
42
+ type = meta[:type]
43
+
44
+ if type.equal?(:hash)
45
+ meta[:wrap] ? Wrap : Hash
46
+ elsif type.equal?(:array)
47
+ meta[:group] ? Group : Array
48
+ else
49
+ self
50
+ end
51
+ end
52
+
53
+ # Coerce an array with attribute meta-data into an attribute object
54
+ #
55
+ # @param [Array<Symbol,Hash>] name/options pair
56
+ #
57
+ # @return [Attribute]
58
+ #
59
+ # @api private
60
+ def self.coerce(input)
61
+ name = input[0]
62
+ meta = (input[1] || {}).dup
63
+
64
+ meta[:type] ||= :object
65
+
66
+ if meta.key?(:header)
67
+ meta[:header] = Header.coerce(meta[:header], meta[:model])
68
+ end
69
+
70
+ self[meta].new(name, meta)
71
+ end
72
+
73
+ # @api private
74
+ def initialize(name, meta)
75
+ @name = name
76
+ @meta = meta
77
+ @key = meta.fetch(:from) { name }
78
+ @type = meta.fetch(:type)
79
+ end
80
+
81
+ # Return if an attribute has a specific type identifier
82
+ #
83
+ # @api private
84
+ def typed?
85
+ type != :object
86
+ end
87
+
88
+ # Return if an attribute should be aliased
89
+ #
90
+ # @api private
91
+ def aliased?
92
+ key != name
93
+ end
94
+
95
+ # Return :key-to-:name mapping hash
96
+ #
97
+ # @return [Hash]
98
+ #
99
+ # @api private
100
+ def mapping
101
+ { key => name }
102
+ end
103
+ end
104
+
105
+ # Embedded attribute is a special attribute type that has a header
106
+ #
107
+ # This is the base of complex attributes like Hash or Group
108
+ #
109
+ # @private
110
+ class Embedded < Attribute
111
+ include Equalizer.new(:name, :key, :type, :header)
112
+
113
+ # return [Header] header of an attribute
114
+ #
115
+ # @api private
116
+ attr_reader :header
117
+
118
+ # @api private
119
+ def initialize(*)
120
+ super
121
+ @header = meta.fetch(:header)
122
+ end
123
+
124
+ # Return tuple keys from the header
125
+ #
126
+ # @return [Array<Symbol>]
127
+ #
128
+ # @api private
129
+ def tuple_keys
130
+ header.tuple_keys
131
+ end
132
+ end
133
+
134
+ # Array is an embedded attribute type
135
+ Array = Class.new(Embedded)
136
+
137
+ # Hash is an embedded attribute type
138
+ Hash = Class.new(Embedded)
139
+
140
+ # Wrap is a special type of Hash attribute that requires wrapping
141
+ # transformation
142
+ Wrap = Class.new(Hash)
143
+
144
+ # Group is a special type of Array attribute that requires grouping
145
+ # transformation
146
+ Group = Class.new(Array)
147
+ end
148
+ end
@@ -1,83 +1,62 @@
1
1
  module ROM
2
-
3
- # @api private
2
+ # Mapper is a simple object that uses a transformer to load relations
3
+ #
4
+ # @private
4
5
  class Mapper
5
- attr_reader :header, :model, :loader, :transformer
6
-
7
- class Basic < Mapper
8
- attr_reader :mapping
9
-
10
- def initialize(*args)
11
- super
12
- @mapping = header.mapping
13
- end
14
-
15
- def load(tuple)
16
- super(Hash[call(tuple)])
17
- end
18
-
19
- def call(tuple)
20
- tuple.map { |key, value| [header.mapping[key], value] }
21
- end
6
+ # @return [Object] transformer object built by a processor
7
+ #
8
+ # @api private
9
+ attr_reader :transformer
10
+
11
+ # @return [Header] header that was used to build the transformer
12
+ #
13
+ # @api private
14
+ attr_reader :header
15
+
16
+ # @return [Hash] registered processors
17
+ #
18
+ # @api private
19
+ def self.processors
20
+ @_processors ||= {}
22
21
  end
23
22
 
24
- class Recursive < Basic
25
- attr_reader :transformer
26
-
27
- def initialize(*args)
28
- super
29
- @transformer = Transformer.build(header)
30
- end
31
-
32
- def process(relation)
33
- transformer.call(relation.to_a).each { |tuple| yield(load(tuple)) }
34
- end
35
-
36
- def call(tuple, header = self.header)
37
- mapping = header.mapping
38
-
39
- tuple.map do |key, value|
40
- case value
41
- when Hash
42
- [key, loader[Hash[call(value, header[key])], header[key].model]]
43
- when Array
44
- [key, value.map { |v| loader[Hash[call(v, header[key])], header[key].model] }]
45
- else
46
- [mapping[key], value]
47
- end
48
- end
49
- end
23
+ # Register a processor class
24
+ #
25
+ # @return [Hash]
26
+ #
27
+ # @api private
28
+ def self.register_processor(processor)
29
+ name = processor.name.split('::').last.downcase.to_sym
30
+ processors.update(name => processor)
50
31
  end
51
32
 
52
- def self.build(header, model)
53
- klass =
54
- if header.any? { |attribute| attribute.embedded? }
55
- Recursive
56
- elsif header.any? { |attribute| attribute.aliased? }
57
- Basic
58
- else
59
- self
60
- end
61
-
62
- loader = Proc.new { |tuple, m| m ? m.new(tuple) : tuple }
63
-
64
- klass.new(header, model, loader)
33
+ # Build a mapper using provided processor type
34
+ #
35
+ # @return [Mapper]
36
+ #
37
+ # @api private
38
+ def self.build(header, processor = :transproc)
39
+ new(processors.fetch(processor).build(header), header)
65
40
  end
66
41
 
67
- def initialize(header, model, loader)
42
+ # @api private
43
+ def initialize(transformer, header)
44
+ @transformer = transformer
68
45
  @header = header
69
- @model = model
70
- @loader = loader
71
46
  end
72
47
 
73
- def process(relation)
74
- relation.each { |tuple| yield(load(tuple)) }
48
+ # @return [Class] optional model that is instantiated by a mapper
49
+ #
50
+ # @api private
51
+ def model
52
+ header.model
75
53
  end
76
54
 
77
- def load(tuple)
78
- loader[tuple, model]
55
+ # Process a relation using the transfomer
56
+ #
57
+ # @api private
58
+ def process(relation, &block)
59
+ transformer[relation.to_a].each(&block)
79
60
  end
80
-
81
61
  end
82
-
83
62
  end
@@ -1,105 +1,52 @@
1
- require 'rom/model_builder'
1
+ require 'rom/mapper_builder/model_dsl'
2
+ require 'rom/mapper_builder/mapper_dsl'
2
3
 
3
4
  module ROM
4
-
5
5
  # @api private
6
6
  class MapperBuilder
7
+ attr_reader :name, :root, :options, :prefix, :symbolize_keys, :dsl
7
8
 
8
- class AttributeDSL
9
- attr_reader :attributes, :model_class, :model_builder
10
-
11
- def initialize
12
- @attributes = []
13
- end
14
-
15
- def header
16
- Header.coerce(attributes)
17
- end
18
-
19
- def model(options = nil)
20
- if options.is_a?(Class)
21
- @model_class = options
22
- elsif options
23
- @model_builder = ModelBuilder[options.fetch(:type) { :poro }].new(options)
24
- end
25
-
26
- if options
27
- self
28
- else
29
- model_class || (model_builder && model_builder.call(header))
30
- end
31
- end
32
-
33
- def attribute(name, options = {})
34
- attributes << [name, options]
35
- end
36
- end
37
-
38
- attr_reader :name, :root, :prefix,
39
- :model_builder, :model_class, :attributes
9
+ DEFAULT_PROCESSOR = :transproc
40
10
 
41
11
  def initialize(name, root, options = {})
42
12
  @name = name
13
+ @options = options
43
14
  @root = root
44
15
  @prefix = options[:prefix]
16
+ @symbolize_keys = options[:symbolize_keys]
45
17
 
46
- @attributes =
18
+ attributes =
47
19
  if options[:inherit_header]
48
20
  root.header.map { |attr| [prefix ? :"#{prefix}_#{attr}" : attr] }
49
21
  else
50
22
  []
51
23
  end
52
- end
53
24
 
54
- def model(options)
55
- if options.is_a?(Class)
56
- @model_class = options
57
- else
58
- @model_builder = ModelBuilder[options.fetch(:type) { :poro }].new(options)
59
- end
60
-
61
- self
62
- end
63
-
64
- def attribute(name, options = {})
65
- options[:from] = :"#{prefix}_#{name}" if prefix
66
- attributes << [name, options]
67
- end
68
-
69
- def exclude(name)
70
- attributes.delete([name])
71
- end
25
+ @dsl = MapperDSL.new(attributes, options)
72
26
 
73
- def group(options, &block)
74
- attribute_dsl(options, Array, &block)
27
+ @processor = DEFAULT_PROCESSOR
75
28
  end
76
29
 
77
- def wrap(options, &block)
78
- attribute_dsl(options, Hash, &block)
30
+ def processor(identifier = nil)
31
+ if identifier
32
+ @processor = identifier
33
+ else
34
+ @processor
35
+ end
79
36
  end
80
37
 
81
38
  def call
82
- header = Header.coerce(attributes)
83
-
84
- @model_class = model_builder.call(header) if model_builder
85
-
86
- Mapper.build(header, model_class)
39
+ Mapper.build(dsl.header, processor)
87
40
  end
88
41
 
89
42
  private
90
43
 
91
- def attribute_dsl(options, type, &block)
92
- if block
93
- dsl = AttributeDSL.new
94
- dsl.instance_exec(&block)
95
- attributes << [options, header: dsl.header, type: type, model: dsl.model]
44
+ def method_missing(name, *args, &block)
45
+ if dsl.respond_to?(name)
46
+ dsl.public_send(name, *args, &block)
96
47
  else
97
- options.each do |name, header|
98
- attributes << [name, header: header.zip, type: type]
99
- end
48
+ super
100
49
  end
101
50
  end
102
-
103
51
  end
104
-
105
52
  end
@@ -0,0 +1,114 @@
1
+ require 'rom/mapper_builder/model_dsl'
2
+
3
+ module ROM
4
+ class MapperBuilder
5
+ # @api private
6
+ class MapperDSL
7
+ include ModelDSL
8
+
9
+ attr_reader :attributes, :options, :symbolize_keys, :prefix
10
+
11
+ def initialize(attributes, options)
12
+ @attributes = attributes
13
+ @options = options
14
+ @symbolize_keys = options[:symbolize_keys]
15
+ @prefix = options[:prefix]
16
+ end
17
+
18
+ def attribute(name, options = EMPTY_HASH)
19
+ with_attr_options(name, options) do |attr_options|
20
+ add_attribute(name, attr_options)
21
+ end
22
+ end
23
+
24
+ def exclude(*names)
25
+ names.each { |name| attributes.delete([name]) }
26
+ end
27
+
28
+ def embedded(name, options, &block)
29
+ with_attr_options(name) do |attr_options|
30
+ dsl = new(options, &block)
31
+
32
+ attr_options.update(options)
33
+
34
+ add_attribute(
35
+ name, { header: dsl.header, type: :array }.update(attr_options)
36
+ )
37
+ end
38
+ end
39
+
40
+ def wrap(*args, &block)
41
+ with_name_or_options(*args) do |name, options|
42
+ dsl(name, { type: :hash, wrap: true }.update(options), &block)
43
+ end
44
+ end
45
+
46
+ def group(*args, &block)
47
+ with_name_or_options(*args) do |name, options|
48
+ dsl(name, { type: :array, group: true }.update(options), &block)
49
+ end
50
+ end
51
+
52
+ def header
53
+ Header.coerce(attributes, model)
54
+ end
55
+
56
+ private
57
+
58
+ def with_attr_options(name, options = EMPTY_HASH)
59
+ attr_options = options.dup
60
+
61
+ attr_options[:from] ||= :"#{prefix}_#{name}" if prefix
62
+
63
+ if symbolize_keys
64
+ attr_options.update(from: attr_options.fetch(:from) { name }.to_s)
65
+ end
66
+
67
+ yield(attr_options)
68
+ end
69
+
70
+ def with_name_or_options(*args)
71
+ name, options =
72
+ if args.size > 1
73
+ args
74
+ else
75
+ [args.first, {}]
76
+ end
77
+
78
+ yield(name, options)
79
+ end
80
+
81
+ def dsl(name_or_attrs, options, &block)
82
+ if block
83
+ attributes_from_block(name_or_attrs, options, &block)
84
+ else
85
+ attributes_from_hash(name_or_attrs, options)
86
+ end
87
+ end
88
+
89
+ def attributes_from_block(name, options, &block)
90
+ dsl = new(options, &block)
91
+ add_attribute(name, options.update(header: dsl.header))
92
+ end
93
+
94
+ def attributes_from_hash(hash, options)
95
+ hash.each do |name, header|
96
+ with_attr_options(name, options) do |attr_options|
97
+ add_attribute(name, attr_options.update(header: header.zip))
98
+ end
99
+ end
100
+ end
101
+
102
+ def add_attribute(name, options)
103
+ exclude(name, name.to_s)
104
+ attributes << [name, options]
105
+ end
106
+
107
+ def new(options, &block)
108
+ dsl = self.class.new([], @options.merge(options))
109
+ dsl.instance_exec(&block)
110
+ dsl
111
+ end
112
+ end
113
+ end
114
+ end