rom 0.8.1 → 0.9.0.beta1

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