rom 0.5.0 → 0.6.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/.rubocop.yml +19 -15
- data/.rubocop_todo.yml +28 -0
- data/.travis.yml +8 -1
- data/CHANGELOG.md +40 -0
- data/Gemfile +10 -2
- data/Guardfile +12 -10
- data/README.md +42 -43
- data/Rakefile +13 -23
- data/lib/rom.rb +19 -27
- data/lib/rom/command.rb +118 -0
- data/lib/rom/command_registry.rb +13 -27
- data/lib/rom/commands.rb +1 -59
- data/lib/rom/commands/abstract.rb +147 -0
- data/lib/rom/commands/composite.rb +47 -0
- data/lib/rom/commands/create.rb +2 -17
- data/lib/rom/commands/delete.rb +5 -25
- data/lib/rom/commands/result.rb +5 -5
- data/lib/rom/commands/update.rb +3 -27
- data/lib/rom/constants.rb +19 -0
- data/lib/rom/env.rb +85 -35
- data/lib/rom/global.rb +173 -42
- data/lib/rom/header.rb +5 -5
- data/lib/rom/header/attribute.rb +2 -2
- data/lib/rom/lint/enumerable_dataset.rb +52 -0
- data/lib/rom/lint/linter.rb +64 -0
- data/lib/rom/lint/repository.rb +78 -0
- data/lib/rom/lint/spec.rb +20 -0
- data/lib/rom/lint/test.rb +98 -0
- data/lib/rom/mapper.rb +32 -5
- data/lib/rom/mapper/attribute_dsl.rb +240 -0
- data/lib/rom/mapper/dsl.rb +100 -0
- data/lib/rom/mapper/model_dsl.rb +55 -0
- data/lib/rom/mapper_registry.rb +8 -1
- data/lib/rom/memory.rb +4 -0
- data/lib/rom/memory/commands.rb +46 -0
- data/lib/rom/memory/dataset.rb +72 -0
- data/lib/rom/memory/relation.rb +44 -0
- data/lib/rom/memory/repository.rb +62 -0
- data/lib/rom/memory/storage.rb +57 -0
- data/lib/rom/model_builder.rb +44 -5
- data/lib/rom/processor.rb +1 -1
- data/lib/rom/processor/transproc.rb +109 -16
- data/lib/rom/reader.rb +91 -39
- data/lib/rom/relation.rb +165 -26
- data/lib/rom/relation/composite.rb +132 -0
- data/lib/rom/relation/curried.rb +48 -0
- data/lib/rom/relation/lazy.rb +173 -0
- data/lib/rom/relation/loaded.rb +75 -0
- data/lib/rom/relation/registry_reader.rb +23 -0
- data/lib/rom/repository.rb +93 -34
- data/lib/rom/setup.rb +54 -98
- data/lib/rom/setup/finalize.rb +85 -76
- data/lib/rom/setup_dsl/command.rb +36 -0
- data/lib/rom/setup_dsl/command_dsl.rb +34 -0
- data/lib/rom/setup_dsl/mapper.rb +32 -0
- data/lib/rom/setup_dsl/mapper_dsl.rb +30 -0
- data/lib/rom/setup_dsl/relation.rb +21 -0
- data/lib/rom/setup_dsl/setup.rb +75 -0
- data/lib/rom/support/array_dataset.rb +38 -0
- data/lib/rom/support/class_builder.rb +44 -0
- data/lib/rom/support/class_macros.rb +56 -0
- data/lib/rom/support/data_proxy.rb +102 -0
- data/lib/rom/support/enumerable_dataset.rb +58 -0
- data/lib/rom/support/inflector.rb +73 -0
- data/lib/rom/support/options.rb +188 -0
- data/lib/rom/support/registry.rb +4 -8
- data/lib/rom/version.rb +1 -1
- data/rakelib/benchmark.rake +13 -0
- data/rakelib/mutant.rake +16 -0
- data/rakelib/rubocop.rake +18 -0
- data/rom.gemspec +4 -7
- data/spec/integration/commands/create_spec.rb +32 -24
- data/spec/integration/commands/delete_spec.rb +15 -7
- data/spec/integration/commands/update_spec.rb +13 -11
- data/spec/integration/mappers/deep_embedded_spec.rb +4 -11
- data/spec/integration/mappers/definition_dsl_spec.rb +31 -44
- data/spec/integration/mappers/embedded_spec.rb +9 -24
- data/spec/integration/mappers/group_spec.rb +22 -30
- data/spec/integration/mappers/prefixing_attributes_spec.rb +18 -23
- data/spec/integration/mappers/renaming_attributes_spec.rb +23 -38
- data/spec/integration/mappers/symbolizing_attributes_spec.rb +18 -24
- data/spec/integration/mappers/wrap_spec.rb +22 -30
- data/spec/integration/multi_repo_spec.rb +15 -37
- data/spec/integration/relations/reading_spec.rb +82 -14
- data/spec/integration/repositories/extending_relations_spec.rb +50 -0
- data/spec/integration/{adapters → repositories}/setting_logger_spec.rb +6 -5
- data/spec/integration/setup_spec.rb +59 -62
- data/spec/shared/enumerable_dataset.rb +49 -0
- data/spec/shared/one_behavior.rb +26 -0
- data/spec/shared/users_and_tasks.rb +11 -23
- data/spec/spec_helper.rb +16 -7
- data/spec/support/constant_leak_finder.rb +14 -0
- data/spec/test/memory_repository_lint_test.rb +27 -0
- data/spec/unit/rom/command_registry_spec.rb +44 -0
- data/spec/unit/rom/commands/result_spec.rb +14 -0
- data/spec/unit/rom/commands_spec.rb +174 -0
- data/spec/unit/rom/env_spec.rb +40 -7
- data/spec/unit/rom/global_spec.rb +14 -0
- data/spec/unit/rom/{mapper_builder_spec.rb → mapper/dsl_spec.rb} +52 -38
- data/spec/unit/rom/mapper_spec.rb +51 -10
- data/spec/unit/rom/{adapter/memory → memory}/dataset_spec.rb +6 -4
- data/spec/unit/rom/memory/repository_spec.rb +12 -0
- data/spec/unit/rom/memory/storage_spec.rb +45 -0
- data/spec/unit/rom/model_builder_spec.rb +4 -3
- data/spec/unit/rom/processor/transproc_spec.rb +1 -0
- data/spec/unit/rom/reader_spec.rb +97 -24
- data/spec/unit/rom/relation/composite_spec.rb +65 -0
- data/spec/unit/rom/relation/lazy_spec.rb +145 -0
- data/spec/unit/rom/relation/loaded_spec.rb +28 -0
- data/spec/unit/rom/relation_spec.rb +111 -6
- data/spec/unit/rom/repository_spec.rb +59 -9
- data/spec/unit/rom/setup_spec.rb +99 -11
- data/spec/unit/rom/support/array_dataset_spec.rb +59 -0
- data/spec/unit/rom/support/class_builder_spec.rb +42 -0
- data/spec/unit/rom/support/enumerable_dataset_spec.rb +17 -0
- data/spec/unit/rom/support/inflector_spec.rb +89 -0
- data/spec/unit/rom/support/options_spec.rb +119 -0
- metadata +74 -112
- data/lib/rom/adapter.rb +0 -191
- data/lib/rom/adapter/memory.rb +0 -32
- data/lib/rom/adapter/memory/commands.rb +0 -31
- data/lib/rom/adapter/memory/dataset.rb +0 -67
- data/lib/rom/adapter/memory/storage.rb +0 -26
- data/lib/rom/commands/with_options.rb +0 -18
- data/lib/rom/config.rb +0 -70
- data/lib/rom/mapper_builder.rb +0 -52
- data/lib/rom/mapper_builder/mapper_dsl.rb +0 -114
- data/lib/rom/mapper_builder/model_dsl.rb +0 -29
- data/lib/rom/reader_builder.rb +0 -48
- data/lib/rom/relation_builder.rb +0 -62
- data/lib/rom/setup/base_relation_dsl.rb +0 -46
- data/lib/rom/setup/command_dsl.rb +0 -46
- data/lib/rom/setup/mapper_dsl.rb +0 -19
- data/lib/rom/setup/relation_dsl.rb +0 -20
- data/lib/rom/setup/schema_dsl.rb +0 -33
- data/spec/integration/adapters/extending_relations_spec.rb +0 -41
- data/spec/integration/commands/try_spec.rb +0 -27
- data/spec/integration/schema_spec.rb +0 -77
- data/spec/unit/config_spec.rb +0 -60
- data/spec/unit/rom/adapter_spec.rb +0 -79
- data/spec/unit/rom_spec.rb +0 -14
data/lib/rom/model_builder.rb
CHANGED
@@ -1,8 +1,31 @@
|
|
1
1
|
module ROM
|
2
|
-
#
|
2
|
+
# Model builders can be used to build model classes for mappers
|
3
|
+
#
|
4
|
+
# This is used when you define a mapper and setup a model using :name option.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# # this will define User model for you
|
8
|
+
# class UserMapper < ROM::Mapper
|
9
|
+
# model name: 'User'
|
10
|
+
# attribute :id
|
11
|
+
# attribute :name
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# @private
|
3
15
|
class ModelBuilder
|
4
|
-
|
16
|
+
include Options
|
5
17
|
|
18
|
+
option :name, reader: true
|
19
|
+
|
20
|
+
attr_reader :const_name, :namespace, :klass
|
21
|
+
|
22
|
+
# Return model builder subclass based on type
|
23
|
+
#
|
24
|
+
# @param [Symbol] type
|
25
|
+
#
|
26
|
+
# @return [Class]
|
27
|
+
#
|
28
|
+
# @api private
|
6
29
|
def self.[](type)
|
7
30
|
case type
|
8
31
|
when :poro then PORO
|
@@ -11,14 +34,19 @@ module ROM
|
|
11
34
|
end
|
12
35
|
end
|
13
36
|
|
37
|
+
# Build a model class
|
38
|
+
#
|
39
|
+
# @return [Class]
|
40
|
+
#
|
41
|
+
# @api private
|
14
42
|
def self.call(*args)
|
15
43
|
new(*args).call
|
16
44
|
end
|
17
45
|
|
46
|
+
# @api private
|
18
47
|
def initialize(options = {})
|
19
|
-
|
48
|
+
super
|
20
49
|
|
21
|
-
name = options[:name]
|
22
50
|
if name
|
23
51
|
parts = name.split('::')
|
24
52
|
|
@@ -26,23 +54,34 @@ module ROM
|
|
26
54
|
|
27
55
|
@namespace =
|
28
56
|
if parts.any?
|
29
|
-
|
57
|
+
Inflector.constantize(parts.join('::'))
|
30
58
|
else
|
31
59
|
Object
|
32
60
|
end
|
33
61
|
end
|
34
62
|
end
|
35
63
|
|
64
|
+
# Define a model class constant
|
65
|
+
#
|
66
|
+
# @api private
|
36
67
|
def define_const
|
37
68
|
namespace.const_set(const_name, klass)
|
38
69
|
end
|
39
70
|
|
71
|
+
# Build a model class supporting specific attributes
|
72
|
+
#
|
73
|
+
# @return [Class]
|
74
|
+
#
|
75
|
+
# @api private
|
40
76
|
def call(attrs)
|
41
77
|
define_class(attrs)
|
42
78
|
define_const if const_name
|
43
79
|
@klass
|
44
80
|
end
|
45
81
|
|
82
|
+
# PORO model class builder
|
83
|
+
#
|
84
|
+
# @private
|
46
85
|
class PORO < ModelBuilder
|
47
86
|
def define_class(attrs)
|
48
87
|
@klass = Class.new
|
data/lib/rom/processor.rb
CHANGED
@@ -4,66 +4,146 @@ require 'rom/processor'
|
|
4
4
|
|
5
5
|
module ROM
|
6
6
|
class Processor
|
7
|
+
# Data mapping transformer builder using Transproc
|
8
|
+
#
|
9
|
+
# This builds a transproc function that is used to map a whole relation
|
10
|
+
#
|
11
|
+
# @see https://github.com/solnic/transproc too
|
12
|
+
#
|
13
|
+
# @private
|
7
14
|
class Transproc < Processor
|
8
15
|
include ::Transproc::Composer
|
9
16
|
|
10
|
-
|
17
|
+
# @return [Header] header from a mapper
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
attr_reader :header
|
11
21
|
|
22
|
+
# @return [Class] model class from a mapper
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
attr_reader :model
|
26
|
+
|
27
|
+
# @return [Hash] header's attribute mapping
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
attr_reader :mapping
|
31
|
+
|
32
|
+
# @return [Proc] row-processing proc
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
attr_reader :row_proc
|
36
|
+
|
37
|
+
# Default no-op row_proc
|
12
38
|
EMPTY_FN = -> tuple { tuple }.freeze
|
13
39
|
|
40
|
+
# Build a transproc function from the header
|
41
|
+
#
|
42
|
+
# @param [ROM::Header] header
|
43
|
+
#
|
44
|
+
# @return [Transproc::Function]
|
45
|
+
#
|
46
|
+
# @api private
|
14
47
|
def self.build(header)
|
15
48
|
new(header).to_transproc
|
16
49
|
end
|
17
50
|
|
51
|
+
# @api private
|
18
52
|
def initialize(header)
|
19
53
|
@header = header
|
20
54
|
@model = header.model
|
21
55
|
@mapping = header.mapping
|
22
|
-
|
56
|
+
initialize_row_proc
|
23
57
|
end
|
24
58
|
|
59
|
+
# Coerce mapper header to a transproc data mapping function
|
60
|
+
#
|
61
|
+
# @return [Transproc::Function]
|
62
|
+
#
|
63
|
+
# @api private
|
25
64
|
def to_transproc
|
26
65
|
compose(EMPTY_FN) do |ops|
|
27
66
|
ops << header.groups.map { |attr| visit_group(attr, true) }
|
28
|
-
ops << t(:map_array
|
67
|
+
ops << t(:map_array, row_proc) if row_proc
|
29
68
|
end
|
30
69
|
end
|
31
70
|
|
32
71
|
private
|
33
72
|
|
73
|
+
# Visit an attribute from the header
|
74
|
+
#
|
75
|
+
# This forwards to a specialized visitor based on the attribute type
|
76
|
+
#
|
77
|
+
# @param [Header::Attribute] attribute
|
78
|
+
#
|
79
|
+
# @api private
|
34
80
|
def visit(attribute)
|
35
81
|
type = attribute.class.name.split('::').last.downcase
|
36
82
|
send("visit_#{type}", attribute)
|
37
83
|
end
|
38
84
|
|
85
|
+
# Visit plain attribute
|
86
|
+
#
|
87
|
+
# If it's a typed attribute a coercion transformation is added
|
88
|
+
#
|
89
|
+
# @param [Header::Attribute] attribute
|
90
|
+
#
|
91
|
+
# @api private
|
39
92
|
def visit_attribute(attribute)
|
40
93
|
if attribute.typed?
|
41
|
-
t(:map_key
|
94
|
+
t(:map_key, attribute.name, t(:"to_#{attribute.type}"))
|
42
95
|
end
|
43
96
|
end
|
44
97
|
|
98
|
+
# Visit hash attribute
|
99
|
+
#
|
100
|
+
# @param [Header::Attribute::Hash] attribute
|
101
|
+
#
|
102
|
+
# @api private
|
45
103
|
def visit_hash(attribute)
|
46
|
-
|
47
|
-
t(:map_key
|
104
|
+
with_row_proc(attribute) do |row_proc|
|
105
|
+
t(:map_key, attribute.name, row_proc)
|
48
106
|
end
|
49
107
|
end
|
50
108
|
|
109
|
+
# Visit array attribute
|
110
|
+
#
|
111
|
+
# @param [Header::Attribute::Array] attribute
|
112
|
+
#
|
113
|
+
# @api private
|
51
114
|
def visit_array(attribute)
|
52
|
-
|
53
|
-
t(:map_key
|
115
|
+
with_row_proc(attribute) do |row_proc|
|
116
|
+
t(:map_key, attribute.name, t(:map_array, row_proc))
|
54
117
|
end
|
55
118
|
end
|
56
119
|
|
120
|
+
# Visit wrapped hash attribute
|
121
|
+
#
|
122
|
+
# :nest transformation is added to handle wrapping
|
123
|
+
#
|
124
|
+
# @param [Header::Attribute::Wrap] attribute
|
125
|
+
#
|
126
|
+
# @api private
|
57
127
|
def visit_wrap(attribute)
|
58
128
|
name = attribute.name
|
59
129
|
keys = attribute.tuple_keys
|
60
130
|
|
61
131
|
compose do |ops|
|
62
|
-
ops << t(:nest
|
132
|
+
ops << t(:nest, name, keys)
|
63
133
|
ops << visit_hash(attribute)
|
64
134
|
end
|
65
135
|
end
|
66
136
|
|
137
|
+
# Visit group hash attribute
|
138
|
+
#
|
139
|
+
# :group transformation is added to handle grouping during preprocessing.
|
140
|
+
# Otherwise we simply use array visitor for the attribute.
|
141
|
+
#
|
142
|
+
# @param [Header::Attribute::Group] attribute
|
143
|
+
# @param [Boolean] preprocess true if we are building a relation preprocessing
|
144
|
+
# function that is applied to the whole relation
|
145
|
+
#
|
146
|
+
# @api private
|
67
147
|
def visit_group(attribute, preprocess = false)
|
68
148
|
if preprocess
|
69
149
|
name = attribute.name
|
@@ -76,7 +156,7 @@ module ROM
|
|
76
156
|
ops << t(:group, name, keys)
|
77
157
|
|
78
158
|
ops << other.map { |attr|
|
79
|
-
t(:map_array
|
159
|
+
t(:map_array, t(:map_key, name, visit_group(attr, true)))
|
80
160
|
}
|
81
161
|
end
|
82
162
|
else
|
@@ -84,19 +164,32 @@ module ROM
|
|
84
164
|
end
|
85
165
|
end
|
86
166
|
|
87
|
-
|
88
|
-
|
89
|
-
|
167
|
+
# Build row_proc
|
168
|
+
#
|
169
|
+
# This transproc function is applied to each row in a dataset
|
170
|
+
#
|
171
|
+
# @api private
|
172
|
+
def initialize_row_proc
|
173
|
+
@row_proc = compose do |ops|
|
174
|
+
ops << t(:map_hash, mapping) if header.aliased?
|
90
175
|
ops << header.map { |attr| visit(attr) }
|
91
176
|
ops << t(-> tuple { model.new(tuple) }) if model
|
92
177
|
end
|
93
178
|
end
|
94
179
|
|
95
|
-
|
96
|
-
|
97
|
-
|
180
|
+
# Yield row proc for a given attribute if any
|
181
|
+
#
|
182
|
+
# @param [Header::Attribute] attribute
|
183
|
+
#
|
184
|
+
# @api private
|
185
|
+
def with_row_proc(attribute)
|
186
|
+
row_proc = new(attribute.header).row_proc
|
187
|
+
yield(row_proc) if row_proc
|
98
188
|
end
|
99
189
|
|
190
|
+
# Return a new instance of the processor
|
191
|
+
#
|
192
|
+
# @api private
|
100
193
|
def new(*args)
|
101
194
|
self.class.new(*args)
|
102
195
|
end
|
data/lib/rom/reader.rb
CHANGED
@@ -5,11 +5,16 @@ module ROM
|
|
5
5
|
#
|
6
6
|
# @api public
|
7
7
|
class Reader
|
8
|
-
MapperMissingError = Class.new(StandardError)
|
9
|
-
|
10
8
|
include Enumerable
|
11
9
|
include Equalizer.new(:path, :relation, :mapper)
|
12
10
|
|
11
|
+
# Map relation to an array using a mapper
|
12
|
+
#
|
13
|
+
# @return [Array]
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
alias_method :to_ary, :to_a
|
17
|
+
|
13
18
|
# @return [String] access path used to read a relation
|
14
19
|
#
|
15
20
|
# @api private
|
@@ -30,58 +35,55 @@ module ROM
|
|
30
35
|
# @api private
|
31
36
|
attr_reader :mapper
|
32
37
|
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# This method defines public methods on the class narrowing down data access
|
36
|
-
# only to the methods exposed by a given relation
|
38
|
+
# Builds a reader instance for the provided relation
|
37
39
|
#
|
38
40
|
# @param [Symbol] name of the root relation
|
39
41
|
# @param [Relation] relation that the reader will use
|
40
|
-
# @param [MapperRegistry] registry of mappers
|
41
|
-
# @param [Array<Symbol>] a list of method names exposed by the relation
|
42
|
+
# @param [MapperRegistry] mappers registry of mappers
|
43
|
+
# @param [Array<Symbol>] method_names a list of method names exposed by the relation
|
42
44
|
#
|
43
45
|
# @return [Reader]
|
44
46
|
#
|
45
47
|
# @api private
|
46
48
|
def self.build(name, relation, mappers, method_names = [])
|
47
|
-
klass =
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
52
|
-
def self.name
|
53
|
-
#{klass_name.inspect}
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.inspect
|
57
|
-
name
|
58
|
-
end
|
49
|
+
klass = build_class(relation, method_names)
|
50
|
+
klass.new(name, relation, mappers)
|
51
|
+
end
|
59
52
|
|
60
|
-
|
61
|
-
|
53
|
+
# Build a reader subclass for the relation
|
54
|
+
#
|
55
|
+
# This method defines public methods on the class narrowing down data access
|
56
|
+
# only to the methods exposed by a given relation
|
57
|
+
#
|
58
|
+
# @param [Relation] relation that the reader will use
|
59
|
+
# @param [Array<Symbol>] method_names a list of method names exposed by the relation
|
60
|
+
#
|
61
|
+
# @return [Class]
|
62
|
+
#
|
63
|
+
# @api private
|
64
|
+
def self.build_class(relation, method_names)
|
65
|
+
klass_name = "#{Reader.name}[#{Inflector.camelize(relation.name)}]"
|
66
|
+
|
67
|
+
ClassBuilder.new(name: klass_name, parent: Reader).call do |klass|
|
68
|
+
method_names.each do |method_name|
|
69
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
70
|
+
def #{method_name}(*args, &block)
|
71
|
+
new_relation = relation.send(#{method_name.inspect}, *args, &block)
|
72
|
+
self.class.new(
|
73
|
+
new_path(#{method_name.to_s.inspect}), new_relation, mappers
|
74
|
+
)
|
75
|
+
end
|
76
|
+
RUBY
|
62
77
|
end
|
63
|
-
RUBY
|
64
|
-
|
65
|
-
method_names.each do |method_name|
|
66
|
-
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
67
|
-
def #{method_name}(*args, &block)
|
68
|
-
new_relation = relation.send(#{method_name.inspect}, *args, &block)
|
69
|
-
self.class.new(
|
70
|
-
new_path(#{method_name.to_s.inspect}), new_relation, mappers
|
71
|
-
)
|
72
|
-
end
|
73
|
-
RUBY
|
74
78
|
end
|
75
|
-
|
76
|
-
klass.new(name, relation, mappers)
|
77
79
|
end
|
78
80
|
|
79
81
|
# @api private
|
80
|
-
def initialize(path, relation, mappers =
|
82
|
+
def initialize(path, relation, mappers, mapper = nil)
|
81
83
|
@path = path.to_s
|
82
84
|
@relation = relation
|
83
85
|
@mappers = mappers
|
84
|
-
@mapper = mappers.by_path(@path)
|
86
|
+
@mapper = mapper || mappers.by_path(@path)
|
85
87
|
end
|
86
88
|
|
87
89
|
# @api private
|
@@ -101,13 +103,63 @@ module ROM
|
|
101
103
|
#
|
102
104
|
# @api public
|
103
105
|
def each
|
104
|
-
mapper.
|
106
|
+
mapper.call(relation).each { |tuple| yield(tuple) }
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns a single tuple from the relation if there is one.
|
110
|
+
#
|
111
|
+
# @raise [ROM::TupleCountMismatchError] if the relation contains more than
|
112
|
+
# one tuple
|
113
|
+
#
|
114
|
+
# @api public
|
115
|
+
def one
|
116
|
+
if relation.count > 1
|
117
|
+
raise(
|
118
|
+
TupleCountMismatchError,
|
119
|
+
'The relation consists of more than one tuple'
|
120
|
+
)
|
121
|
+
else
|
122
|
+
mapper.call(relation).first
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Like [one], but additionally raises an error if the relation is empty.
|
127
|
+
#
|
128
|
+
# @raise [ROM::TupleCountMismatchError] if the relation does not contain
|
129
|
+
# exactly one tuple
|
130
|
+
#
|
131
|
+
# @api public
|
132
|
+
def one!
|
133
|
+
one || raise(
|
134
|
+
TupleCountMismatchError,
|
135
|
+
'The relation does not contain any tuples'
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Map tuples using a specific mapper if name is provided
|
140
|
+
#
|
141
|
+
# Defaults to Enumerable#map behavior
|
142
|
+
#
|
143
|
+
# @example
|
144
|
+
#
|
145
|
+
# rom.read(:users).map(:my_mapper_name)
|
146
|
+
# rom.read(:users).map { |user| ... }
|
147
|
+
#
|
148
|
+
# @return [Array,Reader]
|
149
|
+
#
|
150
|
+
# @api public
|
151
|
+
def map(*args)
|
152
|
+
if args.any?
|
153
|
+
mappers[args[0]].call(relation)
|
154
|
+
else
|
155
|
+
super
|
156
|
+
end
|
105
157
|
end
|
106
158
|
|
107
159
|
private
|
108
160
|
|
109
161
|
# @api private
|
110
|
-
def method_missing(name)
|
162
|
+
def method_missing(name, *)
|
111
163
|
raise(
|
112
164
|
NoRelationError,
|
113
165
|
"undefined relation #{name.inspect} within #{path.inspect}"
|