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,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