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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +5 -1
- data/lib/rom.rb +35 -16
- data/lib/rom/command.rb +1 -9
- data/lib/rom/commands/graph/class_interface.rb +2 -2
- data/lib/rom/constants.rb +0 -6
- data/lib/rom/{env.rb → container.rb} +3 -3
- data/lib/rom/environment.rb +238 -0
- data/lib/rom/environment_plugin.rb +17 -0
- data/lib/rom/environment_plugins/auto_registration.rb +17 -0
- data/lib/rom/global.rb +0 -203
- data/lib/rom/mapper_registry.rb +2 -0
- data/lib/rom/pipeline.rb +2 -0
- data/lib/rom/plugin.rb +4 -18
- data/lib/rom/plugin_base.rb +31 -0
- data/lib/rom/plugin_registry.rb +54 -17
- data/lib/rom/relation.rb +54 -11
- data/lib/rom/relation/class_interface.rb +14 -21
- data/lib/rom/relation/curried.rb +36 -2
- data/lib/rom/relation/graph.rb +7 -0
- data/lib/rom/relation_registry.rb +4 -0
- data/lib/rom/setup.rb +9 -8
- data/lib/rom/setup/finalize.rb +5 -5
- data/lib/rom/version.rb +1 -1
- data/rom.gemspec +2 -0
- data/spec/integration/commands/create_spec.rb +1 -1
- data/spec/integration/commands/update_spec.rb +1 -1
- data/spec/integration/mappers/unwrap_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/rom/{env_spec.rb → container_spec.rb} +5 -5
- data/spec/unit/rom/plugin_spec.rb +0 -8
- data/spec/unit/rom/relation/composite_spec.rb +2 -2
- data/spec/unit/rom/relation/curried_spec.rb +53 -0
- data/spec/unit/rom/relation/graph_spec.rb +4 -0
- data/spec/unit/rom/relation/lazy/combine_spec.rb +6 -6
- data/spec/unit/rom/relation/lazy_spec.rb +4 -8
- data/spec/unit/rom/relation_spec.rb +0 -14
- data/spec/unit/rom/setup_spec.rb +1 -1
- metadata +52 -35
- data/lib/rom/header.rb +0 -193
- data/lib/rom/header/attribute.rb +0 -184
- data/lib/rom/mapper.rb +0 -103
- data/lib/rom/mapper/attribute_dsl.rb +0 -477
- data/lib/rom/mapper/dsl.rb +0 -119
- data/lib/rom/mapper/model_dsl.rb +0 -55
- data/lib/rom/model_builder.rb +0 -101
- data/lib/rom/processor.rb +0 -28
- data/lib/rom/processor/transproc.rb +0 -388
- data/lib/rom/relation/lazy.rb +0 -145
- data/lib/rom/support/array_dataset.rb +0 -41
- data/lib/rom/support/class_builder.rb +0 -44
- data/lib/rom/support/class_macros.rb +0 -56
- data/lib/rom/support/data_proxy.rb +0 -102
- data/lib/rom/support/deprecations.rb +0 -36
- data/lib/rom/support/enumerable_dataset.rb +0 -65
- data/lib/rom/support/inflector.rb +0 -73
- data/lib/rom/support/options.rb +0 -195
- data/lib/rom/support/registry.rb +0 -43
- data/spec/unit/rom/header_spec.rb +0 -102
- data/spec/unit/rom/mapper/dsl_spec.rb +0 -467
- data/spec/unit/rom/mapper_spec.rb +0 -84
- data/spec/unit/rom/model_builder_spec.rb +0 -46
- data/spec/unit/rom/processor/transproc_spec.rb +0 -448
- data/spec/unit/rom/support/array_dataset_spec.rb +0 -61
- data/spec/unit/rom/support/class_builder_spec.rb +0 -42
- data/spec/unit/rom/support/enumerable_dataset_spec.rb +0 -17
- data/spec/unit/rom/support/inflector_spec.rb +0 -89
- 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
|
data/lib/rom/support/options.rb
DELETED
@@ -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
|
data/lib/rom/support/registry.rb
DELETED
@@ -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
|