rom-core 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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