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,164 @@
1
+ require 'dry/equalizer'
2
+
3
+ require 'rom/types'
4
+ require 'rom/schema/attribute'
5
+ require 'rom/schema/associations_dsl'
6
+
7
+ module ROM
8
+ # Relation schema
9
+ #
10
+ # @api public
11
+ class Schema
12
+ # @api public
13
+ class DSL < BasicObject
14
+ KERNEL_METHODS = %i(extend method).freeze
15
+ KERNEL_METHODS.each { |m| define_method(m, ::Kernel.instance_method(m)) }
16
+
17
+ extend Initializer
18
+
19
+ param :relation
20
+
21
+ option :inferrer, default: -> { DEFAULT_INFERRER }
22
+
23
+ option :schema_class, default: -> { Schema }
24
+
25
+ option :attr_class, default: -> { Attribute }
26
+
27
+ option :adapter, default: -> { :default }
28
+
29
+ attr_reader :attributes, :plugins, :definition, :associations_dsl
30
+
31
+ # @api private
32
+ def initialize(*, &block)
33
+ super
34
+
35
+ @attributes = {}
36
+ @plugins = {}
37
+
38
+ @definition = block
39
+ end
40
+
41
+ # Defines a relation attribute with its type
42
+ #
43
+ # @see Relation.schema
44
+ #
45
+ # @api public
46
+ def attribute(name, type, options = EMPTY_HASH)
47
+ if attributes.key?(name)
48
+ ::Kernel.raise ::ROM::Schema::AttributeAlreadyDefinedError,
49
+ "Attribute #{ name.inspect } already defined"
50
+ end
51
+
52
+ attributes[name] = build_type(name, type, options)
53
+ end
54
+
55
+ # Define associations for a relation
56
+ #
57
+ # @example
58
+ # class Users < ROM::Relation[:sql]
59
+ # schema(infer: true) do
60
+ # associations do
61
+ # has_many :tasks
62
+ # has_many :posts
63
+ # has_many :posts, as: :priority_posts, view: :prioritized
64
+ # belongs_to :account
65
+ # end
66
+ # end
67
+ # end
68
+ #
69
+ # class Posts < ROM::Relation[:sql]
70
+ # schema(infer: true) do
71
+ # associations do
72
+ # belongs_to :users, as: :author
73
+ # end
74
+ # end
75
+ #
76
+ # view(:prioritized) do
77
+ # where { priority <= 3 }
78
+ # end
79
+ # end
80
+ #
81
+ # @return [AssociationDSL]
82
+ #
83
+ # @api public
84
+ def associations(&block)
85
+ @associations_dsl = AssociationsDSL.new(relation, &block)
86
+ end
87
+
88
+ # Builds a type instance from a name, options and a base type
89
+ #
90
+ # @return [Dry::Types::Type] Type instance
91
+ #
92
+ # @api private
93
+ def build_type(name, type, options = EMPTY_HASH)
94
+ if options[:read]
95
+ type.meta(name: name, source: relation, read: options[:read])
96
+ else
97
+ type.meta(name: name, source: relation)
98
+ end
99
+ end
100
+
101
+ # Specify which key(s) should be the primary key
102
+ #
103
+ # @api public
104
+ def primary_key(*names)
105
+ names.each do |name|
106
+ attributes[name] = attributes[name].meta(primary_key: true)
107
+ end
108
+ self
109
+ end
110
+
111
+ # Enables for the schema
112
+ #
113
+ # @param [Symbol] plugin Plugin name
114
+ # @param [Hash] options Plugin options
115
+ #
116
+ # @api public
117
+ def use(plugin, options = ::ROM::EMPTY_HASH)
118
+ mod = ::ROM.plugin_registry.schemas.adapter(adapter).fetch(plugin)
119
+ app_plugin(mod, options)
120
+ end
121
+
122
+ # @api private
123
+ def app_plugin(plugin, options = ::ROM::EMPTY_HASH)
124
+ plugin_name = ::ROM.plugin_registry.schemas.adapter(adapter).plugin_name(plugin)
125
+ plugin.extend_dsl(self)
126
+ @plugins[plugin_name] = [plugin, plugin.config.to_hash.merge(options)]
127
+ end
128
+
129
+ # @api private
130
+ def call(&block)
131
+ instance_exec(&block) if block
132
+ instance_exec(&definition) if definition
133
+
134
+ schema_class.define(relation, opts) do |schema|
135
+ plugins.values.each { |(plugin, options)|
136
+ plugin.apply_to(schema, options)
137
+ }
138
+ end
139
+ end
140
+
141
+ # @api private
142
+ def plugin_options(plugin)
143
+ @plugins[plugin][1]
144
+ end
145
+
146
+ private
147
+
148
+ # Return schema opts
149
+ #
150
+ # @return [Hash]
151
+ #
152
+ # @api private
153
+ def opts
154
+ opts = { attributes: attributes.values, inferrer: inferrer, attr_class: attr_class }
155
+
156
+ if associations_dsl
157
+ { **opts, associations: associations_dsl.call }
158
+ else
159
+ opts
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,66 @@
1
+ require 'dry/core/class_attributes'
2
+
3
+ module ROM
4
+ class Schema
5
+ # @api private
6
+ class Inferrer
7
+ extend Dry::Core::ClassAttributes
8
+ extend Initializer
9
+
10
+ defines :attributes_inferrer, :attr_class
11
+
12
+ MissingAttributesError = Class.new(StandardError) do
13
+ def initialize(name, attributes)
14
+ super("missing attributes in #{name.inspect} schema: #{attributes.map(&:inspect).join(', ')}")
15
+ end
16
+ end
17
+
18
+ DEFAULT_ATTRIBUTES = [EMPTY_ARRAY, EMPTY_ARRAY].freeze
19
+
20
+ attributes_inferrer -> * { DEFAULT_ATTRIBUTES }
21
+
22
+ attr_class Attribute
23
+
24
+ include Dry::Equalizer(:options)
25
+
26
+ option :attr_class, default: -> { self.class.attr_class }
27
+
28
+ option :enabled, default: -> { true }
29
+
30
+ alias_method :enabled?, :enabled
31
+
32
+ option :attributes_inferrer, default: -> { self.class.attributes_inferrer }
33
+
34
+ # @api private
35
+ def call(schema, gateway)
36
+ if enabled?
37
+ inferred, missing = attributes_inferrer.(schema, gateway, options)
38
+ else
39
+ inferred, missing = DEFAULT_ATTRIBUTES
40
+ end
41
+
42
+ attributes = merge_attributes(schema.attributes, inferred)
43
+
44
+ check_all_attributes_defined(schema, attributes, missing)
45
+
46
+ { attributes: attributes }
47
+ end
48
+
49
+ # @api private
50
+ def check_all_attributes_defined(schema, all_known, not_inferred)
51
+ not_defined = not_inferred - all_known.map(&:name)
52
+
53
+ if not_defined.size > 0
54
+ raise MissingAttributesError.new(schema.name, not_defined)
55
+ end
56
+ end
57
+
58
+ # @api private
59
+ def merge_attributes(defined, inferred)
60
+ defined_names = defined.map(&:name)
61
+
62
+ defined + inferred.reject { |attr| defined_names.include?(attr.name) }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,27 @@
1
+ require 'rom/plugin_base'
2
+
3
+ module ROM
4
+ # @api private
5
+ class SchemaPlugin < PluginBase
6
+ include Configurable
7
+
8
+ # Apply this plugin to the provided configuration
9
+ #
10
+ # @param [ROM::Schema] schema A schema instance for extension
11
+ # @param [Hash] options Extension options
12
+ #
13
+ # @api private
14
+ def apply_to(schema, options = EMPTY_HASH)
15
+ mod.apply(schema, options) if mod.respond_to?(:apply)
16
+ end
17
+
18
+ # Extends a DSL instance with a module provided by the plugin
19
+ #
20
+ # @param [ROM::Schema::DSL] dsl
21
+ #
22
+ # @api private
23
+ def extend_dsl(dsl)
24
+ dsl.extend(mod.const_get(:DSL)) if mod.const_defined?(:DSL)
25
+ end
26
+ end
27
+ end
data/lib/rom/setup.rb ADDED
@@ -0,0 +1,68 @@
1
+ require 'rom/setup/auto_registration'
2
+
3
+ module ROM
4
+ class Setup
5
+ # @return [Array] registered relation subclasses
6
+ #
7
+ # @api private
8
+ attr_reader :relation_classes
9
+
10
+ # @return [Array] registered mapper subclasses
11
+ #
12
+ # @api private
13
+ attr_reader :mapper_classes
14
+
15
+ # @return [Array] registered command subclasses
16
+ #
17
+ # @api private
18
+ attr_reader :command_classes
19
+
20
+ # @api private
21
+ attr_reader :plugins
22
+
23
+ # @api private
24
+ attr_reader :notifications
25
+
26
+ # @api private
27
+ def initialize(notifications)
28
+ @relation_classes = []
29
+ @command_classes = []
30
+ @mapper_classes = []
31
+ @plugins = []
32
+ @notifications = notifications
33
+ end
34
+
35
+ # Relation sub-classes are being registered with this method during setup
36
+ #
37
+ # @api private
38
+ def register_relation(*klasses)
39
+ klasses.reduce(@relation_classes, :<<)
40
+ end
41
+
42
+ # Mapper sub-classes are being registered with this method during setup
43
+ #
44
+ # @api private
45
+ def register_mapper(*klasses)
46
+ klasses.reduce(@mapper_classes, :<<)
47
+ end
48
+
49
+ # Command sub-classes are being registered with this method during setup
50
+ #
51
+ # @api private
52
+ def register_command(*klasses)
53
+ klasses.reduce(@command_classes, :<<)
54
+ end
55
+
56
+ # @api private
57
+ def register_plugin(plugin)
58
+ plugins << plugin
59
+ end
60
+
61
+ def auto_registration(directory, options = {})
62
+ auto_registration = AutoRegistration.new(directory, options)
63
+ auto_registration.relations.map { |r| register_relation(r) }
64
+ auto_registration.commands.map { |r| register_command(r) }
65
+ auto_registration.mappers.map { |r| register_mapper(r) }
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,74 @@
1
+ require 'pathname'
2
+
3
+ require 'dry/core/inflector'
4
+
5
+ require 'rom/types'
6
+ require 'rom/initializer'
7
+ require 'rom/setup/auto_registration_strategies/no_namespace'
8
+ require 'rom/setup/auto_registration_strategies/with_namespace'
9
+ require 'rom/setup/auto_registration_strategies/custom_namespace'
10
+
11
+ module ROM
12
+ class AutoRegistration
13
+ extend Initializer
14
+
15
+ NamespaceType = Types::Strict::Bool | Types::Strict::String
16
+ PathnameType = Types.Constructor(Pathname, &Kernel.method(:Pathname))
17
+ DEFAULT_MAPPING = {
18
+ relations: :relations,
19
+ mappers: :mappers,
20
+ commands: :commands
21
+ }.freeze
22
+
23
+ param :directory, type: PathnameType
24
+
25
+ option :namespace, type: NamespaceType, default: -> { true }
26
+
27
+ option :component_dirs, type: Types::Strict::Hash, default: -> { DEFAULT_MAPPING }
28
+
29
+ option :globs, default: -> {
30
+ Hash[
31
+ component_dirs.map { |component, path|
32
+ [component, directory.join("#{path}/**/*.rb")]
33
+ }
34
+ ]
35
+ }
36
+
37
+ def relations
38
+ load_entities(:relations)
39
+ end
40
+
41
+ def commands
42
+ load_entities(:commands)
43
+ end
44
+
45
+ def mappers
46
+ load_entities(:mappers)
47
+ end
48
+
49
+ private
50
+
51
+ def load_entities(entity)
52
+ Dir[globs[entity]].map do |file|
53
+ require file
54
+ klass_name =
55
+ case namespace
56
+ when String
57
+ AutoRegistrationStrategies::CustomNamespace.new(
58
+ namespace: namespace, file: file, directory: directory
59
+ ).call
60
+ when TrueClass
61
+ AutoRegistrationStrategies::WithNamespace.new(
62
+ file: file, directory: directory
63
+ ).call
64
+ when FalseClass
65
+ AutoRegistrationStrategies::NoNamespace.new(
66
+ file: file, directory: directory, entity: component_dirs.fetch(entity)
67
+ ).call
68
+ end
69
+
70
+ Dry::Core::Inflector.constantize(klass_name)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,16 @@
1
+ require 'rom/types'
2
+ require 'rom/initializer'
3
+
4
+ module ROM
5
+ module AutoRegistrationStrategies
6
+ class Base
7
+ extend Initializer
8
+
9
+ PathnameType = Types.Definition(Pathname).constrained(type: Pathname)
10
+
11
+ option :file, type: Types::Strict::String
12
+
13
+ EXTENSION_REGEX = /\.rb\z/
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,63 @@
1
+ require 'pathname'
2
+
3
+ require 'dry/core/inflector'
4
+ require 'rom/types'
5
+ require 'rom/setup/auto_registration_strategies/base'
6
+
7
+ module ROM
8
+ module AutoRegistrationStrategies
9
+ class CustomNamespace < Base
10
+ option :directory, type: PathnameType
11
+ option :namespace, type: Types::Strict::String
12
+
13
+ def call
14
+ potential = []
15
+ attempted = []
16
+
17
+ path_arr.reverse.each do |dir|
18
+ const_fragment = potential.unshift(
19
+ Dry::Core::Inflector.camelize(dir)
20
+ ).join("::")
21
+
22
+ constant = "#{namespace}::#{const_fragment}"
23
+
24
+ return constant if ns_const.const_defined?(const_fragment)
25
+
26
+ attempted << constant
27
+ end
28
+
29
+ # If we have reached this point, its means constant is not defined and
30
+ # NameError will be thrown if we attempt to camelize something like:
31
+ # `"#{namespace}::#{Dry::Core::Inflector.camelize(filename)}"`
32
+ # so we can assume naming convention was not respected in required
33
+ # file.
34
+
35
+ raise NameError, name_error_message(attempted)
36
+ end
37
+
38
+ private
39
+
40
+ def name_error_message(attempted)
41
+ "required file does not define expected constant name; either " \
42
+ "register your constant explicitly of try following the path" \
43
+ "naming convention like:\n\n\t- #{attempted.join("\n\t- ")}\n"
44
+ end
45
+
46
+ def filename
47
+ Pathname(file).basename('.rb')
48
+ end
49
+
50
+ def ns_const
51
+ @namespace_constant ||= Dry::Core::Inflector.constantize(namespace)
52
+ end
53
+
54
+ def path_arr
55
+ file_path << filename
56
+ end
57
+
58
+ def file_path
59
+ File.dirname(file).split("/") - directory.to_s.split("/")
60
+ end
61
+ end
62
+ end
63
+ end