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