rom 5.4.1 → 6.0.0.alpha1

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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -65
  3. data/LICENSE +1 -1
  4. data/README.md +7 -6
  5. data/lib/rom/array_dataset.rb +46 -0
  6. data/lib/rom/associations/abstract.rb +217 -0
  7. data/lib/rom/associations/definitions/abstract.rb +150 -0
  8. data/lib/rom/associations/definitions/many_to_many.rb +29 -0
  9. data/lib/rom/associations/definitions/many_to_one.rb +14 -0
  10. data/lib/rom/associations/definitions/one_to_many.rb +14 -0
  11. data/lib/rom/associations/definitions/one_to_one.rb +14 -0
  12. data/lib/rom/associations/definitions/one_to_one_through.rb +14 -0
  13. data/lib/rom/associations/definitions.rb +7 -0
  14. data/lib/rom/associations/many_to_many.rb +128 -0
  15. data/lib/rom/associations/many_to_one.rb +65 -0
  16. data/lib/rom/associations/one_to_many.rb +65 -0
  17. data/lib/rom/associations/one_to_one.rb +13 -0
  18. data/lib/rom/associations/one_to_one_through.rb +13 -0
  19. data/lib/rom/associations/through_identifier.rb +41 -0
  20. data/lib/rom/attribute.rb +425 -0
  21. data/lib/rom/auto_curry.rb +70 -0
  22. data/lib/rom/cache.rb +87 -0
  23. data/lib/rom/changeset/associated.rb +110 -0
  24. data/lib/rom/changeset/create.rb +18 -0
  25. data/lib/rom/changeset/delete.rb +15 -0
  26. data/lib/rom/changeset/extensions/relation.rb +26 -0
  27. data/lib/rom/changeset/pipe.rb +81 -0
  28. data/lib/rom/changeset/pipe_registry.rb +27 -0
  29. data/lib/rom/changeset/stateful.rb +285 -0
  30. data/lib/rom/changeset/update.rb +81 -0
  31. data/lib/rom/changeset.rb +185 -0
  32. data/lib/rom/command.rb +351 -0
  33. data/lib/rom/command_compiler.rb +201 -0
  34. data/lib/rom/command_proxy.rb +36 -0
  35. data/lib/rom/commands/class_interface.rb +236 -0
  36. data/lib/rom/commands/composite.rb +55 -0
  37. data/lib/rom/commands/create.rb +15 -0
  38. data/lib/rom/commands/delete.rb +16 -0
  39. data/lib/rom/commands/graph/class_interface.rb +64 -0
  40. data/lib/rom/commands/graph/input_evaluator.rb +94 -0
  41. data/lib/rom/commands/graph.rb +88 -0
  42. data/lib/rom/commands/lazy/create.rb +35 -0
  43. data/lib/rom/commands/lazy/delete.rb +39 -0
  44. data/lib/rom/commands/lazy/update.rb +46 -0
  45. data/lib/rom/commands/lazy.rb +106 -0
  46. data/lib/rom/commands/update.rb +16 -0
  47. data/lib/rom/commands.rb +5 -0
  48. data/lib/rom/compat/auto_registration.rb +115 -0
  49. data/lib/rom/compat/auto_registration_strategies/base.rb +29 -0
  50. data/lib/rom/compat/auto_registration_strategies/custom_namespace.rb +84 -0
  51. data/lib/rom/compat/auto_registration_strategies/no_namespace.rb +33 -0
  52. data/lib/rom/compat/auto_registration_strategies/with_namespace.rb +29 -0
  53. data/lib/rom/compat/command.rb +74 -0
  54. data/lib/rom/compat/components/dsl/schema.rb +130 -0
  55. data/lib/rom/compat/components.rb +91 -0
  56. data/lib/rom/compat/global.rb +17 -0
  57. data/lib/rom/compat/mapper.rb +22 -0
  58. data/lib/rom/compat/registries.rb +47 -0
  59. data/lib/rom/compat/relation.rb +40 -0
  60. data/lib/rom/compat/schema/dsl.rb +260 -0
  61. data/lib/rom/compat/setting_proxy.rb +44 -0
  62. data/lib/rom/compat/setup.rb +151 -0
  63. data/lib/rom/compat/transformer.rb +49 -0
  64. data/lib/rom/compat.rb +22 -0
  65. data/lib/rom/components/association.rb +26 -0
  66. data/lib/rom/components/command.rb +24 -0
  67. data/lib/rom/components/core.rb +148 -0
  68. data/lib/rom/components/dataset.rb +60 -0
  69. data/lib/rom/components/dsl/association.rb +47 -0
  70. data/lib/rom/components/dsl/command.rb +60 -0
  71. data/lib/rom/components/dsl/core.rb +126 -0
  72. data/lib/rom/components/dsl/dataset.rb +33 -0
  73. data/lib/rom/components/dsl/gateway.rb +14 -0
  74. data/lib/rom/components/dsl/mapper.rb +70 -0
  75. data/lib/rom/components/dsl/relation.rb +49 -0
  76. data/lib/rom/components/dsl/schema.rb +150 -0
  77. data/lib/rom/components/dsl/view.rb +82 -0
  78. data/lib/rom/components/dsl.rb +255 -0
  79. data/lib/rom/components/gateway.rb +50 -0
  80. data/lib/rom/components/mapper.rb +29 -0
  81. data/lib/rom/components/provider.rb +160 -0
  82. data/lib/rom/components/registry.rb +154 -0
  83. data/lib/rom/components/relation.rb +41 -0
  84. data/lib/rom/components/schema.rb +61 -0
  85. data/lib/rom/components/view.rb +55 -0
  86. data/lib/rom/components.rb +55 -0
  87. data/lib/rom/configuration_dsl.rb +4 -0
  88. data/lib/rom/constants.rb +135 -0
  89. data/lib/rom/container.rb +182 -0
  90. data/lib/rom/core.rb +125 -0
  91. data/lib/rom/data_proxy.rb +97 -0
  92. data/lib/rom/enumerable_dataset.rb +70 -0
  93. data/lib/rom/gateway.rb +232 -0
  94. data/lib/rom/global.rb +56 -0
  95. data/lib/rom/header/attribute.rb +190 -0
  96. data/lib/rom/header.rb +198 -0
  97. data/lib/rom/inferrer.rb +55 -0
  98. data/lib/rom/initializer.rb +80 -0
  99. data/lib/rom/lint/enumerable_dataset.rb +56 -0
  100. data/lib/rom/lint/gateway.rb +120 -0
  101. data/lib/rom/lint/linter.rb +79 -0
  102. data/lib/rom/lint/spec.rb +22 -0
  103. data/lib/rom/lint/test.rb +98 -0
  104. data/lib/rom/loader.rb +161 -0
  105. data/lib/rom/mapper/attribute_dsl.rb +480 -0
  106. data/lib/rom/mapper/dsl.rb +107 -0
  107. data/lib/rom/mapper/model_dsl.rb +61 -0
  108. data/lib/rom/mapper.rb +99 -0
  109. data/lib/rom/mapper_compiler.rb +84 -0
  110. data/lib/rom/memory/associations/many_to_many.rb +12 -0
  111. data/lib/rom/memory/associations/many_to_one.rb +12 -0
  112. data/lib/rom/memory/associations/one_to_many.rb +12 -0
  113. data/lib/rom/memory/associations/one_to_one.rb +12 -0
  114. data/lib/rom/memory/associations.rb +6 -0
  115. data/lib/rom/memory/commands.rb +60 -0
  116. data/lib/rom/memory/dataset.rb +127 -0
  117. data/lib/rom/memory/gateway.rb +66 -0
  118. data/lib/rom/memory/mapper_compiler.rb +10 -0
  119. data/lib/rom/memory/relation.rb +91 -0
  120. data/lib/rom/memory/schema.rb +32 -0
  121. data/lib/rom/memory/storage.rb +61 -0
  122. data/lib/rom/memory/types.rb +11 -0
  123. data/lib/rom/memory.rb +7 -0
  124. data/lib/rom/model_builder.rb +103 -0
  125. data/lib/rom/open_struct.rb +112 -0
  126. data/lib/rom/pipeline.rb +111 -0
  127. data/lib/rom/plugin.rb +130 -0
  128. data/lib/rom/plugins/class_methods.rb +37 -0
  129. data/lib/rom/plugins/command/schema.rb +45 -0
  130. data/lib/rom/plugins/command/timestamps.rb +149 -0
  131. data/lib/rom/plugins/dsl.rb +53 -0
  132. data/lib/rom/plugins/relation/changeset.rb +97 -0
  133. data/lib/rom/plugins/relation/instrumentation.rb +66 -0
  134. data/lib/rom/plugins/relation/registry_reader.rb +36 -0
  135. data/lib/rom/plugins/schema/timestamps.rb +59 -0
  136. data/lib/rom/plugins.rb +100 -0
  137. data/lib/rom/processor/composer.rb +37 -0
  138. data/lib/rom/processor/transformer.rb +415 -0
  139. data/lib/rom/processor.rb +30 -0
  140. data/lib/rom/registries/associations.rb +26 -0
  141. data/lib/rom/registries/commands.rb +11 -0
  142. data/lib/rom/registries/container.rb +12 -0
  143. data/lib/rom/registries/datasets.rb +21 -0
  144. data/lib/rom/registries/gateways.rb +8 -0
  145. data/lib/rom/registries/mappers.rb +21 -0
  146. data/lib/rom/registries/nestable.rb +32 -0
  147. data/lib/rom/registries/relations.rb +8 -0
  148. data/lib/rom/registries/root.rb +203 -0
  149. data/lib/rom/registries/schemas.rb +44 -0
  150. data/lib/rom/registries/views.rb +11 -0
  151. data/lib/rom/relation/class_interface.rb +61 -0
  152. data/lib/rom/relation/combined.rb +160 -0
  153. data/lib/rom/relation/commands.rb +65 -0
  154. data/lib/rom/relation/composite.rb +53 -0
  155. data/lib/rom/relation/curried.rb +129 -0
  156. data/lib/rom/relation/graph.rb +107 -0
  157. data/lib/rom/relation/loaded.rb +136 -0
  158. data/lib/rom/relation/materializable.rb +62 -0
  159. data/lib/rom/relation/name.rb +122 -0
  160. data/lib/rom/relation/wrap.rb +64 -0
  161. data/lib/rom/relation.rb +625 -0
  162. data/lib/rom/repository/class_interface.rb +162 -0
  163. data/lib/rom/repository/relation_reader.rb +48 -0
  164. data/lib/rom/repository/root.rb +75 -0
  165. data/lib/rom/repository/session.rb +60 -0
  166. data/lib/rom/repository.rb +179 -0
  167. data/lib/rom/schema/associations_dsl.rb +222 -0
  168. data/lib/rom/schema/inferrer.rb +106 -0
  169. data/lib/rom/schema.rb +471 -0
  170. data/lib/rom/settings.rb +141 -0
  171. data/lib/rom/setup.rb +297 -0
  172. data/lib/rom/struct.rb +99 -0
  173. data/lib/rom/struct_compiler.rb +114 -0
  174. data/lib/rom/support/configurable.rb +213 -0
  175. data/lib/rom/support/inflector.rb +31 -0
  176. data/lib/rom/support/memoizable.rb +61 -0
  177. data/lib/rom/support/notifications.rb +238 -0
  178. data/lib/rom/transaction.rb +26 -0
  179. data/lib/rom/transformer.rb +46 -0
  180. data/lib/rom/types.rb +74 -0
  181. data/lib/rom/version.rb +1 -1
  182. data/lib/rom-changeset.rb +4 -0
  183. data/lib/rom-core.rb +3 -0
  184. data/lib/rom-repository.rb +4 -0
  185. data/lib/rom.rb +3 -3
  186. metadata +302 -23
data/lib/rom/memory.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/memory/gateway"
4
+ require "rom/memory/relation"
5
+ require "rom/memory/mapper_compiler"
6
+
7
+ ROM.register_adapter(:memory, ROM::Memory)
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/support/inflector"
4
+
5
+ module ROM
6
+ # Model builders can be used to build model classes for mappers
7
+ #
8
+ # This is used when you define a mapper and setup a model using :name option.
9
+ #
10
+ # @example
11
+ # # this will define User model for you
12
+ # class UserMapper < ROM::Mapper
13
+ # model name: 'User'
14
+ # attribute :id
15
+ # attribute :name
16
+ # end
17
+ #
18
+ # @private
19
+ class ModelBuilder
20
+ attr_reader :name
21
+
22
+ attr_reader :const_name, :namespace, :klass
23
+
24
+ # Return model builder subclass based on type
25
+ #
26
+ # @param [Symbol] type
27
+ #
28
+ # @return [Class]
29
+ #
30
+ # @api private
31
+ def self.[](type)
32
+ case type
33
+ when :poro then PORO
34
+ else
35
+ raise ArgumentError, "#{type.inspect} is not a supported model type"
36
+ end
37
+ end
38
+
39
+ # Build a model class
40
+ #
41
+ # @return [Class]
42
+ #
43
+ # @api private
44
+ def self.call(*args)
45
+ new(*args).call
46
+ end
47
+
48
+ # @api private
49
+ def initialize(options = {})
50
+ @name = options[:name]
51
+
52
+ if name
53
+ parts = name.split("::")
54
+
55
+ @const_name = parts.pop
56
+
57
+ @namespace =
58
+ if parts.any?
59
+ Inflector.constantize(parts.join("::"))
60
+ else
61
+ Object
62
+ end
63
+ end
64
+ end
65
+
66
+ # Define a model class constant
67
+ #
68
+ # @api private
69
+ def define_const
70
+ namespace.const_set(const_name, klass)
71
+ end
72
+
73
+ # Build a model class supporting specific attributes
74
+ #
75
+ # @return [Class]
76
+ #
77
+ # @api private
78
+ def call(attrs)
79
+ define_class(attrs)
80
+ define_const if const_name
81
+ @klass
82
+ end
83
+
84
+ # PORO model class builder
85
+ #
86
+ # @private
87
+ class PORO < ModelBuilder
88
+ def define_class(attrs)
89
+ @klass = Class.new
90
+
91
+ @klass.send(:attr_reader, *attrs)
92
+
93
+ @klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
94
+ def initialize(params)
95
+ #{attrs.map { |name| "@#{name} = params[:#{name}]" }.join("\n")}
96
+ end
97
+ RUBY
98
+
99
+ self
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ require "dry/core/equalizer"
6
+ require_relative "constants"
7
+
8
+ module ROM
9
+ # ROM's open structs are used for relations with empty schemas.
10
+ # Such relations may exist in cases like using raw SQL strings
11
+ # where schema was not explicitly defined using `view` DSL.
12
+ #
13
+ # @api public
14
+ class OpenStruct
15
+ include Dry::Equalizer(:__keys__, :to_h, inspect: false)
16
+
17
+ include Enumerable
18
+
19
+ IVAR = -> v { :"@#{v}" }
20
+ WRITER = -> v { :"#{v}=" }
21
+
22
+ # @api private
23
+ attr_reader :__keys__
24
+
25
+ # @api private
26
+ def initialize(attributes = EMPTY_HASH)
27
+ @__keys__ = Set.new
28
+ __load__(attributes)
29
+ end
30
+
31
+ # @api public
32
+ def each
33
+ __keys__.each { |key| yield(key, __get__(key)) }
34
+ end
35
+
36
+ # @api public
37
+ def to_h
38
+ map { |key, value| [key, value] }.to_h
39
+ end
40
+ alias_method :to_hash, :to_h
41
+
42
+ # @api public
43
+ def update(other)
44
+ __load__(other)
45
+ end
46
+
47
+ # @api public
48
+ def fetch(key, &block)
49
+ to_h.fetch(key, &block)
50
+ end
51
+
52
+ # @api public
53
+ def [](key)
54
+ __send__(key)
55
+ end
56
+
57
+ # @api public
58
+ def []=(key, value)
59
+ __set__(key, value)
60
+ end
61
+
62
+ # @api public
63
+ def key?(key)
64
+ __keys__.include?(key)
65
+ end
66
+
67
+ # @api public
68
+ def inspect
69
+ %(#<#{self.class} #{to_h}>)
70
+ end
71
+
72
+ # @api private
73
+ def respond_to_missing?(meth, include_private = false)
74
+ super || key?(meth)
75
+ end
76
+
77
+ private
78
+
79
+ # @api public
80
+ def method_missing(meth, *args, &block)
81
+ if meth.to_s.end_with?("=")
82
+ key = meth.to_s.tr("=", "").to_sym
83
+
84
+ if methods.include?(key)
85
+ super
86
+ else
87
+ __set__(key, *args)
88
+ end
89
+ elsif key?(meth)
90
+ __get__(meth)
91
+ else
92
+ super
93
+ end
94
+ end
95
+
96
+ # @api private
97
+ def __load__(attributes)
98
+ Hash(attributes).each { |key, value| __set__(key, value) }
99
+ end
100
+
101
+ # @api private
102
+ def __set__(key, value)
103
+ __keys__ << key
104
+ instance_variable_set(IVAR[key], value.is_a?(Hash) ? self.class.new(value) : value)
105
+ end
106
+
107
+ # @api private
108
+ def __get__(key)
109
+ instance_variable_get(IVAR[key])
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ # Data pipeline common interface
5
+ #
6
+ # @api private
7
+ module Pipeline
8
+ # Common `>>` operator extension
9
+ #
10
+ # @api private
11
+ module Operator
12
+ # Compose two relation with a left-to-right composition
13
+ #
14
+ # @example
15
+ # users.by_name('Jane') >> tasks.for_users
16
+ #
17
+ # @param [Relation] other The right relation
18
+ #
19
+ # @return [Relation::Composite]
20
+ #
21
+ # @api public
22
+ def >>(other)
23
+ composite_class.new(self, other)
24
+ end
25
+
26
+ private
27
+
28
+ # @api private
29
+ def composite_class
30
+ raise NotImplementedError
31
+ end
32
+ end
33
+
34
+ include Operator
35
+
36
+ # Send data through specified mappers
37
+ #
38
+ # @return [Relation::Composite]
39
+ #
40
+ # @api public
41
+ def map_with(*names)
42
+ [self, *names.map { |name| mappers[name] }]
43
+ .reduce { |a, e| composite_class.new(a, e) }
44
+ end
45
+
46
+ # Forwards messages to the left side of a pipeline
47
+ #
48
+ # @api private
49
+ module Proxy
50
+ # @api private
51
+ def respond_to_missing?(name, include_private = false)
52
+ left.respond_to?(name) || super
53
+ end
54
+
55
+ private
56
+
57
+ # Check if response from method missing should be decorated
58
+ #
59
+ # @api private
60
+ def decorate?(response)
61
+ response.is_a?(left.class)
62
+ end
63
+
64
+ # @api private
65
+ def method_missing(name, *args, &block)
66
+ if left.respond_to?(name)
67
+ response = left.__send__(name, *args, &block)
68
+
69
+ if decorate?(response)
70
+ self.class.new(response, right)
71
+ else
72
+ response
73
+ end
74
+ else
75
+ super
76
+ end
77
+ end
78
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
79
+ end
80
+
81
+ # Base composite class with left-to-right pipeline behavior
82
+ #
83
+ # @api private
84
+ class Composite
85
+ (Kernel.private_instance_methods - %i[respond_to_missing? block_given?])
86
+ .each(&method(:undef_method))
87
+
88
+ include Dry::Equalizer(:left, :right)
89
+ include Proxy
90
+
91
+ # @api private
92
+ attr_reader :left
93
+
94
+ # @api private
95
+ attr_reader :right
96
+
97
+ # @api private
98
+ def initialize(left, right)
99
+ @left = left
100
+ @right = right
101
+ end
102
+
103
+ # Compose this composite with another object
104
+ #
105
+ # @api public
106
+ def >>(other)
107
+ self.class.new(self, other)
108
+ end
109
+ end
110
+ end
111
+ end
data/lib/rom/plugin.rb ADDED
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/initializer"
4
+ require "rom/open_struct"
5
+
6
+ module ROM
7
+ # Plugin is a simple object used to store plugin configurations
8
+ #
9
+ # @private
10
+ class Plugin
11
+ include Dry::Equalizer(:type, :name, :mod, :adapter, :config, :dsl)
12
+ extend Initializer
13
+
14
+ # @!attribute [r] type
15
+ # @return [Symbol] plugin type
16
+ # @api private
17
+ option :type
18
+
19
+ # @!attribute [r] name
20
+ # @return [Symbol] plugin name
21
+ # @api private
22
+ option :name
23
+
24
+ # @!attribute [r] mod
25
+ # @return [Module] a module representing the plugin
26
+ # @api private
27
+ option :mod
28
+
29
+ # @!attribute [r] adapter
30
+ # @return [Symbol] plugin adapter
31
+ # @api private
32
+ option :adapter, optional: true
33
+
34
+ # @!attribute [r] config
35
+ # @return [Symbol] Plugin optional config
36
+ option :config, default: -> { ROM::OpenStruct.new }
37
+
38
+ # @!attribute [r] dsl
39
+ # @return [Module,nil] Optional DSL extensions
40
+ option :dsl, default: -> { mod.const_defined?(:DSL) ? mod.const_get(:DSL) : nil }
41
+
42
+ # These opts are excluded when passing to mod's `apply`
43
+ INTERNAL_OPTS = %i[enabled applied target].freeze
44
+
45
+ # Plugin registry key
46
+ #
47
+ # @return [String]
48
+ #
49
+ # @api private
50
+ def key
51
+ [adapter, type, name].compact.join(".")
52
+ end
53
+
54
+ # Configure plugin
55
+ #
56
+ # @api public
57
+ def configure(**options)
58
+ plugin = dup
59
+ plugin.config.update(options)
60
+ yield(plugin.config) if block_given?
61
+ plugin
62
+ end
63
+
64
+ # @api private
65
+ def dup
66
+ with(config: ROM::OpenStruct.new(config.to_h.clone))
67
+ end
68
+
69
+ # @api private
70
+ def enable(target)
71
+ target.extend(dsl) if dsl
72
+ config.enabled = true
73
+ config.target = target
74
+ self
75
+ end
76
+
77
+ # @api private
78
+ def apply
79
+ if enabled?
80
+ apply_to(config.target, **plugin_options)
81
+ config.applied = true
82
+ config.freeze
83
+ freeze
84
+ self
85
+ else
86
+ raise "Cannot apply a plugin because it was not enabled for any target"
87
+ end
88
+ end
89
+
90
+ # @api private
91
+ def enabled?
92
+ config.key?(:enabled) && config.enabled == true
93
+ end
94
+
95
+ # @api private
96
+ def applied?
97
+ config.key?(:applied) && config.applied == true
98
+ end
99
+
100
+ # @api private
101
+ def plugin_options
102
+ (opts = config.to_h).slice(*(opts.keys - INTERNAL_OPTS))
103
+ end
104
+
105
+ private
106
+
107
+ # Apply this plugin to the target
108
+ #
109
+ # @param [Class,Object] target
110
+ #
111
+ # @api private
112
+ def apply_to(target, **options)
113
+ if mod.respond_to?(:apply)
114
+ mod.apply(target, **config, **options)
115
+ elsif mod.respond_to?(:new)
116
+ target.include(mod.new(**options))
117
+ elsif mod.is_a?(::Module)
118
+ # Target can be either a component class, like a Relation class, or a DSL object
119
+ # If it's the former, just include the module, if it's the latter, assume it defines
120
+ # a component constant and include it there
121
+ if target.is_a?(Class)
122
+ target.include(mod)
123
+ elsif target.respond_to?(:constant)
124
+ target.constant.include(mod)
125
+ end
126
+ end
127
+ self
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Plugins
5
+ # @api public
6
+ module ClassMethods
7
+ # Include a registered plugin in this relation class
8
+ #
9
+ # @param [Symbol] plugin
10
+ # @param [Hash] options
11
+ # @option options [Symbol] :adapter (:default) first adapter to check for plugin
12
+ #
13
+ # @api public
14
+ def use(name, **options)
15
+ plugin = plugins[name].configure(**options).enable(self).apply
16
+ component_config.plugins << plugin
17
+ self
18
+ end
19
+
20
+ # Return all available plugins for the component type
21
+ #
22
+ # @api public
23
+ def plugins
24
+ @plugins ||= ROM.plugins[component_config.type].adapter(component_config.adapter)
25
+ end
26
+
27
+ private
28
+
29
+ # Return component configuration
30
+ #
31
+ # @api private
32
+ def component_config
33
+ @component_config ||= config.key?(:component) ? config.component : config
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Plugins
5
+ module Command
6
+ # Command plugin which sets input processing function via relation schema
7
+ #
8
+ # @api private
9
+ module Schema
10
+ def self.included(klass)
11
+ super
12
+ klass.extend(ClassInterface)
13
+ end
14
+
15
+ # @api private
16
+ module ClassInterface
17
+ # Build a command and set it input to relation's input_schema
18
+ #
19
+ # @see Command.build
20
+ #
21
+ # @return [Command]
22
+ #
23
+ # @api public
24
+ def build(relation, **options)
25
+ if relation.schema? && !options.key?(:input)
26
+ schema = relation.input_schema
27
+ input = config.input
28
+
29
+ composed_input =
30
+ if input.equal?(ROM::Command.config.input)
31
+ schema
32
+ else
33
+ -> tuple { schema[input[tuple]] }
34
+ end
35
+
36
+ super(relation, **options, input: composed_input)
37
+ else
38
+ super
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module ROM
6
+ module Plugins
7
+ module Command
8
+ # A plugin for automatically adding timestamp values
9
+ # when executing a command
10
+ #
11
+ # Set up attributes to timestamp when the command is called
12
+ #
13
+ # @example
14
+ # class CreateTask < ROM::Commands::Create[:sql]
15
+ # result :one
16
+ # use :timestamps, timestamps: %i(created_at, updated_at), datestamps: %i(:written)
17
+ # end
18
+ #
19
+ # create_user = rom.command(:user).create.curry(name: 'Jane')
20
+ #
21
+ # result = create_user.call
22
+ # result[:created_at] #=> Time.now.utc
23
+ #
24
+ # @api public
25
+ class Timestamps < Module
26
+ attr_reader :timestamps, :datestamps
27
+
28
+ def initialize(timestamps: [], datestamps: [])
29
+ @timestamps = store_attributes(timestamps)
30
+ @datestamps = store_attributes(datestamps)
31
+ end
32
+
33
+ # @api private
34
+ def store_attributes(attr)
35
+ attr.is_a?(Array) ? attr : Array[attr]
36
+ end
37
+
38
+ # @api private
39
+ def included(klass)
40
+ initialize_timestamp_attributes(klass)
41
+ klass.include(InstanceMethods)
42
+ klass.extend(ClassInterface)
43
+ super
44
+ end
45
+
46
+ def initialize_timestamp_attributes(klass)
47
+ klass.setting :timestamp_columns, default: Set.new, reader: true
48
+ klass.setting :datestamp_columns, default: Set.new, reader: true
49
+
50
+ klass.before :set_timestamps
51
+
52
+ klass.config.timestamp_columns = klass.timestamp_columns.merge(timestamps) if timestamps.any?
53
+ klass.config.datestamp_columns = klass.datestamp_columns.merge(datestamps) if datestamps.any?
54
+ end
55
+
56
+ module InstanceMethods
57
+ # @api private
58
+ def timestamp_columns
59
+ self.class.timestamp_columns
60
+ end
61
+
62
+ # @api private
63
+ def datestamp_columns
64
+ self.class.datestamp_columns
65
+ end
66
+
67
+ # Set the timestamp attributes on the given tuples
68
+ #
69
+ # @param [Array<Hash>, Hash] tuples the input tuple(s)
70
+ #
71
+ # @return [Array<Hash>, Hash]
72
+ #
73
+ # @api private
74
+ def set_timestamps(tuples, *)
75
+ timestamps = build_timestamps
76
+
77
+ map_input_tuples(tuples) { |t| timestamps.merge(t) }
78
+ end
79
+
80
+ private
81
+
82
+ # @api private
83
+ def build_timestamps
84
+ time = Time.now.utc
85
+ date = Date.today
86
+ timestamps = {}
87
+
88
+ timestamp_columns.each do |column|
89
+ timestamps[column.to_sym] = time
90
+ end
91
+
92
+ datestamp_columns.each do |column|
93
+ timestamps[column.to_sym] = date
94
+ end
95
+
96
+ timestamps
97
+ end
98
+ end
99
+
100
+ module ClassInterface
101
+ # @api private
102
+ # Set up attributes to timestamp when the command is called
103
+ #
104
+ # @example
105
+ # class CreateTask < ROM::Commands::Create[:sql]
106
+ # result :one
107
+ # use :timestamps
108
+ # timestamps :created_at, :updated_at
109
+ # end
110
+ #
111
+ # create_user = rom.command(:user).create.curry(name: 'Jane')
112
+ #
113
+ # result = create_user.call
114
+ # result[:created_at] #=> Time.now.utc
115
+ #
116
+ # @param [Array<Symbol>] names A list of attribute names
117
+ #
118
+ # @api public
119
+ def timestamps(*names)
120
+ config.timestamp_columns = timestamp_columns.merge(names)
121
+ end
122
+ alias_method :timestamp, :timestamps
123
+
124
+ # Set up attributes to datestamp when the command is called
125
+ #
126
+ # @example
127
+ # class CreateTask < ROM::Commands::Create[:sql]
128
+ # result :one
129
+ # use :timestamps
130
+ # datestamps :created_on, :updated_on
131
+ # end
132
+ #
133
+ # create_user = rom.command(:user).create.curry(name: 'Jane')
134
+ #
135
+ # result = create_user.call
136
+ # result[:created_at] #=> Date.today
137
+ #
138
+ # @param [Array<Symbol>] names A list of attribute names
139
+ #
140
+ # @api public
141
+ def datestamps(*names)
142
+ config.datestamp_columns = datestamp_columns.merge(names)
143
+ end
144
+ alias_method :datestamp, :datestamps
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end