rom 0.5.0 → 0.6.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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}"
|