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,17 @@
1
+ require 'rom/plugin_base'
2
+
3
+ module ROM
4
+ # ConfigurationPlugin is a simple object used to store configuration plugin configurations
5
+ #
6
+ # @private
7
+ class ConfigurationPlugin < PluginBase
8
+ # Apply this plugin to the provided configuration
9
+ #
10
+ # @param [ROM::Configuration] configuration
11
+ #
12
+ # @api private
13
+ def apply_to(configuration, options = {})
14
+ mod.apply(configuration, options)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,64 @@
1
+ require 'dry/core/constants'
2
+
3
+ # Constants and errors common in the whole library
4
+ module ROM
5
+ include Dry::Core::Constants
6
+
7
+ AdapterLoadError = Class.new(StandardError)
8
+
9
+ class AdapterNotPresentError < StandardError
10
+ def initialize(adapter, component)
11
+ super(
12
+ "Failed to find #{component} class for #{adapter} adapter. " \
13
+ "Make sure ROM setup was started and the adapter identifier is correct."
14
+ )
15
+ end
16
+ end
17
+
18
+ EnvAlreadyFinalizedError = Class.new(StandardError)
19
+ RelationAlreadyDefinedError = Class.new(StandardError)
20
+ MapperAlreadyDefinedError = Class.new(StandardError)
21
+ NoRelationError = Class.new(StandardError)
22
+ CommandError = Class.new(StandardError)
23
+ KeyMissing = Class.new(ROM::CommandError)
24
+ TupleCountMismatchError = Class.new(CommandError)
25
+ UnknownPluginError = Class.new(StandardError)
26
+ UnsupportedRelationError = Class.new(StandardError)
27
+ MissingAdapterIdentifierError = Class.new(StandardError)
28
+
29
+ class ElementNotFoundError < KeyError
30
+ def initialize(key, registry)
31
+ super(set_message(key, registry))
32
+ end
33
+
34
+ def set_message(key, registry)
35
+ "#{key.inspect} doesn't exist in #{registry.class.name} registry"
36
+ end
37
+ end
38
+
39
+ MapperMissingError = Class.new(ElementNotFoundError)
40
+
41
+ CommandNotFoundError = Class.new(ElementNotFoundError) do
42
+ def set_message(key, registry)
43
+ "There is no :#{key} command for :#{registry.relation_name} relation"
44
+ end
45
+ end
46
+
47
+ MissingSchemaClassError = Class.new(StandardError) do
48
+ def initialize(klass)
49
+ super("#{klass.inspect} relation is missing schema_class")
50
+ end
51
+ end
52
+
53
+ MissingSchemaError = Class.new(StandardError) do
54
+ def initialize(klass)
55
+ super("#{klass.inspect} relation is missing schema definition")
56
+ end
57
+ end
58
+
59
+ DuplicateConfigurationError = Class.new(StandardError)
60
+ DuplicateContainerError = Class.new(StandardError)
61
+
62
+ InvalidOptionValueError = Class.new(StandardError)
63
+ InvalidOptionKeyError = Class.new(StandardError)
64
+ end
@@ -0,0 +1,147 @@
1
+ require 'dry/container'
2
+
3
+ require 'rom/cache'
4
+
5
+ module ROM
6
+ # ROM container is an isolated environment with no global state where all
7
+ # components are registered. Container objects provide access to your
8
+ # relations, commands and mappers. ROM containers are usually configured and
9
+ # handled via framework integrations, although it is easy to use them
10
+ # standalone.
11
+ #
12
+ # There are 3 types of container setup:
13
+ #
14
+ # * Setup DSL - a simple block-based configuration which allows configuring
15
+ # all components and gives you back a container instance. This type is suitable
16
+ # for small scripts, or in some cases rake tasks
17
+ # * Explicit setup - this type requires creating a configuration object,
18
+ # registering component classes (ie relation classes) and passing the config
19
+ # to container builder function. This type is suitable when your environment
20
+ # is not typical and you need full control over component registration
21
+ # * Explicit setup with auto-registration - same as explicit setup but allows
22
+ # you to configure auto-registration mechanism which will register component
23
+ # classes for you, based on dir/file naming conventions. This is the most
24
+ # common type of setup that's used by framework integrations
25
+ #
26
+ # @example in-line setup
27
+ # rom = ROM.container(:sql, 'sqlite::memory') do |config|
28
+ # config.default.create_table :users do
29
+ # primary_key :id
30
+ # column :name, String, null: false
31
+ # end
32
+ #
33
+ # config.relation(:users) do
34
+ # schema(infer: true)
35
+ #
36
+ # def by_name(name)
37
+ # where(name: name)
38
+ # end
39
+ # end
40
+ # end
41
+ #
42
+ # rom.relations[:users].insert(name: "Jane")
43
+ #
44
+ # rom.relations[:users].by_name("Jane").to_a
45
+ # # [{:id=>1, :name=>"Jane"}]
46
+ #
47
+ # @example multi-step setup with explicit component classes
48
+ # config = ROM::Configuration.new(:sql, 'sqlite::memory')
49
+ #
50
+ # config.default.create_table :users do
51
+ # primary_key :id
52
+ # column :name, String, null: false
53
+ # end
54
+ #
55
+ # class Users < ROM::Relation[:sql]
56
+ # schema(:users, infer: true)
57
+ #
58
+ # def by_name(name)
59
+ # where(name: name)
60
+ # end
61
+ # end
62
+ #
63
+ # config.register_relation(Users)
64
+ #
65
+ # rom = ROM.container(config)
66
+ #
67
+ # rom.relations[:users].insert(name: "Jane")
68
+ #
69
+ # rom.relations[:users].by_name("Jane").to_a
70
+ # # [{:id=>1, :name=>"Jane"}]
71
+ #
72
+ #
73
+ # @example multi-step setup with auto-registration
74
+ # config = ROM::Configuration.new(:sql, 'sqlite::memory')
75
+ # config.auto_registration('./persistence', namespace: false)
76
+ #
77
+ # config.default.create_table :users do
78
+ # primary_key :id
79
+ # column :name, String, null: false
80
+ # end
81
+ #
82
+ # # ./persistence/relations/users.rb
83
+ # class Users < ROM::Relation[:sql]
84
+ # schema(infer: true)
85
+ #
86
+ # def by_name(name)
87
+ # where(name: name)
88
+ # end
89
+ # end
90
+ #
91
+ # rom = ROM.container(config)
92
+ #
93
+ # rom.relations[:users].insert(name: "Jane")
94
+ #
95
+ # rom.relations[:users].by_name("Jane").to_a
96
+ # # [{:id=>1, :name=>"Jane"}]
97
+ #
98
+ # @api public
99
+ class Container
100
+ include Dry::Container::Mixin
101
+ include Dry::Equalizer(:gateways, :relations, :mappers, :commands)
102
+
103
+ # @api private
104
+ def self.new(gateways, relations, mappers, commands)
105
+ super().tap do |container|
106
+ container.register(:gateways, gateways)
107
+ container.register(:mappers, mappers)
108
+ container.register(:commands, commands)
109
+ container.register(:relations, relations)
110
+ end
111
+ end
112
+
113
+ # @api public
114
+ def gateways
115
+ self[:gateways]
116
+ end
117
+
118
+ # @api public
119
+ def mappers
120
+ self[:mappers]
121
+ end
122
+
123
+ # @api public
124
+ def relations
125
+ self[:relations]
126
+ end
127
+
128
+ # @api public
129
+ def commands
130
+ self[:commands]
131
+ end
132
+
133
+ # Disconnect all gateways
134
+ #
135
+ # @example
136
+ # rom = ROM.container(:sql, 'sqlite://my_db.sqlite')
137
+ # rom.relations[:users].insert(name: "Jane")
138
+ # rom.disconnect
139
+ #
140
+ # @return [Hash<Symbol=>Gateway>] a hash with disconnected gateways
141
+ #
142
+ # @api public
143
+ def disconnect
144
+ gateways.each_value(&:disconnect)
145
+ end
146
+ end
147
+ end
data/lib/rom/core.rb ADDED
@@ -0,0 +1,46 @@
1
+ require 'dry/equalizer'
2
+ require 'dry/core/constants'
3
+
4
+ require 'rom/version'
5
+ require 'rom/constants'
6
+
7
+ # core parts
8
+ require 'rom/configuration_plugin'
9
+ require 'rom/plugin'
10
+ require 'rom/schema_plugin'
11
+ require 'rom/relation'
12
+ require 'rom-mapper'
13
+ require 'rom/mapper/configuration_plugin'
14
+ require 'rom/commands'
15
+
16
+ # rom Global
17
+ require 'rom/global'
18
+
19
+ # rom configurations
20
+ require 'rom/configuration'
21
+
22
+ # container with registries
23
+ require 'rom/container'
24
+
25
+ # container factory
26
+ require 'rom/create_container'
27
+
28
+ # register core plugins
29
+ require 'rom/plugins/configuration/configuration_dsl'
30
+ require 'rom/plugins/relation/registry_reader'
31
+ require 'rom/plugins/relation/instrumentation'
32
+ require 'rom/plugins/command/schema'
33
+ require 'rom/plugins/schema/timestamps'
34
+
35
+ module ROM
36
+ extend Global
37
+
38
+ plugins do
39
+ register :mappers, ROM::Mapper::ConfigurationPlugin, type: :configuration
40
+ register :macros, ROM::ConfigurationPlugins::ConfigurationDSL, type: :configuration
41
+ register :timestamps, ROM::Plugins::Schema::Timestamps, type: :schema
42
+ register :registry_reader, ROM::Plugins::Relation::RegistryReader, type: :relation
43
+ register :instrumentation, ROM::Plugins::Relation::Instrumentation, type: :relation
44
+ register :schema, ROM::Plugins::Command::Schema, type: :command
45
+ end
46
+ end
@@ -0,0 +1,60 @@
1
+ require 'rom/configuration'
2
+ require 'rom/environment'
3
+ require 'rom/setup'
4
+ require 'rom/setup/finalize'
5
+
6
+ module ROM
7
+ class CreateContainer
8
+ attr_reader :container
9
+
10
+ def initialize(environment, setup)
11
+ @container = finalize(environment, setup)
12
+ end
13
+
14
+ private
15
+
16
+ def finalize(environment, setup)
17
+ environment.configure do |config|
18
+ environment.gateways.each_key do |key|
19
+ gateway_config = config.gateways[key]
20
+ gateway_config.infer_relations = true unless gateway_config.key?(:infer_relations)
21
+ end
22
+ end
23
+
24
+ finalize = Finalize.new(
25
+ gateways: environment.gateways,
26
+ relation_classes: setup.relation_classes,
27
+ command_classes: setup.command_classes,
28
+ mappers: setup.mapper_classes,
29
+ plugins: setup.plugins,
30
+ notifications: setup.notifications,
31
+ config: environment.config.dup.freeze
32
+ )
33
+
34
+ finalize.run!
35
+ end
36
+ end
37
+
38
+ class InlineCreateContainer < CreateContainer
39
+ def initialize(*args, &block)
40
+ case args.first
41
+ when Configuration
42
+ environment = args.first.environment
43
+ setup = args.first.setup
44
+ when Environment
45
+ environment = args.first
46
+ setup = args[1]
47
+ else
48
+ configuration = Configuration.new(*args, &block)
49
+ environment = configuration.environment
50
+ setup = configuration.setup
51
+ end
52
+
53
+ super(environment, setup)
54
+ end
55
+ end
56
+
57
+ def self.container(*args, &block)
58
+ InlineCreateContainer.new(*args, &block).container
59
+ end
60
+ end
@@ -0,0 +1,94 @@
1
+ module ROM
2
+ # Helper module for dataset classes
3
+ #
4
+ # It provides a constructor accepting data, header and an optional row_proc.
5
+ # This module is used internally by EnumerableDataset and ArrayDataset.
6
+ #
7
+ # @private
8
+ module DataProxy
9
+ NON_FORWARDABLE = [
10
+ :each, :to_a, :to_ary, :kind_of?, :instance_of?, :is_a?
11
+ ].freeze
12
+
13
+ # Wrapped data array
14
+ #
15
+ # @return [Object] Data object for the iterator
16
+ #
17
+ # @api private
18
+ attr_reader :data
19
+
20
+ # @return [Proc] tuple processing proc
21
+ #
22
+ # @api private
23
+ attr_reader :row_proc
24
+
25
+ # Extends the class with `forward` DSL and Equalizer using `data` attribute
26
+ #
27
+ # @see ClassMethods#forward
28
+ #
29
+ # @api private
30
+ def self.included(klass)
31
+ klass.class_eval do
32
+ extend ClassMethods
33
+
34
+ include Dry::Equalizer(:data)
35
+
36
+ option :row_proc, default: -> { self.class.row_proc }
37
+ end
38
+ end
39
+
40
+ # Iterate over data using row_proc
41
+ #
42
+ # @return [Enumerator] if block is not given
43
+ #
44
+ # @api private
45
+ def each
46
+ return to_enum unless block_given?
47
+ data.each { |tuple| yield(row_proc[tuple]) }
48
+ end
49
+
50
+ module ClassMethods
51
+ # Default no-op tuple proc
52
+ #
53
+ # @return [Proc]
54
+ #
55
+ # @api private
56
+ def row_proc
57
+ -> tuple { tuple }
58
+ end
59
+
60
+ # Forward provided methods to the underlaying data object
61
+ #
62
+ # @example
63
+ #
64
+ # class MyDataset
65
+ # include DataProxy
66
+ #
67
+ # forward(:find_all, :map)
68
+ # end
69
+ #
70
+ # @return [undefined]
71
+ #
72
+ # @api public
73
+ def forward(*methods)
74
+ # FIXME: we should probably raise if one of the non-forwardable methods
75
+ # was provided
76
+ (methods - NON_FORWARDABLE).each do |method_name|
77
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
78
+ def #{method_name}(*args, &block)
79
+ response = data.public_send(#{method_name.inspect}, *args, &block)
80
+
81
+ if response.equal?(data)
82
+ self
83
+ elsif response.is_a?(data.class)
84
+ self.class.new(response)
85
+ else
86
+ response
87
+ end
88
+ end
89
+ RUBY
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,68 @@
1
+ require 'rom/initializer'
2
+ require 'rom/data_proxy'
3
+
4
+ module ROM
5
+ # A helper module that adds data-proxy behavior to an enumerable object
6
+ #
7
+ # This module is intended to be used by gateways
8
+ #
9
+ # Class that includes this module can define `row_proc` class method which
10
+ # must return a proc-like object which will be used to process each element
11
+ # in the enumerable
12
+ #
13
+ # @example
14
+ # class MyDataset
15
+ # include ROM::EnumerableDataset
16
+ #
17
+ # def self.row_proc
18
+ # -> tuple { tuple.each_with_object({}) { |(k,v), h| h[k.to_sym] = v } }
19
+ # end
20
+ # end
21
+ #
22
+ # ds = MyDataset.new([{ 'name' => 'Jane' }, [:name])
23
+ # ds.to_a # => { :name => 'Jane' }
24
+ #
25
+ # @api public
26
+ module EnumerableDataset
27
+ extend DataProxy::ClassMethods
28
+ include Enumerable
29
+
30
+ # Coerce a dataset to an array
31
+ #
32
+ # @return [Array]
33
+ #
34
+ # @api public
35
+ alias_method :to_ary, :to_a
36
+
37
+ # Included hook which extends a class with DataProxy behavior
38
+ #
39
+ # This module can also be included into other modules so we apply the
40
+ # extension only for classes
41
+ #
42
+ # @api private
43
+ def self.included(klass)
44
+ return unless klass.is_a?(Class)
45
+
46
+ klass.class_eval do
47
+ extend Initializer
48
+ include DataProxy
49
+
50
+ param :data
51
+ end
52
+ end
53
+
54
+ forward :take
55
+
56
+ [
57
+ :chunk, :collect, :collect_concat, :drop_while, :find_all, :flat_map,
58
+ :grep, :map, :reject, :select, :sort, :sort_by, :take_while
59
+ ].each do |method|
60
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
61
+ def #{method}(*args, &block)
62
+ return to_enum unless block
63
+ self.class.new(super(*args, &block), options)
64
+ end
65
+ RUBY
66
+ end
67
+ end
68
+ end