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/relation.rb
CHANGED
@@ -1,50 +1,163 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'rom/relation/registry_reader'
|
3
|
+
require 'rom/relation/lazy'
|
4
|
+
require 'rom/relation/curried'
|
5
|
+
|
1
6
|
module ROM
|
2
7
|
# Base relation class
|
3
8
|
#
|
4
|
-
# Relation is a proxy for the dataset object provided by the
|
5
|
-
# forwards every method to the dataset
|
6
|
-
# underlying
|
7
|
-
# considered private and should not be used outside of the
|
9
|
+
# Relation is a proxy for the dataset object provided by the repository. It
|
10
|
+
# forwards every method to the dataset, which is why the "native" interface of
|
11
|
+
# the underlying repository is available in the relation. This interface,
|
12
|
+
# however, is considered private and should not be used outside of the
|
13
|
+
# relation instance.
|
8
14
|
#
|
9
15
|
# ROM builds sub-classes of this class for every relation defined in the env
|
10
|
-
# for easy inspection and extensibility - every
|
16
|
+
# for easy inspection and extensibility - every repository can provide extensions
|
11
17
|
# for those sub-classes but there is always a vanilla relation instance stored
|
12
18
|
# in the schema registry.
|
13
19
|
#
|
14
|
-
# Relation instances also have access to the experimental ROM::RA interface
|
15
|
-
# giving in-memory relational operations that are very handy, especially when
|
16
|
-
# dealing with joined relations or data coming from different sources.
|
17
|
-
#
|
18
20
|
# @api public
|
19
21
|
class Relation
|
20
|
-
|
21
|
-
|
22
|
+
extend ClassMacros
|
23
|
+
|
24
|
+
include Options
|
25
|
+
include Equalizer.new(:dataset)
|
26
|
+
|
27
|
+
defines :repository, :dataset, :register_as, :exposed_relations
|
28
|
+
|
29
|
+
repository :default
|
30
|
+
|
31
|
+
attr_reader :name, :dataset, :exposed_relations
|
32
|
+
|
33
|
+
# Register adapter relation subclasses during setup phase
|
34
|
+
#
|
35
|
+
# In adition those subclasses are extended with an interface for accessing
|
36
|
+
# relation registry and to define `register_as` setting
|
37
|
+
#
|
38
|
+
# @api private
|
39
|
+
def self.inherited(klass)
|
40
|
+
super
|
41
|
+
|
42
|
+
return if self == ROM::Relation
|
43
|
+
|
44
|
+
klass.class_eval do
|
45
|
+
include ROM::Relation::RegistryReader
|
46
|
+
|
47
|
+
dataset(default_name)
|
48
|
+
exposed_relations Set.new
|
49
|
+
|
50
|
+
def self.register_as(value = Undefined)
|
51
|
+
if value == Undefined
|
52
|
+
@register_as || dataset
|
53
|
+
else
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
22
57
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
58
|
+
def self.method_added(name)
|
59
|
+
super
|
60
|
+
exposed_relations << name if public_instance_methods.include?(name)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
ROM.register_relation(klass)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Return adapter-specific relation subclass
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# ROM::Relation[:memory]
|
71
|
+
# # => ROM::Memory::Relation
|
72
|
+
#
|
73
|
+
# @return [Class]
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def self.[](type)
|
77
|
+
ROM.adapters.fetch(type).const_get(:Relation)
|
30
78
|
end
|
31
79
|
|
32
|
-
#
|
80
|
+
# Dynamically define a method that will forward to the dataset and wrap
|
81
|
+
# response in the relation itself
|
82
|
+
#
|
83
|
+
# @example
|
84
|
+
# class SomeAdapterRelation < ROM::Relation
|
85
|
+
# forward :super_query
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# @api public
|
89
|
+
def self.forward(*methods)
|
90
|
+
methods.each do |method|
|
91
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
92
|
+
def #{method}(*args, &block)
|
93
|
+
__new__(dataset.__send__(:#{method}, *args, &block))
|
94
|
+
end
|
95
|
+
RUBY
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Return default relation name used for `register_as` setting
|
100
|
+
#
|
101
|
+
# @return [Symbol]
|
33
102
|
#
|
34
103
|
# @api private
|
35
|
-
|
104
|
+
def self.default_name
|
105
|
+
return unless name
|
106
|
+
Inflector.underscore(name).gsub('/', '_').to_sym
|
107
|
+
end
|
36
108
|
|
37
|
-
#
|
109
|
+
# Build relation registry of specified descendant classes
|
110
|
+
#
|
111
|
+
# This is used by the setup
|
112
|
+
#
|
113
|
+
# @param [Hash] repositories
|
114
|
+
# @param [Array] descendants a list of relation descendants
|
115
|
+
#
|
116
|
+
# @return [Hash]
|
38
117
|
#
|
39
118
|
# @api private
|
40
|
-
def self.
|
41
|
-
|
119
|
+
def self.registry(repositories, descendants)
|
120
|
+
registry = {}
|
121
|
+
|
122
|
+
descendants.each do |klass|
|
123
|
+
# TODO: raise a meaningful error here and add spec covering the case
|
124
|
+
# where klass' repository points to non-existant repo
|
125
|
+
repository = repositories.fetch(klass.repository)
|
126
|
+
dataset = repository.dataset(klass.dataset)
|
127
|
+
|
128
|
+
relation = klass.new(dataset, __registry__: registry)
|
129
|
+
|
130
|
+
name = klass.register_as
|
131
|
+
|
132
|
+
if registry.key?(name)
|
133
|
+
raise RelationAlreadyDefinedError,
|
134
|
+
"Relation with `register_as #{name.inspect}` registered more " \
|
135
|
+
"than once"
|
136
|
+
end
|
137
|
+
|
138
|
+
registry[name] = relation
|
139
|
+
end
|
140
|
+
|
141
|
+
registry.each_value do |relation|
|
142
|
+
relation.class.finalize(registry, relation)
|
143
|
+
end
|
144
|
+
|
145
|
+
registry
|
42
146
|
end
|
43
147
|
|
44
148
|
# @api private
|
45
|
-
def initialize(dataset,
|
149
|
+
def initialize(dataset, options = {})
|
150
|
+
@dataset = dataset
|
151
|
+
@name = self.class.dataset
|
152
|
+
@exposed_relations = self.class.exposed_relations
|
46
153
|
super
|
47
|
-
|
154
|
+
end
|
155
|
+
|
156
|
+
# Hook to finalize a relation after its instance was created
|
157
|
+
#
|
158
|
+
# @api private
|
159
|
+
def self.finalize(_env, _relation)
|
160
|
+
# noop
|
48
161
|
end
|
49
162
|
|
50
163
|
# Yield dataset tuples
|
@@ -54,7 +167,33 @@ module ROM
|
|
54
167
|
# @api private
|
55
168
|
def each(&block)
|
56
169
|
return to_enum unless block
|
57
|
-
dataset.each(
|
170
|
+
dataset.each { |tuple| yield(tuple) }
|
171
|
+
end
|
172
|
+
|
173
|
+
# Materialize relation into an array
|
174
|
+
#
|
175
|
+
# @return [Array<Hash>]
|
176
|
+
#
|
177
|
+
# @api public
|
178
|
+
def to_a
|
179
|
+
to_enum.to_a
|
180
|
+
end
|
181
|
+
|
182
|
+
# @api private
|
183
|
+
def repository
|
184
|
+
self.class.repository
|
185
|
+
end
|
186
|
+
|
187
|
+
# @api public
|
188
|
+
def to_lazy(*args)
|
189
|
+
Lazy.new(self, *args)
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
# @api private
|
195
|
+
def __new__(dataset, new_opts = {})
|
196
|
+
self.class.new(dataset, options.merge(new_opts))
|
58
197
|
end
|
59
198
|
end
|
60
199
|
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'rom/relation/loaded'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Relation
|
5
|
+
# Left-to-right relation composition used for data-pipelining
|
6
|
+
#
|
7
|
+
# @api public
|
8
|
+
class Composite
|
9
|
+
include Equalizer.new(:left, :right)
|
10
|
+
|
11
|
+
# @return [Lazy,Curried,Composite,#call]
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
attr_reader :left
|
15
|
+
|
16
|
+
# @return [Lazy,Curried,Composite,#call]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
attr_reader :right
|
20
|
+
|
21
|
+
# @api private
|
22
|
+
def initialize(left, right)
|
23
|
+
@left = left
|
24
|
+
@right = right
|
25
|
+
end
|
26
|
+
|
27
|
+
# Compose with another callable object
|
28
|
+
#
|
29
|
+
# @param [#call]
|
30
|
+
#
|
31
|
+
# @return [Composite]
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
def >>(other)
|
35
|
+
self.class.new(self, other)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Call the pipeline by passing results from left to right
|
39
|
+
#
|
40
|
+
# Optional args are passed to the left object
|
41
|
+
#
|
42
|
+
# @return [Loaded]
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def call(*args)
|
46
|
+
relation = left.call(*args)
|
47
|
+
response = right.call(relation)
|
48
|
+
|
49
|
+
if relation.is_a?(Loaded)
|
50
|
+
relation.new(response)
|
51
|
+
else
|
52
|
+
Loaded.new(relation, response)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
alias_method :[], :call
|
56
|
+
|
57
|
+
# Coerce composite relation to an array
|
58
|
+
#
|
59
|
+
# @return [Array]
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def to_a
|
63
|
+
call.to_a
|
64
|
+
end
|
65
|
+
alias_method :to_ary, :to_a
|
66
|
+
|
67
|
+
# Delegate to loaded relation and return one object
|
68
|
+
#
|
69
|
+
# @return [Object]
|
70
|
+
#
|
71
|
+
# @see Loaded#one
|
72
|
+
#
|
73
|
+
# @api public
|
74
|
+
def one
|
75
|
+
call.one
|
76
|
+
end
|
77
|
+
|
78
|
+
# Delegate to loaded relation and return one object
|
79
|
+
#
|
80
|
+
# @return [Object]
|
81
|
+
#
|
82
|
+
# @see Loaded#one
|
83
|
+
#
|
84
|
+
# @api public
|
85
|
+
def one!
|
86
|
+
call.one!
|
87
|
+
end
|
88
|
+
|
89
|
+
# Yield composite relation objects
|
90
|
+
#
|
91
|
+
# @yield [Object]
|
92
|
+
#
|
93
|
+
# @api public
|
94
|
+
def each(&block)
|
95
|
+
return to_enum unless block
|
96
|
+
call.each { |object| yield(object) }
|
97
|
+
end
|
98
|
+
|
99
|
+
# Return first object from the called relation
|
100
|
+
#
|
101
|
+
# @return [Object]
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def first
|
105
|
+
call.first
|
106
|
+
end
|
107
|
+
|
108
|
+
# @api private
|
109
|
+
def respond_to_missing?(name, include_private = false)
|
110
|
+
left.respond_to?(name) || super
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# Allow calling methods on the left side object
|
116
|
+
#
|
117
|
+
# @api private
|
118
|
+
def method_missing(name, *args, &block)
|
119
|
+
if left.respond_to?(name)
|
120
|
+
response = left.__send__(name, *args, &block)
|
121
|
+
if response.is_a?(left.class)
|
122
|
+
self.class.new(response, right)
|
123
|
+
else
|
124
|
+
response
|
125
|
+
end
|
126
|
+
else
|
127
|
+
super
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rom/relation/lazy'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Relation
|
5
|
+
class Curried < Lazy
|
6
|
+
option :name, type: Symbol, reader: true
|
7
|
+
option :arity, type: Integer, reader: true, default: -1
|
8
|
+
option :curry_args, type: Array, reader: true, default: EMPTY_ARRAY
|
9
|
+
|
10
|
+
# Load relation if args match the arity
|
11
|
+
#
|
12
|
+
# @return [Loaded,Lazy,Curried]
|
13
|
+
# @see Lazy#call
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
def call(*args)
|
17
|
+
if arity != -1
|
18
|
+
all_args = curry_args + args
|
19
|
+
|
20
|
+
if arity == all_args.size
|
21
|
+
Loaded.new(relation.__send__(name, *all_args))
|
22
|
+
else
|
23
|
+
__new__(relation, curry_args: all_args)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
alias_method :[], :call
|
30
|
+
|
31
|
+
# Return if this lazy relation is curried
|
32
|
+
#
|
33
|
+
# @return [true]
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
def curried?
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def __new__(relation, new_opts = {})
|
44
|
+
Curried.new(relation, options.merge(new_opts))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'rom/relation/loaded'
|
2
|
+
require 'rom/relation/composite'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
class Relation
|
6
|
+
# Lazy relation wraps canonical relation for data-pipelining
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# ROM.setup(:memory)
|
10
|
+
#
|
11
|
+
# class Users < ROM::Relation[:memory]
|
12
|
+
# def by_name(name)
|
13
|
+
# restrict(name: name)
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# rom = ROM.finalize.env
|
18
|
+
#
|
19
|
+
# rom.relations.users << { name: 'Jane' }
|
20
|
+
# rom.relations.users << { name: 'Joe' }
|
21
|
+
#
|
22
|
+
# mapper = proc { |users| users.map { |user| user[:name] } }
|
23
|
+
# users = rom.relation(:users)
|
24
|
+
#
|
25
|
+
# (users.by_name >> mapper)['Jane'].inspect # => ["Jane"]
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
class Lazy
|
29
|
+
include Equalizer.new(:relation, :options)
|
30
|
+
include Options
|
31
|
+
|
32
|
+
option :mappers, reader: true, default: EMPTY_HASH
|
33
|
+
|
34
|
+
# @return [Relation]
|
35
|
+
#
|
36
|
+
# @api private
|
37
|
+
attr_reader :relation
|
38
|
+
|
39
|
+
# Map of exposed relation methods
|
40
|
+
#
|
41
|
+
# @return [Hash<Symbol=>TrueClass>]
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
attr_reader :methods
|
45
|
+
|
46
|
+
# @api private
|
47
|
+
def initialize(relation, options = {})
|
48
|
+
super
|
49
|
+
@relation = relation
|
50
|
+
@methods = @relation.exposed_relations
|
51
|
+
end
|
52
|
+
|
53
|
+
# Compose two relation with a left-to-right composition
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# users.by_name('Jane') >> tasks.for_users
|
57
|
+
#
|
58
|
+
# @param [Relation] other The right relation
|
59
|
+
#
|
60
|
+
# @return [Relation::Composite]
|
61
|
+
#
|
62
|
+
# @api public
|
63
|
+
def >>(other)
|
64
|
+
Composite.new(self, other)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Build a relation pipeline using registered mappers
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# rom.relation(:users).map_with(:json_serializer)
|
71
|
+
#
|
72
|
+
# @return [Relation::Composite]
|
73
|
+
#
|
74
|
+
# @api public
|
75
|
+
def map_with(*names)
|
76
|
+
[self, *names.map { |name| mappers[name] }]
|
77
|
+
.reduce { |a, e| Composite.new(a, e) }
|
78
|
+
end
|
79
|
+
alias_method :as, :map_with
|
80
|
+
|
81
|
+
# Coerce lazy relation to an array
|
82
|
+
#
|
83
|
+
# @return [Array]
|
84
|
+
#
|
85
|
+
# @api public
|
86
|
+
def to_a
|
87
|
+
call.to_a
|
88
|
+
end
|
89
|
+
alias_method :to_ary, :to_a
|
90
|
+
|
91
|
+
# Load relation
|
92
|
+
#
|
93
|
+
# @return [Relation::Loaded]
|
94
|
+
#
|
95
|
+
# @api public
|
96
|
+
def call
|
97
|
+
Loaded.new(relation)
|
98
|
+
end
|
99
|
+
alias_method :[], :call
|
100
|
+
|
101
|
+
# Delegate to loaded relation and return one object
|
102
|
+
#
|
103
|
+
# @return [Object]
|
104
|
+
#
|
105
|
+
# @see Loaded#one
|
106
|
+
#
|
107
|
+
# @api public
|
108
|
+
def one
|
109
|
+
call.one
|
110
|
+
end
|
111
|
+
|
112
|
+
# Delegate to loaded relation and return one object
|
113
|
+
#
|
114
|
+
# @return [Object]
|
115
|
+
#
|
116
|
+
# @see Loaded#one
|
117
|
+
#
|
118
|
+
# @api public
|
119
|
+
def one!
|
120
|
+
call.one!
|
121
|
+
end
|
122
|
+
|
123
|
+
# @api private
|
124
|
+
def respond_to_missing?(name, include_private = false)
|
125
|
+
methods.include?(name) || super
|
126
|
+
end
|
127
|
+
|
128
|
+
# Return if this lazy relation is curried
|
129
|
+
#
|
130
|
+
# @return [false]
|
131
|
+
#
|
132
|
+
# @api private
|
133
|
+
def curried?
|
134
|
+
false
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
# Forward methods to the underlaying relation
|
140
|
+
#
|
141
|
+
# Auto-curry relations when args size doesn't match arity
|
142
|
+
#
|
143
|
+
# @return [Lazy,Curried]
|
144
|
+
#
|
145
|
+
# @api private
|
146
|
+
def method_missing(meth, *args, &block)
|
147
|
+
if !methods.include?(meth) || (curried? && name != meth)
|
148
|
+
super
|
149
|
+
else
|
150
|
+
arity = relation.method(meth).arity
|
151
|
+
|
152
|
+
if arity == -1 || arity == args.size
|
153
|
+
response = relation.__send__(meth, *args, &block)
|
154
|
+
if response.is_a?(Relation)
|
155
|
+
__new__(response)
|
156
|
+
else
|
157
|
+
response
|
158
|
+
end
|
159
|
+
else
|
160
|
+
Curried.new(relation, name: meth, curry_args: args, arity: arity)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Return new lazy relation with updated options
|
166
|
+
#
|
167
|
+
# @api private
|
168
|
+
def __new__(relation, new_opts = {})
|
169
|
+
Lazy.new(relation, options.merge(new_opts))
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|