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/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
|