rom-core 4.0.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.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +603 -0
  3. data/LICENSE +20 -0
  4. data/README.md +18 -0
  5. data/lib/rom-core.rb +1 -0
  6. data/lib/rom/array_dataset.rb +44 -0
  7. data/lib/rom/association_set.rb +16 -0
  8. data/lib/rom/associations/abstract.rb +135 -0
  9. data/lib/rom/associations/definitions.rb +5 -0
  10. data/lib/rom/associations/definitions/abstract.rb +116 -0
  11. data/lib/rom/associations/definitions/many_to_many.rb +24 -0
  12. data/lib/rom/associations/definitions/many_to_one.rb +11 -0
  13. data/lib/rom/associations/definitions/one_to_many.rb +11 -0
  14. data/lib/rom/associations/definitions/one_to_one.rb +11 -0
  15. data/lib/rom/associations/definitions/one_to_one_through.rb +11 -0
  16. data/lib/rom/associations/many_to_many.rb +81 -0
  17. data/lib/rom/associations/many_to_one.rb +37 -0
  18. data/lib/rom/associations/one_to_many.rb +37 -0
  19. data/lib/rom/associations/one_to_one.rb +8 -0
  20. data/lib/rom/associations/one_to_one_through.rb +8 -0
  21. data/lib/rom/associations/through_identifier.rb +39 -0
  22. data/lib/rom/auto_curry.rb +55 -0
  23. data/lib/rom/cache.rb +46 -0
  24. data/lib/rom/command.rb +488 -0
  25. data/lib/rom/command_compiler.rb +239 -0
  26. data/lib/rom/command_proxy.rb +24 -0
  27. data/lib/rom/command_registry.rb +141 -0
  28. data/lib/rom/commands.rb +3 -0
  29. data/lib/rom/commands/class_interface.rb +270 -0
  30. data/lib/rom/commands/composite.rb +53 -0
  31. data/lib/rom/commands/create.rb +13 -0
  32. data/lib/rom/commands/delete.rb +14 -0
  33. data/lib/rom/commands/graph.rb +88 -0
  34. data/lib/rom/commands/graph/class_interface.rb +62 -0
  35. data/lib/rom/commands/graph/input_evaluator.rb +62 -0
  36. data/lib/rom/commands/lazy.rb +99 -0
  37. data/lib/rom/commands/lazy/create.rb +23 -0
  38. data/lib/rom/commands/lazy/delete.rb +27 -0
  39. data/lib/rom/commands/lazy/update.rb +34 -0
  40. data/lib/rom/commands/result.rb +96 -0
  41. data/lib/rom/commands/update.rb +14 -0
  42. data/lib/rom/configuration.rb +114 -0
  43. data/lib/rom/configuration_dsl.rb +87 -0
  44. data/lib/rom/configuration_dsl/command.rb +41 -0
  45. data/lib/rom/configuration_dsl/command_dsl.rb +35 -0
  46. data/lib/rom/configuration_dsl/relation.rb +26 -0
  47. data/lib/rom/configuration_plugin.rb +17 -0
  48. data/lib/rom/constants.rb +64 -0
  49. data/lib/rom/container.rb +147 -0
  50. data/lib/rom/core.rb +46 -0
  51. data/lib/rom/create_container.rb +60 -0
  52. data/lib/rom/data_proxy.rb +94 -0
  53. data/lib/rom/enumerable_dataset.rb +68 -0
  54. data/lib/rom/environment.rb +70 -0
  55. data/lib/rom/gateway.rb +184 -0
  56. data/lib/rom/global.rb +58 -0
  57. data/lib/rom/global/plugin_dsl.rb +47 -0
  58. data/lib/rom/initializer.rb +64 -0
  59. data/lib/rom/lint/enumerable_dataset.rb +54 -0
  60. data/lib/rom/lint/gateway.rb +120 -0
  61. data/lib/rom/lint/linter.rb +78 -0
  62. data/lib/rom/lint/spec.rb +20 -0
  63. data/lib/rom/lint/test.rb +98 -0
  64. data/lib/rom/mapper_registry.rb +24 -0
  65. data/lib/rom/memory.rb +4 -0
  66. data/lib/rom/memory/associations.rb +4 -0
  67. data/lib/rom/memory/associations/many_to_many.rb +10 -0
  68. data/lib/rom/memory/associations/many_to_one.rb +10 -0
  69. data/lib/rom/memory/associations/one_to_many.rb +10 -0
  70. data/lib/rom/memory/associations/one_to_one.rb +10 -0
  71. data/lib/rom/memory/commands.rb +56 -0
  72. data/lib/rom/memory/dataset.rb +97 -0
  73. data/lib/rom/memory/gateway.rb +64 -0
  74. data/lib/rom/memory/relation.rb +62 -0
  75. data/lib/rom/memory/schema.rb +23 -0
  76. data/lib/rom/memory/storage.rb +59 -0
  77. data/lib/rom/memory/types.rb +9 -0
  78. data/lib/rom/pipeline.rb +105 -0
  79. data/lib/rom/plugin.rb +25 -0
  80. data/lib/rom/plugin_base.rb +45 -0
  81. data/lib/rom/plugin_registry.rb +197 -0
  82. data/lib/rom/plugins/command/schema.rb +37 -0
  83. data/lib/rom/plugins/configuration/configuration_dsl.rb +21 -0
  84. data/lib/rom/plugins/relation/instrumentation.rb +51 -0
  85. data/lib/rom/plugins/relation/registry_reader.rb +44 -0
  86. data/lib/rom/plugins/schema/timestamps.rb +58 -0
  87. data/lib/rom/registry.rb +71 -0
  88. data/lib/rom/relation.rb +548 -0
  89. data/lib/rom/relation/class_interface.rb +282 -0
  90. data/lib/rom/relation/commands.rb +23 -0
  91. data/lib/rom/relation/composite.rb +46 -0
  92. data/lib/rom/relation/curried.rb +103 -0
  93. data/lib/rom/relation/graph.rb +197 -0
  94. data/lib/rom/relation/loaded.rb +127 -0
  95. data/lib/rom/relation/materializable.rb +66 -0
  96. data/lib/rom/relation/name.rb +111 -0
  97. data/lib/rom/relation/view_dsl.rb +64 -0
  98. data/lib/rom/relation/wrap.rb +83 -0
  99. data/lib/rom/relation_registry.rb +10 -0
  100. data/lib/rom/schema.rb +437 -0
  101. data/lib/rom/schema/associations_dsl.rb +195 -0
  102. data/lib/rom/schema/attribute.rb +419 -0
  103. data/lib/rom/schema/dsl.rb +164 -0
  104. data/lib/rom/schema/inferrer.rb +66 -0
  105. data/lib/rom/schema_plugin.rb +27 -0
  106. data/lib/rom/setup.rb +68 -0
  107. data/lib/rom/setup/auto_registration.rb +74 -0
  108. data/lib/rom/setup/auto_registration_strategies/base.rb +16 -0
  109. data/lib/rom/setup/auto_registration_strategies/custom_namespace.rb +63 -0
  110. data/lib/rom/setup/auto_registration_strategies/no_namespace.rb +20 -0
  111. data/lib/rom/setup/auto_registration_strategies/with_namespace.rb +18 -0
  112. data/lib/rom/setup/finalize.rb +103 -0
  113. data/lib/rom/setup/finalize/finalize_commands.rb +60 -0
  114. data/lib/rom/setup/finalize/finalize_mappers.rb +56 -0
  115. data/lib/rom/setup/finalize/finalize_relations.rb +135 -0
  116. data/lib/rom/support/configurable.rb +85 -0
  117. data/lib/rom/support/memoizable.rb +58 -0
  118. data/lib/rom/support/notifications.rb +103 -0
  119. data/lib/rom/transaction.rb +24 -0
  120. data/lib/rom/types.rb +26 -0
  121. data/lib/rom/version.rb +5 -0
  122. metadata +289 -0
@@ -0,0 +1,282 @@
1
+ require 'set'
2
+
3
+ require 'dry/core/inflector'
4
+
5
+ require 'rom/constants'
6
+ require 'rom/relation/name'
7
+ require 'rom/relation/view_dsl'
8
+ require 'rom/schema'
9
+ require 'rom/support/notifications'
10
+
11
+ module ROM
12
+ class Relation
13
+ # @api public
14
+ module ClassInterface
15
+ extend Notifications::Listener
16
+
17
+ subscribe('configuration.relations.object.registered') do |event|
18
+ relation = event[:relation]
19
+ registry = event[:registry]
20
+
21
+ schemas = relation.schemas.reduce({}) do |h, (a, e)|
22
+ h.update(a => e.is_a?(Proc) ? relation.class.instance_exec(registry, &e) : e)
23
+ end
24
+
25
+ relation.schemas.update(schemas)
26
+ relation
27
+ end
28
+
29
+ DEFAULT_DATASET_PROC = -> * { self }.freeze
30
+
31
+ # Return adapter-specific relation subclass
32
+ #
33
+ # @example
34
+ # ROM::Relation[:memory]
35
+ # # => ROM::Memory::Relation
36
+ #
37
+ # @return [Class]
38
+ #
39
+ # @api public
40
+ def [](adapter)
41
+ ROM.adapters.fetch(adapter).const_get(:Relation)
42
+ rescue KeyError
43
+ raise AdapterNotPresentError.new(adapter, :relation)
44
+ end
45
+
46
+ # Set or get custom dataset block
47
+ #
48
+ # This block will be evaluated when a relation is instantiated and registered
49
+ # in a relation registry.
50
+ #
51
+ # @example
52
+ # class Users < ROM::Relation[:memory]
53
+ # dataset { sort_by(:id) }
54
+ # end
55
+ #
56
+ # @api public
57
+ def dataset(&block)
58
+ if defined?(@dataset)
59
+ @dataset
60
+ else
61
+ @dataset = block || DEFAULT_DATASET_PROC
62
+ end
63
+ end
64
+
65
+ # Specify canonical schema for a relation
66
+ #
67
+ # With a schema defined commands will set up a type-safe input handler
68
+ # automatically
69
+ #
70
+ # @example
71
+ # class Users < ROM::Relation[:sql]
72
+ # schema do
73
+ # attribute :id, Types::Serial
74
+ # attribute :name, Types::String
75
+ # end
76
+ # end
77
+ #
78
+ # # access schema
79
+ # Users.schema
80
+ #
81
+ # @return [Schema]
82
+ #
83
+ # @param [Symbol] dataset An optional dataset name
84
+ # @param [Boolean] infer Whether to do an automatic schema inferring
85
+ #
86
+ # @api public
87
+ def schema(dataset = nil, as: nil, infer: false, &block)
88
+ if defined?(@schema) && !block && !infer
89
+ @schema
90
+ elsif block || infer
91
+ raise MissingSchemaClassError.new(self) unless schema_class
92
+
93
+ ds_name = dataset || schema_opts.fetch(:dataset, default_name.dataset)
94
+ relation = as || schema_opts.fetch(:relation, ds_name)
95
+
96
+ @relation_name = Name[relation, ds_name]
97
+
98
+ dsl = schema_dsl.new(
99
+ relation_name,
100
+ schema_class: schema_class,
101
+ attr_class: schema_attr_class,
102
+ inferrer: schema_inferrer.with(enabled: infer),
103
+ &block
104
+ )
105
+
106
+ @schema_proc = dsl.method(:call).to_proc
107
+ end
108
+ end
109
+
110
+ # @api private
111
+ def set_schema!(schema)
112
+ @schema = schema
113
+ end
114
+
115
+ # @api private
116
+ attr_reader :schema_proc
117
+
118
+ # !@attribute [r] relation_name
119
+ # @return [Name] Qualified relation name
120
+ def relation_name
121
+ raise MissingSchemaError.new(self) unless defined?(@relation_name)
122
+ @relation_name
123
+ end
124
+
125
+ # Define a relation view with a specific schema
126
+ #
127
+ # Explicit relation views allow relation composition with auto-mapping
128
+ # in repositories. It's useful for cases like defining custom views
129
+ # for associations where relations (even from different databases) can
130
+ # be composed together and automatically mapped in memory to structs.
131
+ #
132
+ # @overload view(name, schema, &block)
133
+ # @example View with the canonical schema
134
+ # class Users < ROM::Relation[:sql]
135
+ # view(:listing, schema) do
136
+ # order(:name)
137
+ # end
138
+ # end
139
+ #
140
+ # @example View with a projected schema
141
+ # class Users < ROM::Relation[:sql]
142
+ # view(:listing, schema.project(:id, :name)) do
143
+ # order(:name)
144
+ # end
145
+ # end
146
+ #
147
+ # @overload view(name, &block)
148
+ # @example View with the canonical schema and arguments
149
+ # class Users < ROM::Relation[:sql]
150
+ # view(:by_name) do |name|
151
+ # where(name: name)
152
+ # end
153
+ # end
154
+ #
155
+ # @example View with projected schema and arguments
156
+ # class Users < ROM::Relation[:sql]
157
+ # view(:by_name) do
158
+ # schema { project(:id, :name) }
159
+ # relation { |name| where(name: name) }
160
+ # end
161
+ # end
162
+ #
163
+ # @example View with a schema extended with foreign attributes
164
+ # class Users < ROM::Relation[:sql]
165
+ # view(:index) do
166
+ # schema { append(relations[:tasks][:title]) }
167
+ # relation { |name| where(name: name) }
168
+ # end
169
+ # end
170
+ #
171
+ # @return [Symbol] view method name
172
+ #
173
+ # @api public
174
+ def view(*args, &block)
175
+ if args.size == 1 && block.arity > 0
176
+ raise ArgumentError, "header must be set as second argument"
177
+ end
178
+
179
+ name, new_schema_fn, relation_block =
180
+ if args.size == 1
181
+ ViewDSL.new(*args, schema, &block).call
182
+ else
183
+ [*args, block]
184
+ end
185
+
186
+ schemas[name] =
187
+ if args.size == 2
188
+ -> _ { schema.project(*args[1]) }
189
+ else
190
+ new_schema_fn
191
+ end
192
+
193
+ if relation_block.arity > 0
194
+ auto_curry_guard do
195
+ define_method(name, &relation_block)
196
+
197
+ auto_curry(name) do
198
+ schemas[name].(self)
199
+ end
200
+ end
201
+ else
202
+ define_method(name) do
203
+ schemas[name].(instance_exec(&relation_block))
204
+ end
205
+ end
206
+
207
+ name
208
+ end
209
+
210
+ # Dynamically define a method that will forward to the dataset and wrap
211
+ # response in the relation itself
212
+ #
213
+ # @example
214
+ # class SomeAdapterRelation < ROM::Relation
215
+ # forward :super_query
216
+ # end
217
+ #
218
+ # @api public
219
+ def forward(*methods)
220
+ methods.each do |method|
221
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
222
+ def #{method}(*args, &block)
223
+ new(dataset.__send__(:#{method}, *args, &block))
224
+ end
225
+ RUBY
226
+ end
227
+ end
228
+
229
+ # Include a registered plugin in this relation class
230
+ #
231
+ # @param [Symbol] plugin
232
+ # @param [Hash] options
233
+ # @option options [Symbol] :adapter (:default) first adapter to check for plugin
234
+ #
235
+ # @api public
236
+ def use(plugin, options = EMPTY_HASH)
237
+ ROM.plugin_registry.relations.fetch(plugin, adapter).apply_to(self, options)
238
+ end
239
+
240
+ # @api private
241
+ def curried
242
+ Curried
243
+ end
244
+
245
+ # @api private
246
+ def view_methods
247
+ ancestor_methods = ancestors.reject { |klass| klass == self }
248
+ .map(&:instance_methods).flatten(1)
249
+
250
+ instance_methods - ancestor_methods + auto_curried_methods
251
+ end
252
+
253
+ # @api private
254
+ def schemas
255
+ @schemas ||= {}
256
+ end
257
+
258
+ # Return default relation name used in schemas
259
+ #
260
+ # @return [Name]
261
+ #
262
+ # @api private
263
+ def default_name
264
+ Name[Dry::Core::Inflector.underscore(name).tr('/', '_').to_sym]
265
+ end
266
+
267
+ # @api private
268
+ def default_schema(klass = self)
269
+ klass.schema ||
270
+ if klass.schema_proc
271
+ klass.set_schema!(klass.schema_proc.call)
272
+ else
273
+ klass.schema_class.define(klass.default_name)
274
+ end
275
+ end
276
+
277
+ def name
278
+ super || superclass.name
279
+ end
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,23 @@
1
+ module ROM
2
+ class Relation
3
+ # Extensions for relation classes which provide access to commands
4
+ #
5
+ # @api private
6
+ module Commands
7
+ # @api public
8
+ def command(type, mapper: nil, use: EMPTY_ARRAY, **opts)
9
+ command = commands[type, adapter, to_ast, use, opts]
10
+
11
+ if mapper
12
+ command >> mappers[mapper]
13
+ elsif mappers.any? && !command.is_a?(CommandProxy)
14
+ mappers.reduce(command) { |a, (_, e)| a >> e }
15
+ elsif auto_struct? || auto_map?
16
+ command >> self.mapper
17
+ else
18
+ command
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ require 'rom/relation/loaded'
2
+ require 'rom/relation/materializable'
3
+ require 'rom/pipeline'
4
+
5
+ module ROM
6
+ class Relation
7
+ # Left-to-right relation composition used for data-pipelining
8
+ #
9
+ # @api public
10
+ class Composite < Pipeline::Composite
11
+ include Materializable
12
+
13
+ # Call the pipeline by passing results from left to right
14
+ #
15
+ # Optional args are passed to the left object
16
+ #
17
+ # @return [Loaded]
18
+ #
19
+ # @api public
20
+ def call(*args)
21
+ relation = left.call(*args)
22
+ response = right.call(relation)
23
+
24
+ if response.is_a?(Loaded)
25
+ response
26
+ elsif relation.is_a?(Loaded)
27
+ relation.new(response)
28
+ else
29
+ Loaded.new(relation, response)
30
+ end
31
+ end
32
+ alias_method :[], :call
33
+
34
+ private
35
+
36
+ # @api private
37
+ #
38
+ # @see Pipeline::Proxy#decorate?
39
+ #
40
+ # @api private
41
+ def decorate?(response)
42
+ super || response.is_a?(Graph)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,103 @@
1
+ require 'rom/types'
2
+ require 'rom/initializer'
3
+ require 'rom/pipeline'
4
+ require 'rom/relation/name'
5
+ require 'rom/relation/materializable'
6
+
7
+ module ROM
8
+ class Relation
9
+ class Curried
10
+ extend Initializer
11
+
12
+ include Materializable
13
+ include Pipeline
14
+
15
+ undef :map_with
16
+
17
+ param :relation
18
+
19
+ option :view, type: Types::Strict::Symbol
20
+ option :arity, type: Types::Strict::Int, default: -> { -1 }
21
+ option :curry_args, default: -> { EMPTY_ARRAY }
22
+
23
+ # Load relation if args match the arity
24
+ #
25
+ # @return [Loaded,Curried]
26
+ #
27
+ # @api public
28
+ def call(*args)
29
+ if arity != -1
30
+ all_args = curry_args + args
31
+
32
+ if all_args.empty?
33
+ raise ArgumentError, "curried #{relation.class}##{view} relation was called without any arguments"
34
+ end
35
+
36
+ if args.empty?
37
+ self
38
+ elsif arity == all_args.size
39
+ Loaded.new(relation.__send__(view, *all_args))
40
+ else
41
+ __new__(relation, curry_args: all_args)
42
+ end
43
+ else
44
+ super
45
+ end
46
+ end
47
+ alias_method :[], :call
48
+
49
+ # @api public
50
+ def to_a
51
+ raise(
52
+ ArgumentError,
53
+ "#{relation.class}##{view} arity is #{arity} " \
54
+ "(#{curry_args.size} args given)"
55
+ )
56
+ end
57
+ alias_method :to_ary, :to_a
58
+
59
+ # Return if this lazy relation is curried
60
+ #
61
+ # @return [true]
62
+ #
63
+ # @api private
64
+ def curried?
65
+ true
66
+ end
67
+
68
+ # @api private
69
+ def respond_to_missing?(name, include_private = false)
70
+ super || relation.respond_to?(name, include_private)
71
+ end
72
+
73
+ private
74
+
75
+ # @api private
76
+ def __new__(relation, new_opts = EMPTY_HASH)
77
+ self.class.new(relation, new_opts.empty? ? options : options.merge(new_opts))
78
+ end
79
+
80
+ # @api private
81
+ def composite_class
82
+ Relation::Composite
83
+ end
84
+
85
+ # @api private
86
+ def method_missing(meth, *args, &block)
87
+ if relation.respond_to?(meth)
88
+ response = relation.__send__(meth, *args, &block)
89
+
90
+ super if response.is_a?(self.class)
91
+
92
+ if response.is_a?(Relation) || response.is_a?(Graph) || response.is_a?(Wrap) || response.is_a?(Composite)
93
+ __new__(response)
94
+ else
95
+ response
96
+ end
97
+ else
98
+ super
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end