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,20 @@
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 NoNamespace < Base
10
+ option :directory, type: PathnameType
11
+ option :entity, type: Types::Strict::Symbol
12
+
13
+ def call
14
+ Dry::Core::Inflector.camelize(
15
+ file.sub(/^#{directory}\/#{entity}\//, '').sub(EXTENSION_REGEX, '')
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ require 'pathname'
2
+
3
+ require 'dry/core/inflector'
4
+ require 'rom/setup/auto_registration_strategies/base'
5
+
6
+ module ROM
7
+ module AutoRegistrationStrategies
8
+ class WithNamespace < Base
9
+ option :directory, type: PathnameType
10
+
11
+ def call
12
+ Dry::Core::Inflector.camelize(
13
+ file.sub(/^#{directory.dirname}\//, '').sub(EXTENSION_REGEX, '')
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,103 @@
1
+ require 'rom/relation'
2
+ require 'rom/command'
3
+
4
+ require 'rom/registry'
5
+ require 'rom/command_registry'
6
+ require 'rom/mapper_registry'
7
+
8
+ require 'rom/container'
9
+ require 'rom/setup/finalize/finalize_commands'
10
+ require 'rom/setup/finalize/finalize_relations'
11
+ require 'rom/setup/finalize/finalize_mappers'
12
+
13
+ # temporary
14
+ require 'rom/configuration_dsl/relation'
15
+
16
+ module ROM
17
+ # This giant builds an container using defined classes for core parts of ROM
18
+ #
19
+ # It is used by the setup object after it's done gathering class definitions
20
+ #
21
+ # @private
22
+ class Finalize
23
+ attr_reader :gateways, :repo_adapter,
24
+ :relation_classes, :mapper_classes, :mapper_objects,
25
+ :command_classes, :plugins, :config, :notifications
26
+
27
+ # @api private
28
+ def initialize(options)
29
+ @gateways = options.fetch(:gateways)
30
+
31
+ @relation_classes = options.fetch(:relation_classes)
32
+ @command_classes = options.fetch(:command_classes)
33
+
34
+ mappers = options.fetch(:mappers, [])
35
+ @mapper_classes = mappers.select { |mapper| mapper.is_a?(Class) }
36
+ @mapper_objects = (mappers - @mapper_classes).reduce(:merge) || {}
37
+
38
+ @config = options.fetch(:config)
39
+ @notifications = options.fetch(:notifications)
40
+
41
+ @plugins = options.fetch(:plugins)
42
+ end
43
+
44
+ # Return adapter identifier for a given gateway object
45
+ #
46
+ # @return [Symbol]
47
+ #
48
+ # @api private
49
+ def adapter_for(gateway)
50
+ gateways[gateway].adapter
51
+ end
52
+
53
+ # Run the finalization process
54
+ #
55
+ # This creates relations, mappers and commands
56
+ #
57
+ # @return [Container]
58
+ #
59
+ # @api private
60
+ def run!
61
+ mappers = load_mappers
62
+ relations = load_relations(mappers)
63
+ commands = load_commands(relations)
64
+
65
+ container = Container.new(gateways, relations, mappers, commands)
66
+ container.freeze
67
+ container
68
+ end
69
+
70
+ private
71
+
72
+ # Build entire relation registry from all known relation subclasses
73
+ #
74
+ # This includes both classes created via DSL and explicit definitions
75
+ #
76
+ # @api private
77
+ def load_relations(mappers)
78
+ global_plugins = plugins.select { |p| p.relation? || p.schema? }
79
+
80
+ FinalizeRelations.new(
81
+ gateways,
82
+ relation_classes,
83
+ mappers: mappers,
84
+ plugins: global_plugins,
85
+ notifications: notifications
86
+ ).run!
87
+ end
88
+
89
+ # @api private
90
+ def load_mappers
91
+ FinalizeMappers.new(mapper_classes, mapper_objects).run!
92
+ end
93
+
94
+ # Build entire command registries
95
+ #
96
+ # This includes both classes created via DSL and explicit definitions
97
+ #
98
+ # @api private
99
+ def load_commands(relations)
100
+ FinalizeCommands.new(relations, gateways, command_classes, notifications).run!
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,60 @@
1
+ require 'rom/registry'
2
+ require 'rom/command_compiler'
3
+ require 'rom/command_registry'
4
+
5
+ module ROM
6
+ class Finalize
7
+ class FinalizeCommands
8
+ attr_reader :notifications
9
+
10
+ # Build command registry hash for provided relations
11
+ #
12
+ # @param [RelationRegistry] relations registry
13
+ # @param [Hash] gateways
14
+ # @param [Array] command_classes a list of command subclasses
15
+ #
16
+ # @api private
17
+ def initialize(relations, gateways, command_classes, notifications)
18
+ @relations = relations
19
+ @gateways = gateways
20
+ @command_classes = command_classes
21
+ @notifications = notifications
22
+ end
23
+
24
+ # @return [Hash]
25
+ #
26
+ # @api private
27
+ def run!
28
+ commands = @command_classes.map do |klass|
29
+ relation = @relations[klass.relation]
30
+ gateway = @gateways[relation.gateway]
31
+
32
+ notifications.trigger(
33
+ 'configuration.commands.class.before_build',
34
+ command: klass, gateway: gateway, dataset: relation.dataset
35
+ )
36
+
37
+ klass.extend_for_relation(relation) if klass.restrictable
38
+
39
+ klass.build(relation)
40
+ end
41
+
42
+ registry = Registry.new({})
43
+ compiler = CommandCompiler.new(@gateways, @relations, registry, notifications)
44
+
45
+ @relations.each do |(name, relation)|
46
+ commands.
47
+ select { |c| c.relation.name == relation.name }.
48
+ each { |c| relation.commands.elements[c.class.register_as || c.class.default_name] = c }
49
+
50
+ relation.commands.set_compiler(compiler)
51
+ relation.commands.set_mappers(relation.mappers)
52
+
53
+ registry.elements[name] = relation.commands
54
+ end
55
+
56
+ registry
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,56 @@
1
+ require 'rom/registry'
2
+
3
+ module ROM
4
+ class Finalize
5
+ class FinalizeMappers
6
+ attr_reader :mapper_classes, :mapper_objects, :registry_hash
7
+
8
+ # @api private
9
+ def initialize(mapper_classes, mapper_objects)
10
+ @mapper_classes = mapper_classes
11
+ @mapper_objects = mapper_objects
12
+
13
+ check_duplicate_registered_mappers
14
+
15
+ @registry_hash = [@mapper_classes.map(&:base_relation) + @mapper_objects.keys].
16
+ flatten.
17
+ uniq.
18
+ each_with_object({}) { |n, h| h[n] = {} }
19
+ end
20
+
21
+ # @api private
22
+ def run!
23
+ mappers = registry_hash.each_with_object({}) do |(relation_name, relation_mappers), h|
24
+ relation_mappers.update(build_mappers(relation_name))
25
+
26
+ if mapper_objects.key?(relation_name)
27
+ relation_mappers.update(mapper_objects[relation_name])
28
+ end
29
+
30
+ h[relation_name] = MapperRegistry.new(relation_mappers)
31
+ end
32
+
33
+ Registry.new(mappers)
34
+ end
35
+
36
+ private
37
+
38
+ def check_duplicate_registered_mappers
39
+ mappers_register_as = mapper_classes.map(&:register_as).compact
40
+ mappers_register_as.select { |register_as| mappers_register_as.count(register_as) > 1 }
41
+ .uniq
42
+ .each do |duplicated_mappers|
43
+ raise MapperAlreadyDefinedError,
44
+ "Mapper with `register_as #{duplicated_mappers.inspect}` registered more " \
45
+ "than once"
46
+ end
47
+ end
48
+
49
+ def build_mappers(relation_name)
50
+ mapper_classes.
51
+ select { |klass| klass.base_relation == relation_name }.
52
+ each_with_object({}) { |klass, h| h[klass.register_as || klass.relation] = klass.build }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,135 @@
1
+ require 'rom/constants'
2
+ require 'rom/relation_registry'
3
+ require 'rom/mapper_registry'
4
+
5
+ module ROM
6
+ class Finalize
7
+ class FinalizeRelations
8
+ attr_reader :notifications
9
+
10
+ # Build relation registry of specified descendant classes
11
+ #
12
+ # This is used by the setup
13
+ #
14
+ # @param [Hash] gateways
15
+ # @param [Array] relation_classes a list of relation descendants
16
+ #
17
+ # @api private
18
+ def initialize(gateways, relation_classes, notifications:, mappers: nil, plugins: EMPTY_ARRAY)
19
+ @gateways = gateways
20
+ @relation_classes = relation_classes
21
+ @mappers = mappers
22
+ @plugins = plugins
23
+ @notifications = notifications
24
+ end
25
+
26
+ # @return [Hash]
27
+ #
28
+ # @api private
29
+ def run!
30
+ relation_registry = RelationRegistry.new do |registry, relations|
31
+ @relation_classes.each do |klass|
32
+ unless klass.adapter
33
+ raise MissingAdapterIdentifierError,
34
+ "Relation class +#{klass}+ is missing the adapter identifier"
35
+ end
36
+
37
+ key = klass.relation_name.to_sym
38
+
39
+ if registry.key?(key)
40
+ raise RelationAlreadyDefinedError,
41
+ "Relation with name #{key.inspect} registered more than once"
42
+ end
43
+
44
+ klass.use(:registry_reader, relation_names)
45
+
46
+ notifications.trigger('configuration.relations.class.ready', relation: klass)
47
+
48
+ relations[key] = build_relation(klass, registry)
49
+ end
50
+
51
+ registry.each do |_, relation|
52
+ notifications.trigger(
53
+ 'configuration.relations.object.registered',
54
+ relation: relation, registry: registry
55
+ )
56
+ end
57
+ end
58
+
59
+ notifications.trigger(
60
+ 'configuration.relations.registry.created', registry: relation_registry
61
+ )
62
+
63
+ relation_registry
64
+ end
65
+
66
+ # @return [ROM::Relation]
67
+ #
68
+ # @api private
69
+ def build_relation(klass, registry)
70
+ # TODO: raise a meaningful error here and add spec covering the case
71
+ # where klass' gateway points to non-existant repo
72
+ gateway = @gateways.fetch(klass.gateway)
73
+
74
+ if klass.schema_proc && !klass.schema
75
+ plugins = schema_plugins
76
+
77
+ resolved_schema = klass.schema_proc.call do
78
+ plugins.each { |plugin| app_plugin(plugin) }
79
+ end
80
+
81
+ klass.set_schema!(resolved_schema)
82
+ end
83
+
84
+ notifications.trigger(
85
+ 'configuration.relations.schema.allocated',
86
+ schema: klass.schema, gateway: gateway, registry: registry
87
+ )
88
+
89
+ relation_plugins.each do |plugin|
90
+ plugin.apply_to(klass)
91
+ end
92
+
93
+ notifications.trigger(
94
+ 'configuration.relations.schema.set',
95
+ schema: resolved_schema, relation: klass, adapter: klass.adapter
96
+ )
97
+
98
+ schema = klass.schema
99
+ rel_key = schema.name.to_sym
100
+ dataset = gateway.dataset(schema.name.dataset).instance_exec(klass, &klass.dataset)
101
+
102
+ notifications.trigger(
103
+ 'configuration.relations.dataset.allocated',
104
+ dataset: dataset, relation: klass, adapter: klass.adapter
105
+ )
106
+
107
+ mappers = @mappers.key?(rel_key) ? @mappers[rel_key] : MapperRegistry.new
108
+
109
+ options = { __registry__: registry, mappers: mappers, schema: schema, **plugin_options }
110
+
111
+ klass.new(dataset, options)
112
+ end
113
+
114
+ # @api private
115
+ def plugin_options
116
+ relation_plugins.map(&:config).map(&:to_hash).reduce(:merge) || EMPTY_HASH
117
+ end
118
+
119
+ # @api private
120
+ def relation_plugins
121
+ @plugins.select(&:relation?)
122
+ end
123
+
124
+ # @api private
125
+ def schema_plugins
126
+ @plugins.select(&:schema?)
127
+ end
128
+
129
+ # @api private
130
+ def relation_names
131
+ @relation_classes.map(&:relation_name).map(&:relation).uniq
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,85 @@
1
+ module ROM
2
+ # @api private
3
+ module Configurable
4
+ class Config
5
+ WRITER_REGEXP = /=$/.freeze
6
+
7
+ attr_reader :settings
8
+
9
+ # @api private
10
+ def initialize(settings = {})
11
+ @settings = settings
12
+ end
13
+
14
+ # @api public
15
+ def [](name)
16
+ public_send(name)
17
+ end
18
+
19
+ # @api private
20
+ def key?(name)
21
+ settings.key?(name)
22
+ end
23
+
24
+ def to_hash
25
+ settings
26
+ end
27
+
28
+ # @api private
29
+ def freeze
30
+ settings.each_value(&:freeze)
31
+ super
32
+ end
33
+
34
+ # @api private
35
+ def respond_to_missing?(_name, _include_private = false)
36
+ true
37
+ end
38
+
39
+ def dup
40
+ self.class.new(dup_settings(settings))
41
+ end
42
+
43
+ private
44
+
45
+ def dup_settings(settings)
46
+ settings.each_with_object({}) do |(key, value), new_settings|
47
+ if value.is_a?(self.class)
48
+ new_settings[key] = value.dup
49
+ else
50
+ new_settings[key] = value
51
+ end
52
+ end
53
+ end
54
+
55
+ # @api private
56
+ def method_missing(meth, *args, &_block)
57
+ return settings.fetch(meth, nil) if frozen?
58
+
59
+ name = meth.to_s
60
+ key = name.gsub(WRITER_REGEXP, '').to_sym
61
+
62
+ if writer?(name)
63
+ settings[key] = args.first
64
+ else
65
+ settings.fetch(key) { settings[key] = self.class.new }
66
+ end
67
+ end
68
+
69
+ # @api private
70
+ def writer?(name)
71
+ !WRITER_REGEXP.match(name).nil?
72
+ end
73
+ end
74
+
75
+ def config
76
+ @config ||= Config.new
77
+ end
78
+
79
+ # @api public
80
+ def configure
81
+ yield(config)
82
+ self
83
+ end
84
+ end
85
+ end