rom 5.4.2 → 6.0.0.alpha1

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.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -71
  3. data/LICENSE +1 -1
  4. data/README.md +7 -6
  5. data/lib/rom/array_dataset.rb +46 -0
  6. data/lib/rom/associations/abstract.rb +217 -0
  7. data/lib/rom/associations/definitions/abstract.rb +150 -0
  8. data/lib/rom/associations/definitions/many_to_many.rb +29 -0
  9. data/lib/rom/associations/definitions/many_to_one.rb +14 -0
  10. data/lib/rom/associations/definitions/one_to_many.rb +14 -0
  11. data/lib/rom/associations/definitions/one_to_one.rb +14 -0
  12. data/lib/rom/associations/definitions/one_to_one_through.rb +14 -0
  13. data/lib/rom/associations/definitions.rb +7 -0
  14. data/lib/rom/associations/many_to_many.rb +128 -0
  15. data/lib/rom/associations/many_to_one.rb +65 -0
  16. data/lib/rom/associations/one_to_many.rb +65 -0
  17. data/lib/rom/associations/one_to_one.rb +13 -0
  18. data/lib/rom/associations/one_to_one_through.rb +13 -0
  19. data/lib/rom/associations/through_identifier.rb +41 -0
  20. data/lib/rom/attribute.rb +425 -0
  21. data/lib/rom/auto_curry.rb +70 -0
  22. data/lib/rom/cache.rb +87 -0
  23. data/lib/rom/changeset/associated.rb +110 -0
  24. data/lib/rom/changeset/create.rb +18 -0
  25. data/lib/rom/changeset/delete.rb +15 -0
  26. data/lib/rom/changeset/extensions/relation.rb +26 -0
  27. data/lib/rom/changeset/pipe.rb +81 -0
  28. data/lib/rom/changeset/pipe_registry.rb +27 -0
  29. data/lib/rom/changeset/stateful.rb +285 -0
  30. data/lib/rom/changeset/update.rb +81 -0
  31. data/lib/rom/changeset.rb +185 -0
  32. data/lib/rom/command.rb +351 -0
  33. data/lib/rom/command_compiler.rb +201 -0
  34. data/lib/rom/command_proxy.rb +36 -0
  35. data/lib/rom/commands/class_interface.rb +236 -0
  36. data/lib/rom/commands/composite.rb +55 -0
  37. data/lib/rom/commands/create.rb +15 -0
  38. data/lib/rom/commands/delete.rb +16 -0
  39. data/lib/rom/commands/graph/class_interface.rb +64 -0
  40. data/lib/rom/commands/graph/input_evaluator.rb +94 -0
  41. data/lib/rom/commands/graph.rb +88 -0
  42. data/lib/rom/commands/lazy/create.rb +35 -0
  43. data/lib/rom/commands/lazy/delete.rb +39 -0
  44. data/lib/rom/commands/lazy/update.rb +46 -0
  45. data/lib/rom/commands/lazy.rb +106 -0
  46. data/lib/rom/commands/update.rb +16 -0
  47. data/lib/rom/commands.rb +5 -0
  48. data/lib/rom/compat/auto_registration.rb +115 -0
  49. data/lib/rom/compat/auto_registration_strategies/base.rb +29 -0
  50. data/lib/rom/compat/auto_registration_strategies/custom_namespace.rb +84 -0
  51. data/lib/rom/compat/auto_registration_strategies/no_namespace.rb +33 -0
  52. data/lib/rom/compat/auto_registration_strategies/with_namespace.rb +29 -0
  53. data/lib/rom/compat/command.rb +74 -0
  54. data/lib/rom/compat/components/dsl/schema.rb +130 -0
  55. data/lib/rom/compat/components.rb +91 -0
  56. data/lib/rom/compat/global.rb +17 -0
  57. data/lib/rom/compat/mapper.rb +22 -0
  58. data/lib/rom/compat/registries.rb +47 -0
  59. data/lib/rom/compat/relation.rb +40 -0
  60. data/lib/rom/compat/schema/dsl.rb +260 -0
  61. data/lib/rom/compat/setting_proxy.rb +44 -0
  62. data/lib/rom/compat/setup.rb +151 -0
  63. data/lib/rom/compat/transformer.rb +49 -0
  64. data/lib/rom/compat.rb +22 -0
  65. data/lib/rom/components/association.rb +26 -0
  66. data/lib/rom/components/command.rb +24 -0
  67. data/lib/rom/components/core.rb +148 -0
  68. data/lib/rom/components/dataset.rb +60 -0
  69. data/lib/rom/components/dsl/association.rb +47 -0
  70. data/lib/rom/components/dsl/command.rb +60 -0
  71. data/lib/rom/components/dsl/core.rb +126 -0
  72. data/lib/rom/components/dsl/dataset.rb +33 -0
  73. data/lib/rom/components/dsl/gateway.rb +14 -0
  74. data/lib/rom/components/dsl/mapper.rb +70 -0
  75. data/lib/rom/components/dsl/relation.rb +49 -0
  76. data/lib/rom/components/dsl/schema.rb +150 -0
  77. data/lib/rom/components/dsl/view.rb +82 -0
  78. data/lib/rom/components/dsl.rb +255 -0
  79. data/lib/rom/components/gateway.rb +50 -0
  80. data/lib/rom/components/mapper.rb +29 -0
  81. data/lib/rom/components/provider.rb +160 -0
  82. data/lib/rom/components/registry.rb +154 -0
  83. data/lib/rom/components/relation.rb +41 -0
  84. data/lib/rom/components/schema.rb +61 -0
  85. data/lib/rom/components/view.rb +55 -0
  86. data/lib/rom/components.rb +55 -0
  87. data/lib/rom/configuration_dsl.rb +4 -0
  88. data/lib/rom/constants.rb +135 -0
  89. data/lib/rom/container.rb +182 -0
  90. data/lib/rom/core.rb +125 -0
  91. data/lib/rom/data_proxy.rb +97 -0
  92. data/lib/rom/enumerable_dataset.rb +70 -0
  93. data/lib/rom/gateway.rb +232 -0
  94. data/lib/rom/global.rb +56 -0
  95. data/lib/rom/header/attribute.rb +190 -0
  96. data/lib/rom/header.rb +198 -0
  97. data/lib/rom/inferrer.rb +55 -0
  98. data/lib/rom/initializer.rb +80 -0
  99. data/lib/rom/lint/enumerable_dataset.rb +56 -0
  100. data/lib/rom/lint/gateway.rb +120 -0
  101. data/lib/rom/lint/linter.rb +79 -0
  102. data/lib/rom/lint/spec.rb +22 -0
  103. data/lib/rom/lint/test.rb +98 -0
  104. data/lib/rom/loader.rb +161 -0
  105. data/lib/rom/mapper/attribute_dsl.rb +480 -0
  106. data/lib/rom/mapper/dsl.rb +107 -0
  107. data/lib/rom/mapper/model_dsl.rb +61 -0
  108. data/lib/rom/mapper.rb +99 -0
  109. data/lib/rom/mapper_compiler.rb +84 -0
  110. data/lib/rom/memory/associations/many_to_many.rb +12 -0
  111. data/lib/rom/memory/associations/many_to_one.rb +12 -0
  112. data/lib/rom/memory/associations/one_to_many.rb +12 -0
  113. data/lib/rom/memory/associations/one_to_one.rb +12 -0
  114. data/lib/rom/memory/associations.rb +6 -0
  115. data/lib/rom/memory/commands.rb +60 -0
  116. data/lib/rom/memory/dataset.rb +127 -0
  117. data/lib/rom/memory/gateway.rb +66 -0
  118. data/lib/rom/memory/mapper_compiler.rb +10 -0
  119. data/lib/rom/memory/relation.rb +91 -0
  120. data/lib/rom/memory/schema.rb +32 -0
  121. data/lib/rom/memory/storage.rb +61 -0
  122. data/lib/rom/memory/types.rb +11 -0
  123. data/lib/rom/memory.rb +7 -0
  124. data/lib/rom/model_builder.rb +103 -0
  125. data/lib/rom/open_struct.rb +112 -0
  126. data/lib/rom/pipeline.rb +111 -0
  127. data/lib/rom/plugin.rb +130 -0
  128. data/lib/rom/plugins/class_methods.rb +37 -0
  129. data/lib/rom/plugins/command/schema.rb +45 -0
  130. data/lib/rom/plugins/command/timestamps.rb +149 -0
  131. data/lib/rom/plugins/dsl.rb +53 -0
  132. data/lib/rom/plugins/relation/changeset.rb +97 -0
  133. data/lib/rom/plugins/relation/instrumentation.rb +66 -0
  134. data/lib/rom/plugins/relation/registry_reader.rb +36 -0
  135. data/lib/rom/plugins/schema/timestamps.rb +59 -0
  136. data/lib/rom/plugins.rb +100 -0
  137. data/lib/rom/processor/composer.rb +37 -0
  138. data/lib/rom/processor/transformer.rb +415 -0
  139. data/lib/rom/processor.rb +30 -0
  140. data/lib/rom/registries/associations.rb +26 -0
  141. data/lib/rom/registries/commands.rb +11 -0
  142. data/lib/rom/registries/container.rb +12 -0
  143. data/lib/rom/registries/datasets.rb +21 -0
  144. data/lib/rom/registries/gateways.rb +8 -0
  145. data/lib/rom/registries/mappers.rb +21 -0
  146. data/lib/rom/registries/nestable.rb +32 -0
  147. data/lib/rom/registries/relations.rb +8 -0
  148. data/lib/rom/registries/root.rb +203 -0
  149. data/lib/rom/registries/schemas.rb +44 -0
  150. data/lib/rom/registries/views.rb +11 -0
  151. data/lib/rom/relation/class_interface.rb +61 -0
  152. data/lib/rom/relation/combined.rb +160 -0
  153. data/lib/rom/relation/commands.rb +65 -0
  154. data/lib/rom/relation/composite.rb +53 -0
  155. data/lib/rom/relation/curried.rb +129 -0
  156. data/lib/rom/relation/graph.rb +107 -0
  157. data/lib/rom/relation/loaded.rb +136 -0
  158. data/lib/rom/relation/materializable.rb +62 -0
  159. data/lib/rom/relation/name.rb +122 -0
  160. data/lib/rom/relation/wrap.rb +64 -0
  161. data/lib/rom/relation.rb +625 -0
  162. data/lib/rom/repository/class_interface.rb +162 -0
  163. data/lib/rom/repository/relation_reader.rb +48 -0
  164. data/lib/rom/repository/root.rb +75 -0
  165. data/lib/rom/repository/session.rb +60 -0
  166. data/lib/rom/repository.rb +179 -0
  167. data/lib/rom/schema/associations_dsl.rb +222 -0
  168. data/lib/rom/schema/inferrer.rb +106 -0
  169. data/lib/rom/schema.rb +471 -0
  170. data/lib/rom/settings.rb +141 -0
  171. data/lib/rom/setup.rb +297 -0
  172. data/lib/rom/struct.rb +99 -0
  173. data/lib/rom/struct_compiler.rb +114 -0
  174. data/lib/rom/support/configurable.rb +213 -0
  175. data/lib/rom/support/inflector.rb +31 -0
  176. data/lib/rom/support/memoizable.rb +61 -0
  177. data/lib/rom/support/notifications.rb +238 -0
  178. data/lib/rom/transaction.rb +26 -0
  179. data/lib/rom/transformer.rb +46 -0
  180. data/lib/rom/types.rb +74 -0
  181. data/lib/rom/version.rb +1 -1
  182. data/lib/rom-changeset.rb +4 -0
  183. data/lib/rom-core.rb +3 -0
  184. data/lib/rom-repository.rb +4 -0
  185. data/lib/rom.rb +3 -3
  186. metadata +302 -23
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+
5
+ require "rom/types"
6
+ require "rom/initializer"
7
+ require "rom/pipeline"
8
+ require "rom/relation/name"
9
+ require "rom/relation/materializable"
10
+
11
+ module ROM
12
+ class Relation
13
+ # Curried relation is a special relation proxy used by auto-curry mechanism.
14
+ #
15
+ # When a relation view method is called without all arguments, a curried proxy
16
+ # is returned that can be fully applied later on.
17
+ #
18
+ # Curried relations are typically used for relation composition
19
+ #
20
+ # @api public
21
+ class Curried
22
+ extend Initializer
23
+
24
+ include Dry::Equalizer(:relation, :options)
25
+ include Materializable
26
+ include Pipeline
27
+
28
+ undef :map_with
29
+
30
+ # @!attribute [r] relation
31
+ # @return [Relation] The source relation that is curried
32
+ param :relation
33
+
34
+ # @!attribute [r] view
35
+ # @return [Symbol] The name of relation's view method
36
+ option :view, type: Types::Strict::Symbol
37
+
38
+ # @!attribute [r] arity
39
+ # @return [Integer] View's arity
40
+ option :arity, type: Types::Strict::Integer
41
+
42
+ # @!attribute [r] curry_args
43
+ # @return [Array] Arguments that will be passed to curried view
44
+ option :curry_args, default: -> { EMPTY_ARRAY }
45
+
46
+ # Load relation if args match the arity
47
+ #
48
+ # @return [Loaded,Curried]
49
+ #
50
+ # @api public
51
+ def call(*args)
52
+ all_args = curry_args + args
53
+
54
+ if all_args.empty?
55
+ raise ArgumentError,
56
+ "curried #{relation.class}##{view} relation was called without any arguments"
57
+ end
58
+
59
+ if args.empty?
60
+ self
61
+ elsif arity == all_args.size
62
+ Loaded.new(relation.__send__(view, *all_args))
63
+ else
64
+ __new__(relation, curry_args: all_args)
65
+ end
66
+ end
67
+ alias_method :[], :call
68
+
69
+ # Relations are coercible to an array but a curried relation cannot be coerced
70
+ # When something tries to do this, an exception will be raised
71
+ #
72
+ # @raise ArgumentError
73
+ #
74
+ # @api public
75
+ def to_a
76
+ raise(
77
+ ArgumentError,
78
+ "#{relation.class}##{view} arity is #{arity} " \
79
+ "(#{curry_args.size} args given)"
80
+ )
81
+ end
82
+ alias_method :to_ary, :to_a
83
+
84
+ # Return if this lazy relation is curried
85
+ #
86
+ # @return [true]
87
+ #
88
+ # @api private
89
+ def curried?
90
+ true
91
+ end
92
+
93
+ # @api private
94
+ def respond_to_missing?(name, include_private = false)
95
+ super || relation.respond_to?(name, include_private)
96
+ end
97
+
98
+ private
99
+
100
+ # @api private
101
+ def __new__(relation, **new_opts)
102
+ self.class.new(relation, **options, **new_opts)
103
+ end
104
+
105
+ # @api private
106
+ def composite_class
107
+ Relation::Composite
108
+ end
109
+
110
+ # @api private
111
+ def method_missing(meth, *args, &block)
112
+ if relation.respond_to?(meth)
113
+ response = relation.__send__(meth, *args, &block)
114
+
115
+ super if response.is_a?(self.class)
116
+
117
+ if response.is_a?(Relation) || response.is_a?(Graph) || response.is_a?(Wrap) || response.is_a?(Composite)
118
+ __new__(response)
119
+ else
120
+ response
121
+ end
122
+ else
123
+ super
124
+ end
125
+ end
126
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+
5
+ require "rom/initializer"
6
+
7
+ require "rom/relation/loaded"
8
+ require "rom/relation/composite"
9
+ require "rom/relation/materializable"
10
+ require "rom/pipeline"
11
+ require "rom/support/memoizable"
12
+
13
+ module ROM
14
+ class Relation
15
+ # Abstract relation graph class
16
+ #
17
+ # @api public
18
+ class Graph
19
+ extend Initializer
20
+
21
+ include Memoizable
22
+
23
+ # @!attribute [r] root
24
+ # @return [Relation] The root relation
25
+ param :root
26
+
27
+ # @!attribute [r] nodes
28
+ # @return [Array<Relation>] An array with relation nodes
29
+ param :nodes
30
+
31
+ include Dry::Equalizer(:root, :nodes)
32
+ include Materializable
33
+ include Pipeline
34
+ include Pipeline::Proxy
35
+
36
+ # for compatibility with the pipeline
37
+ alias_method :left, :root
38
+ alias_method :right, :nodes
39
+
40
+ # Rebuild a graph with new nodes
41
+ #
42
+ # @param [Array<Relation>] nodes
43
+ #
44
+ # @return [Graph]
45
+ #
46
+ # @api public
47
+ def with_nodes(nodes)
48
+ self.class.new(root, nodes)
49
+ end
50
+
51
+ # Return if this is a graph relation
52
+ #
53
+ # @return [true]
54
+ #
55
+ # @api private
56
+ def graph?
57
+ true
58
+ end
59
+
60
+ # Map graph tuples via custom mappers
61
+ #
62
+ # @see Relation#map_with
63
+ #
64
+ # @return [Relation::Composite]
65
+ #
66
+ # @api public
67
+ def map_with(*names, **opts)
68
+ names.reduce(self.class.new(root.with(opts), nodes)) { |a, e| a >> mappers[e] }
69
+ end
70
+
71
+ # Map graph tuples to custom objects
72
+ #
73
+ # @see Relation#map_to
74
+ #
75
+ # @return [Graph]
76
+ #
77
+ # @api public
78
+ def map_to(klass)
79
+ self.class.new(root.map_to(klass), nodes)
80
+ end
81
+
82
+ # @see Relation#mapper
83
+ #
84
+ # @api private
85
+ def mapper
86
+ mappers[to_ast]
87
+ end
88
+
89
+ # @api private
90
+ memoize def to_ast
91
+ [:relation, [name.relation, attr_ast + nodes.map(&:to_ast), meta_ast]]
92
+ end
93
+
94
+ private
95
+
96
+ # @api private
97
+ def decorate?(other)
98
+ super || other.is_a?(Composite) || other.is_a?(Curried)
99
+ end
100
+
101
+ # @api private
102
+ def composite_class
103
+ Relation::Composite
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+
5
+ module ROM
6
+ class Relation
7
+ # Materializes a relation and exposes interface to access the data.
8
+ #
9
+ # This relation type is returned when a lazy relation is called
10
+ #
11
+ # @api public
12
+ class Loaded
13
+ include Enumerable
14
+ include Dry::Equalizer(:source, :collection)
15
+
16
+ # Coerce loaded relation to an array
17
+ #
18
+ # @return [Array]
19
+ #
20
+ # @api public
21
+ alias_method :to_ary, :to_a
22
+
23
+ # Source relation
24
+ #
25
+ # @return [Relation]
26
+ #
27
+ # @api private
28
+ attr_reader :source
29
+
30
+ # Materialized relation
31
+ #
32
+ # @return [Object]
33
+ #
34
+ # @api private
35
+ attr_reader :collection
36
+
37
+ # @api private
38
+ def initialize(source, collection = source.to_a)
39
+ @source = source
40
+ @collection = collection
41
+ end
42
+
43
+ # Yield relation tuples
44
+ #
45
+ # @yield [Hash]
46
+ #
47
+ # @api public
48
+ def each(&block)
49
+ return to_enum unless block_given?
50
+
51
+ collection.each(&block)
52
+ end
53
+
54
+ # Returns a single tuple from the relation if there is one.
55
+ #
56
+ # @raise [ROM::TupleCountMismatchError] if the relation contains more than
57
+ # one tuple
58
+ #
59
+ # @api public
60
+ def one
61
+ if collection.count > 1
62
+ raise(
63
+ TupleCountMismatchError,
64
+ "The relation consists of more than one tuple"
65
+ )
66
+ else
67
+ collection.first
68
+ end
69
+ end
70
+
71
+ # Like [one], but additionally raises an error if the relation is empty.
72
+ #
73
+ # @raise [ROM::TupleCountMismatchError] if the relation does not contain
74
+ # exactly one tuple
75
+ #
76
+ # @api public
77
+ def one!
78
+ one || raise(
79
+ TupleCountMismatchError,
80
+ "The relation does not contain any tuples"
81
+ )
82
+ end
83
+
84
+ # Return a list of values under provided key
85
+ #
86
+ # @example
87
+ # all_users = rom.relations[:users].call
88
+ # all_users.pluck(:name)
89
+ # # ["Jane", "Joe"]
90
+ #
91
+ # @param [Symbol] key The key name
92
+ #
93
+ # @return [Array]
94
+ #
95
+ # @raise KeyError when provided key doesn't exist in any of the tuples
96
+ #
97
+ # @api public
98
+ def pluck(key)
99
+ map { |tuple| tuple.fetch(key) }
100
+ end
101
+
102
+ # Pluck primary key values
103
+ #
104
+ # This method *may not work* with adapters that don't provide relations
105
+ # that have primary key configured
106
+ #
107
+ # @example
108
+ # users = rom.relations[:users].call
109
+ # users.primary_keys
110
+ # # [1, 2, 3]
111
+ #
112
+ # @return [Array]
113
+ #
114
+ # @api public
115
+ def primary_keys
116
+ pluck(source.primary_key)
117
+ end
118
+
119
+ # Return if loaded relation is empty
120
+ #
121
+ # @return [TrueClass,FalseClass]
122
+ #
123
+ # @api public
124
+ def empty?
125
+ collection.empty?
126
+ end
127
+
128
+ # Return a loaded relation with a new collection
129
+ #
130
+ # @api public
131
+ def new(collection)
132
+ self.class.new(source, collection)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ class Relation
5
+ # Interface for objects that can be materialized into a loaded relation
6
+ #
7
+ # @api public
8
+ module Materializable
9
+ # Coerce the relation to an array
10
+ #
11
+ # @return [Array]
12
+ #
13
+ # @api public
14
+ def to_a
15
+ call.to_a
16
+ end
17
+ alias_method :to_ary, :to_a
18
+
19
+ # Yield relation tuples
20
+ #
21
+ # @yield [Hash,Object]
22
+ #
23
+ # @api public
24
+ def each(&block)
25
+ return to_enum unless block_given?
26
+
27
+ to_a.each(&block)
28
+ end
29
+
30
+ # Delegate to loaded relation and return one object
31
+ #
32
+ # @return [Object]
33
+ #
34
+ # @see Loaded#one
35
+ #
36
+ # @api public
37
+ def one
38
+ call.one
39
+ end
40
+
41
+ # Delegate to loaded relation and return one object
42
+ #
43
+ # @return [Object]
44
+ #
45
+ # @see Loaded#one
46
+ #
47
+ # @api public
48
+ def one!
49
+ call.one!
50
+ end
51
+
52
+ # Return first tuple from a relation coerced to an array
53
+ #
54
+ # @return [Object]
55
+ #
56
+ # @api public
57
+ def first
58
+ to_a.first
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/map"
4
+ require "dry/core/equalizer"
5
+ require "rom/support/inflector"
6
+
7
+ module ROM
8
+ class Relation
9
+ # Relation name container
10
+ #
11
+ # This is a simple struct with two fields.
12
+ # It handles both relation registration name (i.e. Symbol) and dataset name.
13
+ # The reason we need it is a simplification of passing around these two objects.
14
+ # It is quite common to have a dataset named differently from a relation
15
+ # built on top if you are dealing with a legacy DB and often you need both
16
+ # to support things such as associations (rom-sql as an example).
17
+ #
18
+ # @api private
19
+ class Name
20
+ include Dry::Equalizer(:relation, :dataset, :key)
21
+
22
+ # Coerce an object to a Name instance
23
+ #
24
+ # @return [ROM::Relation::Name]
25
+ #
26
+ # @api private
27
+ def self.call(*args)
28
+ cache.fetch_or_store(args.hash) do
29
+ relation, dataset, aliaz = args
30
+
31
+ case relation
32
+ when self then relation
33
+ when Symbol then new(relation, dataset, aliaz)
34
+ when Class, String then new(Inflector.component_id(relation).to_sym)
35
+ else
36
+ raise ArgumentError, "+#{relation}+ is not supported"
37
+ end
38
+ end
39
+ end
40
+
41
+ class << self
42
+ # @api private
43
+ alias_method :[], :call
44
+ end
45
+
46
+ # @api private
47
+ def self.cache
48
+ @cache ||= Concurrent::Map.new
49
+ end
50
+
51
+ # Relation registration name
52
+ #
53
+ # @return [Symbol]
54
+ #
55
+ # @api private
56
+ attr_reader :relation
57
+
58
+ # Underlying dataset name
59
+ #
60
+ # @return [Symbol]
61
+ #
62
+ # @api private
63
+ attr_reader :dataset
64
+
65
+ attr_reader :aliaz
66
+
67
+ attr_reader :key
68
+
69
+ # @api private
70
+ def initialize(relation, dataset = relation, aliaz = nil)
71
+ @relation = relation
72
+ @dataset = dataset || relation
73
+ @key = aliaz || relation
74
+ @aliaz = aliaz
75
+ freeze
76
+ end
77
+
78
+ # @api private
79
+ def as(aliaz)
80
+ self.class[relation, dataset, aliaz]
81
+ end
82
+
83
+ # @api private
84
+ def aliased?
85
+ aliaz && aliaz != relation
86
+ end
87
+
88
+ # Return relation name
89
+ #
90
+ # @return [String]
91
+ #
92
+ # @api private
93
+ def to_s
94
+ if aliased?
95
+ "#{relation} on #{dataset} as #{aliaz}"
96
+ elsif relation == dataset
97
+ relation.to_s
98
+ else
99
+ "#{relation} on #{dataset}"
100
+ end
101
+ end
102
+
103
+ # Alias for registration key implicitly called by ROM::Registry
104
+ #
105
+ # @return [Symbol]
106
+ #
107
+ # @api private
108
+ def to_sym
109
+ relation
110
+ end
111
+
112
+ # Return inspected relation
113
+ #
114
+ # @return [String]
115
+ #
116
+ # @api private
117
+ def inspect
118
+ "#{self.class.name}(#{self})"
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/relation/graph"
4
+ require "rom/relation/combined"
5
+
6
+ module ROM
7
+ class Relation
8
+ # Relation wrapping other relations
9
+ #
10
+ # @api public
11
+ class Wrap < Graph
12
+ # Wrap more relations
13
+ #
14
+ # @see Relation#wrap
15
+ #
16
+ # @return [Wrap]
17
+ #
18
+ # @api public
19
+ def wrap(*args)
20
+ self.class.new(root, nodes + root.wrap(*args).nodes)
21
+ end
22
+
23
+ # Materialize a wrap
24
+ #
25
+ # @see Relation#call
26
+ #
27
+ # @return [Loaded]
28
+ #
29
+ # @api public
30
+ def call(*args)
31
+ if auto_map?
32
+ Loaded.new(self, mapper.(relation.with(auto_map: false, auto_struct: false)))
33
+ else
34
+ Loaded.new(self, relation.(*args))
35
+ end
36
+ end
37
+
38
+ # Return an adapter-specific relation representing a wrap
39
+ #
40
+ # @abstract
41
+ #
42
+ # @api private
43
+ def relation
44
+ raise NotImplementedError
45
+ end
46
+
47
+ # Return if this is a wrap relation
48
+ #
49
+ # @return [true]
50
+ #
51
+ # @api private
52
+ def wrap?
53
+ true
54
+ end
55
+
56
+ private
57
+
58
+ # @api private
59
+ def decorate?(other)
60
+ super || other.is_a?(Combined)
61
+ end
62
+ end
63
+ end
64
+ end