rom 0.8.1 → 0.9.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +5 -1
  4. data/lib/rom.rb +35 -16
  5. data/lib/rom/command.rb +1 -9
  6. data/lib/rom/commands/graph/class_interface.rb +2 -2
  7. data/lib/rom/constants.rb +0 -6
  8. data/lib/rom/{env.rb → container.rb} +3 -3
  9. data/lib/rom/environment.rb +238 -0
  10. data/lib/rom/environment_plugin.rb +17 -0
  11. data/lib/rom/environment_plugins/auto_registration.rb +17 -0
  12. data/lib/rom/global.rb +0 -203
  13. data/lib/rom/mapper_registry.rb +2 -0
  14. data/lib/rom/pipeline.rb +2 -0
  15. data/lib/rom/plugin.rb +4 -18
  16. data/lib/rom/plugin_base.rb +31 -0
  17. data/lib/rom/plugin_registry.rb +54 -17
  18. data/lib/rom/relation.rb +54 -11
  19. data/lib/rom/relation/class_interface.rb +14 -21
  20. data/lib/rom/relation/curried.rb +36 -2
  21. data/lib/rom/relation/graph.rb +7 -0
  22. data/lib/rom/relation_registry.rb +4 -0
  23. data/lib/rom/setup.rb +9 -8
  24. data/lib/rom/setup/finalize.rb +5 -5
  25. data/lib/rom/version.rb +1 -1
  26. data/rom.gemspec +2 -0
  27. data/spec/integration/commands/create_spec.rb +1 -1
  28. data/spec/integration/commands/update_spec.rb +1 -1
  29. data/spec/integration/mappers/unwrap_spec.rb +1 -1
  30. data/spec/spec_helper.rb +2 -0
  31. data/spec/unit/rom/{env_spec.rb → container_spec.rb} +5 -5
  32. data/spec/unit/rom/plugin_spec.rb +0 -8
  33. data/spec/unit/rom/relation/composite_spec.rb +2 -2
  34. data/spec/unit/rom/relation/curried_spec.rb +53 -0
  35. data/spec/unit/rom/relation/graph_spec.rb +4 -0
  36. data/spec/unit/rom/relation/lazy/combine_spec.rb +6 -6
  37. data/spec/unit/rom/relation/lazy_spec.rb +4 -8
  38. data/spec/unit/rom/relation_spec.rb +0 -14
  39. data/spec/unit/rom/setup_spec.rb +1 -1
  40. metadata +52 -35
  41. data/lib/rom/header.rb +0 -193
  42. data/lib/rom/header/attribute.rb +0 -184
  43. data/lib/rom/mapper.rb +0 -103
  44. data/lib/rom/mapper/attribute_dsl.rb +0 -477
  45. data/lib/rom/mapper/dsl.rb +0 -119
  46. data/lib/rom/mapper/model_dsl.rb +0 -55
  47. data/lib/rom/model_builder.rb +0 -101
  48. data/lib/rom/processor.rb +0 -28
  49. data/lib/rom/processor/transproc.rb +0 -388
  50. data/lib/rom/relation/lazy.rb +0 -145
  51. data/lib/rom/support/array_dataset.rb +0 -41
  52. data/lib/rom/support/class_builder.rb +0 -44
  53. data/lib/rom/support/class_macros.rb +0 -56
  54. data/lib/rom/support/data_proxy.rb +0 -102
  55. data/lib/rom/support/deprecations.rb +0 -36
  56. data/lib/rom/support/enumerable_dataset.rb +0 -65
  57. data/lib/rom/support/inflector.rb +0 -73
  58. data/lib/rom/support/options.rb +0 -195
  59. data/lib/rom/support/registry.rb +0 -43
  60. data/spec/unit/rom/header_spec.rb +0 -102
  61. data/spec/unit/rom/mapper/dsl_spec.rb +0 -467
  62. data/spec/unit/rom/mapper_spec.rb +0 -84
  63. data/spec/unit/rom/model_builder_spec.rb +0 -46
  64. data/spec/unit/rom/processor/transproc_spec.rb +0 -448
  65. data/spec/unit/rom/support/array_dataset_spec.rb +0 -61
  66. data/spec/unit/rom/support/class_builder_spec.rb +0 -42
  67. data/spec/unit/rom/support/enumerable_dataset_spec.rb +0 -17
  68. data/spec/unit/rom/support/inflector_spec.rb +0 -89
  69. data/spec/unit/rom/support/options_spec.rb +0 -119
@@ -1,73 +0,0 @@
1
- module ROM
2
- # Helper module providing thin interface around an inflection backend.
3
- #
4
- # @private
5
- module Inflector
6
- BACKENDS = {
7
- activesupport: [
8
- 'active_support/inflector',
9
- proc { ::ActiveSupport::Inflector }
10
- ],
11
- inflecto: [
12
- 'inflecto',
13
- proc { ::Inflecto }
14
- ]
15
- }.freeze
16
-
17
- def self.realize_backend(path, inflector_backend_factory)
18
- require path
19
- inflector_backend_factory.call
20
- rescue LoadError
21
- nil
22
- end
23
-
24
- def self.detect_backend
25
- BACKENDS.find do |_, (path, inflector_class)|
26
- backend = realize_backend(path, inflector_class)
27
- break backend if backend
28
- end ||
29
- raise(LoadError,
30
- "No inflector library could be found: "\
31
- "please install either the `inflecto` or `activesupport` gem.")
32
- end
33
-
34
- def self.select_backend(name = nil)
35
- if name && !BACKENDS.key?(name)
36
- raise NameError, "Invalid inflector library selection: '#{name}'"
37
- end
38
- @inflector = name ? realize_backend(*BACKENDS[name]) : detect_backend
39
- end
40
-
41
- def self.inflector
42
- defined?(@inflector) && @inflector || select_backend
43
- end
44
-
45
- def self.camelize(input)
46
- inflector.camelize(input)
47
- end
48
-
49
- def self.underscore(input)
50
- inflector.underscore(input)
51
- end
52
-
53
- def self.singularize(input)
54
- inflector.singularize(input)
55
- end
56
-
57
- def self.pluralize(input)
58
- inflector.pluralize(input)
59
- end
60
-
61
- def self.demodulize(input)
62
- inflector.demodulize(input)
63
- end
64
-
65
- def self.constantize(input)
66
- inflector.constantize(input)
67
- end
68
-
69
- def self.classify(input)
70
- inflector.classify(input)
71
- end
72
- end
73
- end
@@ -1,195 +0,0 @@
1
- module ROM
2
- # Helper module for classes with a constructor accepting option hash
3
- #
4
- # This allows us to DRY up code as option hash is a very common pattern used
5
- # across the codebase. It is an internal implementation detail not meant to
6
- # be used outside of ROM
7
- #
8
- # @example
9
- # class User
10
- # include Options
11
- #
12
- # option :name, type: String, reader: true
13
- # option :admin, allow: [true, false], reader: true, default: false
14
- #
15
- # def initialize(options={})
16
- # super
17
- # end
18
- # end
19
- #
20
- # user = User.new(name: 'Piotr')
21
- # user.name # => "Piotr"
22
- # user.admin # => false
23
- #
24
- # @api public
25
- module Options
26
- # @return [Hash<Option>] Option definitions
27
- #
28
- # @api public
29
- attr_reader :options
30
-
31
- def self.included(klass)
32
- klass.extend ClassMethods
33
- klass.option_definitions = Definitions.new
34
- end
35
-
36
- # Defines a single option
37
- #
38
- # @api private
39
- class Option
40
- attr_reader :name, :type, :allow, :default
41
-
42
- def initialize(name, options = {})
43
- @name = name
44
- @type = options.fetch(:type) { Object }
45
- @reader = options.fetch(:reader) { false }
46
- @allow = options.fetch(:allow) { [] }
47
- @default = options.fetch(:default) { Undefined }
48
- @ivar = :"@#{name}" if @reader
49
- end
50
-
51
- def reader?
52
- @reader
53
- end
54
-
55
- def assign_reader_value(object, value)
56
- object.instance_variable_set(@ivar, value)
57
- end
58
-
59
- def default?
60
- @default != Undefined
61
- end
62
-
63
- def default_value(object)
64
- default.is_a?(Proc) ? default.call(object) : default
65
- end
66
-
67
- def type_matches?(value)
68
- value.is_a?(type)
69
- end
70
-
71
- def allow?(value)
72
- allow.empty? || allow.include?(value)
73
- end
74
- end
75
-
76
- # Manage all available options
77
- #
78
- # @api private
79
- class Definitions
80
- def initialize
81
- @options = {}
82
- end
83
-
84
- def initialize_copy(source)
85
- super
86
- @options = @options.dup
87
- end
88
-
89
- def define(option)
90
- @options[option.name] = option
91
- end
92
-
93
- def process(object, options)
94
- ensure_known_options(options)
95
-
96
- each do |name, option|
97
- if option.default? && !options.key?(name)
98
- options[name] = option.default_value(object)
99
- end
100
-
101
- if options.key?(name)
102
- validate_option_value(option, name, options[name])
103
- end
104
-
105
- option.assign_reader_value(object, options[name]) if option.reader?
106
- end
107
- end
108
-
109
- def names
110
- @options.keys
111
- end
112
-
113
- private
114
-
115
- def each(&block)
116
- @options.each(&block)
117
- end
118
-
119
- def ensure_known_options(options)
120
- options.each_key do |name|
121
- @options.fetch(name) do
122
- raise InvalidOptionKeyError,
123
- "#{name.inspect} is not a valid option"
124
- end
125
- end
126
- end
127
-
128
- def validate_option_value(option, name, value)
129
- unless option.type_matches?(value)
130
- raise InvalidOptionValueError,
131
- "#{name.inspect}:#{value.inspect} has incorrect type"
132
- end
133
-
134
- unless option.allow?(value)
135
- raise InvalidOptionValueError,
136
- "#{name.inspect}:#{value.inspect} has incorrect value"
137
- end
138
- end
139
- end
140
-
141
- # @api private
142
- module ClassMethods
143
- # Available options
144
- #
145
- # @return [Definitions]
146
- #
147
- # @api private
148
- attr_accessor :option_definitions
149
-
150
- # Defines an option
151
- #
152
- # @param [Symbol] name option name
153
- #
154
- # @param [Hash] settings option settings
155
- # @option settings [Class] :type Restrict option type. Default: +Object+
156
- # @option settings [Boolean] :reader Define a reader? Default: +false+
157
- # @option settings [Array] :allow Allow certain values. Default: Allow anything
158
- # @option settings [Object] :default Set default value for missing option
159
- #
160
- # @api public
161
- def option(name, settings = {})
162
- option = Option.new(name, settings)
163
- option_definitions.define(option)
164
- attr_reader(name) if option.reader?
165
- end
166
-
167
- # @api private
168
- def inherited(descendant)
169
- descendant.option_definitions = option_definitions.dup
170
- super
171
- end
172
- end
173
-
174
- # Initialize options provided as optional last argument hash
175
- #
176
- # @example
177
- # class Commands
178
- # include Options
179
- #
180
- # # ...
181
- #
182
- # def initialize(relations, options={})
183
- # @relation = relation
184
- # super
185
- # end
186
- # end
187
- #
188
- # @param [Array] args
189
- def initialize(*args)
190
- options = args.last ? args.last.dup : {}
191
- self.class.option_definitions.process(self, options)
192
- @options = options.freeze
193
- end
194
- end
195
- end
@@ -1,43 +0,0 @@
1
- module ROM
2
- # @api private
3
- class Registry
4
- include Enumerable
5
- include Equalizer.new(:elements)
6
-
7
- class ElementNotFoundError < KeyError
8
- def initialize(key, name)
9
- super("#{key.inspect} doesn't exist in #{name} registry")
10
- end
11
- end
12
-
13
- attr_reader :elements, :name
14
-
15
- def initialize(elements = {}, name = self.class.name)
16
- @elements = elements
17
- @name = name
18
- end
19
-
20
- def each(&block)
21
- return to_enum unless block
22
- elements.each { |element| yield(element) }
23
- end
24
-
25
- def key?(name)
26
- elements.key?(name)
27
- end
28
-
29
- def [](key)
30
- elements.fetch(key) { raise ElementNotFoundError.new(key, name) }
31
- end
32
-
33
- def respond_to_missing?(name, include_private = false)
34
- elements.key?(name) || super
35
- end
36
-
37
- private
38
-
39
- def method_missing(name, *)
40
- elements.fetch(name) { super }
41
- end
42
- end
43
- end
@@ -1,102 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ROM::Header do
4
- describe '.coerce' do
5
- subject(:header) { ROM::Header.coerce(input) }
6
-
7
- context 'with a primitive type' do
8
- let(:input) { [[:name, type: :string]] }
9
-
10
- let(:expected) do
11
- ROM::Header.new(name: ROM::Header::Attribute.coerce(input.first))
12
- end
13
-
14
- it 'returns a header with coerced attributes' do
15
- expect(header).to eql(expected)
16
-
17
- expect(header[:name].type).to be(:string)
18
- end
19
- end
20
-
21
- context 'with a collection type' do
22
- let(:input) { [[:tasks, header: [[:title]], type: :array, model: model]] }
23
- let(:model) { Class.new }
24
-
25
- let(:expected) do
26
- ROM::Header.new(tasks: ROM::Header::Attribute.coerce(input.first))
27
- end
28
-
29
- it 'returns a header with coerced attributes' do
30
- expect(header).to eql(expected)
31
-
32
- tasks = header[:tasks]
33
-
34
- expect(tasks.type).to be(:array)
35
- expect(tasks.header.model).to be(model)
36
- expect(tasks.header).to eql(ROM::Header.coerce([[:title]], model: model))
37
-
38
- expect(input.first[1])
39
- .to eql(header: [[:title]], type: :array, model: model)
40
- end
41
- end
42
-
43
- context 'with a hash type' do
44
- let(:input) { [[:task, header: [[:title]], type: :hash, model: model]] }
45
- let(:model) { Class.new }
46
-
47
- let(:expected) do
48
- ROM::Header.new(task: ROM::Header::Attribute.coerce(input.first))
49
- end
50
-
51
- it 'returns a header with coerced attributes' do
52
- expect(header).to eql(expected)
53
-
54
- tasks = header[:task]
55
-
56
- expect(tasks.type).to be(:hash)
57
- expect(tasks.header.model).to be(model)
58
- expect(tasks.header).to eql(ROM::Header.coerce([[:title]], model: model))
59
-
60
- expect(input.first[1])
61
- .to eql(header: [[:title]], type: :hash, model: model)
62
- end
63
- end
64
- end
65
-
66
- describe '#mapping' do
67
- it 'return mapping hash for primitive attributes' do
68
- header = ROM::Header.coerce([[:title], [:name, from: :user_name]])
69
-
70
- expect(header.mapping).to eql(title: :title, user_name: :name)
71
- end
72
-
73
- it 'returns an empty hash when there are no primitive attributes' do
74
- header = ROM::Header.coerce([
75
- [:city, type: :hash, wrap: true, header: [[:name]]]
76
- ])
77
-
78
- expect(header.mapping).to eql({})
79
- end
80
- end
81
-
82
- describe '#tuple_keys' do
83
- it 'return primitive attribute keys' do
84
- header = ROM::Header.coerce([[:title], [:name, from: :user_name]])
85
- expect(header.tuple_keys).to eql([:title, :user_name])
86
- end
87
-
88
- it 'return primitive attribute, wrap and group keys' do
89
- header = ROM::Header.coerce([
90
- [:title],
91
- [:comments, type: :array, header: [[:author], [:text]]],
92
- [:location, type: :hash, header: [[:lat], [:lng]]],
93
- [:city, type: :hash, wrap: true, header: [[:name, from: :city_name]]],
94
- [:tags, type: :array, group: true, header: [[:name, from: :tag_name]]]
95
- ])
96
-
97
- expect(header.tuple_keys).to match_array([
98
- :title, :comments, :location, :city_name, :tag_name
99
- ])
100
- end
101
- end
102
- end
@@ -1,467 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ROM::Mapper do
4
- subject(:mapper) do
5
- klass = Class.new(parent)
6
- options.each do |k, v|
7
- klass.send(k, v)
8
- end
9
- klass
10
- end
11
-
12
- let(:parent) { Class.new(ROM::Mapper) }
13
-
14
- let(:options) { {} }
15
- let(:header) { mapper.header }
16
-
17
- let(:expected_header) { ROM::Header.coerce(attributes) }
18
-
19
- describe '#attribute' do
20
- context 'simple attribute' do
21
- let(:attributes) { [[:name]] }
22
-
23
- it 'adds an attribute for the header' do
24
- mapper.attribute :name
25
-
26
- expect(header).to eql(expected_header)
27
- end
28
- end
29
-
30
- context 'aliased attribute' do
31
- let(:attributes) { [[:name, from: :user_name]] }
32
-
33
- it 'adds an aliased attribute for the header' do
34
- mapper.attribute :name, from: :user_name
35
-
36
- expect(header).to eql(expected_header)
37
- end
38
- end
39
-
40
- context 'prefixed attribute' do
41
- let(:attributes) { [[:name, from: :user_name]] }
42
- let(:options) { { prefix: :user } }
43
-
44
- it 'adds an aliased attribute for the header using configured :prefix' do
45
- mapper.attribute :name
46
-
47
- expect(header).to eql(expected_header)
48
- end
49
- end
50
-
51
- context 'prefixed attribute using custom separator' do
52
- let(:attributes) { [[:name, from: :'u.name']] }
53
- let(:options) { { prefix: :u, prefix_separator: '.' } }
54
-
55
- it 'adds an aliased attribute for the header using configured :prefix' do
56
- mapper.attribute :name
57
-
58
- expect(header).to eql(expected_header)
59
- end
60
- end
61
-
62
- context 'symbolized attribute' do
63
- let(:attributes) { [[:name, from: 'name']] }
64
- let(:options) { { symbolize_keys: true } }
65
-
66
- it 'adds an attribute with symbolized alias' do
67
- mapper.attribute :name
68
-
69
- expect(header).to eql(expected_header)
70
- end
71
- end
72
- end
73
-
74
- describe 'reject_keys' do
75
- let(:attributes) { [[:name, type: :string]] }
76
- let(:options) { { reject_keys: true } }
77
-
78
- it 'sets rejected_keys for the header' do
79
- mapper.reject_keys true
80
- mapper.attribute :name, type: :string
81
-
82
- expect(header).to eql(expected_header)
83
- end
84
- end
85
-
86
- describe 'overriding inherited attributes' do
87
- context 'when name matches' do
88
- let(:attributes) { [[:name, type: :string]] }
89
-
90
- it 'excludes the inherited attribute' do
91
- parent.attribute :name
92
-
93
- mapper.attribute :name, type: :string
94
-
95
- expect(header).to eql(expected_header)
96
- end
97
- end
98
-
99
- context 'when alias matches' do
100
- let(:attributes) { [[:name, from: 'name', type: :string]] }
101
-
102
- it 'excludes the inherited attribute' do
103
- parent.attribute 'name'
104
-
105
- mapper.attribute :name, from: 'name', type: :string
106
-
107
- expect(header).to eql(expected_header)
108
- end
109
- end
110
-
111
- context 'when name in a wrapped attribute matches' do
112
- let(:attributes) do
113
- [
114
- [:city, type: :hash, wrap: true, header: [[:name, from: :city_name]]]
115
- ]
116
- end
117
-
118
- it 'excludes the inherited attribute' do
119
- parent.attribute :city_name
120
-
121
- mapper.wrap :city do
122
- attribute :name, from: :city_name
123
- end
124
-
125
- expect(header).to eql(expected_header)
126
- end
127
- end
128
-
129
- context 'when name in a grouped attribute matches' do
130
- let(:attributes) do
131
- [
132
- [:tags, type: :array, group: true, header: [[:name, from: :tag_name]]]
133
- ]
134
- end
135
-
136
- it 'excludes the inherited attribute' do
137
- parent.attribute :tag_name
138
-
139
- mapper.group :tags do
140
- attribute :name, from: :tag_name
141
- end
142
-
143
- expect(header).to eql(expected_header)
144
- end
145
- end
146
-
147
- context 'when name in a hash attribute matches' do
148
- let(:attributes) do
149
- [
150
- [:city, type: :hash, header: [[:name, from: :city_name]]]
151
- ]
152
- end
153
-
154
- it 'excludes the inherited attribute' do
155
- parent.attribute :city
156
-
157
- mapper.embedded :city, type: :hash do
158
- attribute :name, from: :city_name
159
- end
160
-
161
- expect(header).to eql(expected_header)
162
- end
163
- end
164
-
165
- context 'when name of an array attribute matches' do
166
- let(:attributes) do
167
- [
168
- [:tags, type: :array, header: [[:name, from: :tag_name]]]
169
- ]
170
- end
171
-
172
- it 'excludes the inherited attribute' do
173
- parent.attribute :tags
174
-
175
- mapper.embedded :tags, type: :array do
176
- attribute :name, from: :tag_name
177
- end
178
-
179
- expect(header).to eql(expected_header)
180
- end
181
- end
182
- end
183
-
184
- describe '#exclude' do
185
- let(:attributes) { [[:name, from: 'name']] }
186
-
187
- it 'removes an attribute from the inherited header' do
188
- mapper.attribute :name, from: 'name'
189
- expect(header).to eql(expected_header)
190
- end
191
- end
192
-
193
- describe '#embedded' do
194
- context 'when :type is set to :hash' do
195
- let(:attributes) { [[:city, type: :hash, header: [[:name]]]] }
196
-
197
- it 'adds an embedded hash attribute' do
198
- mapper.embedded :city, type: :hash do
199
- attribute :name
200
- end
201
-
202
- expect(header).to eql(expected_header)
203
- end
204
- end
205
-
206
- context 'when :type is set to :array' do
207
- let(:attributes) { [[:tags, type: :array, header: [[:name]]]] }
208
-
209
- it 'adds an embedded array attribute' do
210
- mapper.embedded :tags, type: :array do
211
- attribute :name
212
- end
213
-
214
- expect(header).to eql(expected_header)
215
- end
216
- end
217
- end
218
-
219
- describe '#wrap' do
220
- let(:attributes) { [[:city, type: :hash, wrap: true, header: [[:name]]]] }
221
-
222
- it 'adds an wrapped hash attribute using a block to define attributes' do
223
- mapper.wrap :city do
224
- attribute :name
225
- end
226
-
227
- expect(header).to eql(expected_header)
228
- end
229
-
230
- it 'adds an wrapped hash attribute using a options define attributes' do
231
- mapper.wrap city: [:name]
232
-
233
- expect(header).to eql(expected_header)
234
- end
235
-
236
- it 'raises an exception when using a block and options to define attributes' do
237
- expect {
238
- mapper.wrap(city: [:name]) { attribute :other_name }
239
- }.to raise_error(ROM::MapperMisconfiguredError)
240
- end
241
-
242
- it 'raises an exception when using options and a mapper to define attributes' do
243
- task_mapper = Class.new(ROM::Mapper) { attribute :title }
244
- expect {
245
- mapper.wrap city: [:name], mapper: task_mapper
246
- }.to raise_error(ROM::MapperMisconfiguredError)
247
- end
248
- end
249
-
250
- describe '#group' do
251
- let(:attributes) { [[:tags, type: :array, group: true, header: [[:name]]]] }
252
-
253
- it 'adds a group attribute using a block to define attributes' do
254
- mapper.group :tags do
255
- attribute :name
256
- end
257
-
258
- expect(header).to eql(expected_header)
259
- end
260
-
261
- it 'adds a group attribute using a options define attributes' do
262
- mapper.group tags: [:name]
263
-
264
- expect(header).to eql(expected_header)
265
- end
266
-
267
- it 'raises an exception when using a block and options to define attributes' do
268
- expect {
269
- mapper.group(cities: [:name]) { attribute :other_name }
270
- }.to raise_error(ROM::MapperMisconfiguredError)
271
- end
272
-
273
- it 'raises an exception when using options and a mapper to define attributes' do
274
- task_mapper = Class.new(ROM::Mapper) { attribute :title }
275
- expect {
276
- mapper.group cities: [:name], mapper: task_mapper
277
- }.to raise_error(ROM::MapperMisconfiguredError)
278
- end
279
- end
280
-
281
- describe 'top-level :prefix option' do
282
- let(:options) do
283
- { prefix: :user }
284
- end
285
-
286
- context 'when no attribute overrides top-level setting' do
287
- let(:attributes) do
288
- [
289
- [:name, from: :user_name],
290
- [:address, from: :user_address, type: :hash, header: [
291
- [:city, from: :user_city]]
292
- ],
293
- [:contact, type: :hash, wrap: true, header: [
294
- [:mobile, from: :user_mobile]]
295
- ],
296
- [:tasks, type: :array, group: true, header: [
297
- [:title, from: :user_title]]
298
- ]
299
- ]
300
- end
301
-
302
- it 'sets aliased attributes using prefix automatically' do
303
- mapper.attribute :name
304
-
305
- mapper.embedded :address, type: :hash do
306
- attribute :city
307
- end
308
-
309
- mapper.wrap :contact do
310
- attribute :mobile
311
- end
312
-
313
- mapper.group :tasks do
314
- attribute :title
315
- end
316
-
317
- expect(header).to eql(expected_header)
318
- end
319
- end
320
-
321
- context 'when an attribute overrides top-level setting' do
322
- let(:attributes) do
323
- [
324
- [:name, from: :user_name],
325
- [:birthday, from: :user_birthday, type: :hash, header: [
326
- [:year, from: :bd_year],
327
- [:month, from: :bd_month],
328
- [:day, from: :bd_day]]
329
- ],
330
- [:address, from: :user_address, type: :hash, header: [[:city]]],
331
- [:contact, type: :hash, wrap: true, header: [
332
- [:mobile, from: :contact_mobile]]
333
- ],
334
- [:tasks, type: :array, group: true, header: [
335
- [:title, from: :task_title]]
336
- ]
337
- ]
338
- end
339
-
340
- it 'excludes from aliasing the ones which override it' do
341
- mapper.attribute :name
342
-
343
- mapper.embedded :birthday, type: :hash, prefix: :bd do
344
- attribute :year
345
- attribute :month
346
- attribute :day
347
- end
348
-
349
- mapper.embedded :address, type: :hash, prefix: false do
350
- attribute :city
351
- end
352
-
353
- mapper.wrap :contact, prefix: :contact do
354
- attribute :mobile
355
- end
356
-
357
- mapper.group :tasks, prefix: :task do
358
- attribute :title
359
- end
360
-
361
- expect(header).to eql(expected_header)
362
- end
363
- end
364
- end
365
-
366
- context 'reusing mappers' do
367
- describe '#group' do
368
- let(:task_mapper) do
369
- Class.new(ROM::Mapper) { attribute :title }
370
- end
371
-
372
- let(:attributes) do
373
- [
374
- [:name],
375
- [:tasks, type: :array, group: true, header: task_mapper.header]
376
- ]
377
- end
378
-
379
- it 'uses other mapper header' do
380
- mapper.attribute :name
381
- mapper.group :tasks, mapper: task_mapper
382
-
383
- expect(header).to eql(expected_header)
384
- end
385
- end
386
-
387
- describe '#wrap' do
388
- let(:task_mapper) do
389
- Class.new(ROM::Mapper) { attribute :title }
390
- end
391
-
392
- let(:attributes) do
393
- [
394
- [:name],
395
- [:task, type: :hash, wrap: true, header: task_mapper.header]
396
- ]
397
- end
398
-
399
- it 'uses other mapper header' do
400
- mapper.attribute :name
401
- mapper.wrap :task, mapper: task_mapper
402
-
403
- expect(header).to eql(expected_header)
404
- end
405
- end
406
-
407
- describe '#embedded' do
408
- let(:task_mapper) do
409
- Class.new(ROM::Mapper) { attribute :title }
410
- end
411
-
412
- let(:attributes) do
413
- [
414
- [:name],
415
- [:task, type: :hash, header: task_mapper.header]
416
- ]
417
- end
418
-
419
- it 'uses other mapper header' do
420
- mapper.attribute :name
421
- mapper.embedded :task, mapper: task_mapper, type: :hash
422
-
423
- expect(header).to eql(expected_header)
424
- end
425
- end
426
- end
427
-
428
- describe '#combine' do
429
- let(:attributes) do
430
- [
431
- [:title],
432
- [:tasks, combine: true, type: :array, header: [[:title]]]
433
- ]
434
- end
435
-
436
- it 'adds combine attributes' do
437
- mapper.attribute :title
438
-
439
- mapper.combine :tasks, on: { title: :title } do
440
- attribute :title
441
- end
442
-
443
- expect(header).to eql(expected_header)
444
- end
445
-
446
- it 'works without a block' do
447
- expected_header = ROM::Header.coerce(
448
- [
449
- [:title],
450
- [:tasks, combine: true, type: :array, header: []]
451
- ]
452
- )
453
-
454
- mapper.attribute :title
455
-
456
- mapper.combine :tasks, on: { title: :title }
457
-
458
- expect(header).to eql(expected_header)
459
- end
460
- end
461
-
462
- describe '#method_missing' do
463
- it 'responds to DSL methods' do
464
- expect(mapper).to respond_to(:attribute)
465
- end
466
- end
467
- end