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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -65
- data/LICENSE +1 -1
- data/README.md +7 -6
- data/lib/rom/array_dataset.rb +46 -0
- data/lib/rom/associations/abstract.rb +217 -0
- data/lib/rom/associations/definitions/abstract.rb +150 -0
- data/lib/rom/associations/definitions/many_to_many.rb +29 -0
- data/lib/rom/associations/definitions/many_to_one.rb +14 -0
- data/lib/rom/associations/definitions/one_to_many.rb +14 -0
- data/lib/rom/associations/definitions/one_to_one.rb +14 -0
- data/lib/rom/associations/definitions/one_to_one_through.rb +14 -0
- data/lib/rom/associations/definitions.rb +7 -0
- data/lib/rom/associations/many_to_many.rb +128 -0
- data/lib/rom/associations/many_to_one.rb +65 -0
- data/lib/rom/associations/one_to_many.rb +65 -0
- data/lib/rom/associations/one_to_one.rb +13 -0
- data/lib/rom/associations/one_to_one_through.rb +13 -0
- data/lib/rom/associations/through_identifier.rb +41 -0
- data/lib/rom/attribute.rb +425 -0
- data/lib/rom/auto_curry.rb +70 -0
- data/lib/rom/cache.rb +87 -0
- data/lib/rom/changeset/associated.rb +110 -0
- data/lib/rom/changeset/create.rb +18 -0
- data/lib/rom/changeset/delete.rb +15 -0
- data/lib/rom/changeset/extensions/relation.rb +26 -0
- data/lib/rom/changeset/pipe.rb +81 -0
- data/lib/rom/changeset/pipe_registry.rb +27 -0
- data/lib/rom/changeset/stateful.rb +285 -0
- data/lib/rom/changeset/update.rb +81 -0
- data/lib/rom/changeset.rb +185 -0
- data/lib/rom/command.rb +351 -0
- data/lib/rom/command_compiler.rb +201 -0
- data/lib/rom/command_proxy.rb +36 -0
- data/lib/rom/commands/class_interface.rb +236 -0
- data/lib/rom/commands/composite.rb +55 -0
- data/lib/rom/commands/create.rb +15 -0
- data/lib/rom/commands/delete.rb +16 -0
- data/lib/rom/commands/graph/class_interface.rb +64 -0
- data/lib/rom/commands/graph/input_evaluator.rb +94 -0
- data/lib/rom/commands/graph.rb +88 -0
- data/lib/rom/commands/lazy/create.rb +35 -0
- data/lib/rom/commands/lazy/delete.rb +39 -0
- data/lib/rom/commands/lazy/update.rb +46 -0
- data/lib/rom/commands/lazy.rb +106 -0
- data/lib/rom/commands/update.rb +16 -0
- data/lib/rom/commands.rb +5 -0
- data/lib/rom/compat/auto_registration.rb +115 -0
- data/lib/rom/compat/auto_registration_strategies/base.rb +29 -0
- data/lib/rom/compat/auto_registration_strategies/custom_namespace.rb +84 -0
- data/lib/rom/compat/auto_registration_strategies/no_namespace.rb +33 -0
- data/lib/rom/compat/auto_registration_strategies/with_namespace.rb +29 -0
- data/lib/rom/compat/command.rb +74 -0
- data/lib/rom/compat/components/dsl/schema.rb +130 -0
- data/lib/rom/compat/components.rb +91 -0
- data/lib/rom/compat/global.rb +17 -0
- data/lib/rom/compat/mapper.rb +22 -0
- data/lib/rom/compat/registries.rb +47 -0
- data/lib/rom/compat/relation.rb +40 -0
- data/lib/rom/compat/schema/dsl.rb +260 -0
- data/lib/rom/compat/setting_proxy.rb +44 -0
- data/lib/rom/compat/setup.rb +151 -0
- data/lib/rom/compat/transformer.rb +49 -0
- data/lib/rom/compat.rb +22 -0
- data/lib/rom/components/association.rb +26 -0
- data/lib/rom/components/command.rb +24 -0
- data/lib/rom/components/core.rb +148 -0
- data/lib/rom/components/dataset.rb +60 -0
- data/lib/rom/components/dsl/association.rb +47 -0
- data/lib/rom/components/dsl/command.rb +60 -0
- data/lib/rom/components/dsl/core.rb +126 -0
- data/lib/rom/components/dsl/dataset.rb +33 -0
- data/lib/rom/components/dsl/gateway.rb +14 -0
- data/lib/rom/components/dsl/mapper.rb +70 -0
- data/lib/rom/components/dsl/relation.rb +49 -0
- data/lib/rom/components/dsl/schema.rb +150 -0
- data/lib/rom/components/dsl/view.rb +82 -0
- data/lib/rom/components/dsl.rb +255 -0
- data/lib/rom/components/gateway.rb +50 -0
- data/lib/rom/components/mapper.rb +29 -0
- data/lib/rom/components/provider.rb +160 -0
- data/lib/rom/components/registry.rb +154 -0
- data/lib/rom/components/relation.rb +41 -0
- data/lib/rom/components/schema.rb +61 -0
- data/lib/rom/components/view.rb +55 -0
- data/lib/rom/components.rb +55 -0
- data/lib/rom/configuration_dsl.rb +4 -0
- data/lib/rom/constants.rb +135 -0
- data/lib/rom/container.rb +182 -0
- data/lib/rom/core.rb +125 -0
- data/lib/rom/data_proxy.rb +97 -0
- data/lib/rom/enumerable_dataset.rb +70 -0
- data/lib/rom/gateway.rb +232 -0
- data/lib/rom/global.rb +56 -0
- data/lib/rom/header/attribute.rb +190 -0
- data/lib/rom/header.rb +198 -0
- data/lib/rom/inferrer.rb +55 -0
- data/lib/rom/initializer.rb +80 -0
- data/lib/rom/lint/enumerable_dataset.rb +56 -0
- data/lib/rom/lint/gateway.rb +120 -0
- data/lib/rom/lint/linter.rb +79 -0
- data/lib/rom/lint/spec.rb +22 -0
- data/lib/rom/lint/test.rb +98 -0
- data/lib/rom/loader.rb +161 -0
- data/lib/rom/mapper/attribute_dsl.rb +480 -0
- data/lib/rom/mapper/dsl.rb +107 -0
- data/lib/rom/mapper/model_dsl.rb +61 -0
- data/lib/rom/mapper.rb +99 -0
- data/lib/rom/mapper_compiler.rb +84 -0
- data/lib/rom/memory/associations/many_to_many.rb +12 -0
- data/lib/rom/memory/associations/many_to_one.rb +12 -0
- data/lib/rom/memory/associations/one_to_many.rb +12 -0
- data/lib/rom/memory/associations/one_to_one.rb +12 -0
- data/lib/rom/memory/associations.rb +6 -0
- data/lib/rom/memory/commands.rb +60 -0
- data/lib/rom/memory/dataset.rb +127 -0
- data/lib/rom/memory/gateway.rb +66 -0
- data/lib/rom/memory/mapper_compiler.rb +10 -0
- data/lib/rom/memory/relation.rb +91 -0
- data/lib/rom/memory/schema.rb +32 -0
- data/lib/rom/memory/storage.rb +61 -0
- data/lib/rom/memory/types.rb +11 -0
- data/lib/rom/memory.rb +7 -0
- data/lib/rom/model_builder.rb +103 -0
- data/lib/rom/open_struct.rb +112 -0
- data/lib/rom/pipeline.rb +111 -0
- data/lib/rom/plugin.rb +130 -0
- data/lib/rom/plugins/class_methods.rb +37 -0
- data/lib/rom/plugins/command/schema.rb +45 -0
- data/lib/rom/plugins/command/timestamps.rb +149 -0
- data/lib/rom/plugins/dsl.rb +53 -0
- data/lib/rom/plugins/relation/changeset.rb +97 -0
- data/lib/rom/plugins/relation/instrumentation.rb +66 -0
- data/lib/rom/plugins/relation/registry_reader.rb +36 -0
- data/lib/rom/plugins/schema/timestamps.rb +59 -0
- data/lib/rom/plugins.rb +100 -0
- data/lib/rom/processor/composer.rb +37 -0
- data/lib/rom/processor/transformer.rb +415 -0
- data/lib/rom/processor.rb +30 -0
- data/lib/rom/registries/associations.rb +26 -0
- data/lib/rom/registries/commands.rb +11 -0
- data/lib/rom/registries/container.rb +12 -0
- data/lib/rom/registries/datasets.rb +21 -0
- data/lib/rom/registries/gateways.rb +8 -0
- data/lib/rom/registries/mappers.rb +21 -0
- data/lib/rom/registries/nestable.rb +32 -0
- data/lib/rom/registries/relations.rb +8 -0
- data/lib/rom/registries/root.rb +203 -0
- data/lib/rom/registries/schemas.rb +44 -0
- data/lib/rom/registries/views.rb +11 -0
- data/lib/rom/relation/class_interface.rb +61 -0
- data/lib/rom/relation/combined.rb +160 -0
- data/lib/rom/relation/commands.rb +65 -0
- data/lib/rom/relation/composite.rb +53 -0
- data/lib/rom/relation/curried.rb +129 -0
- data/lib/rom/relation/graph.rb +107 -0
- data/lib/rom/relation/loaded.rb +136 -0
- data/lib/rom/relation/materializable.rb +62 -0
- data/lib/rom/relation/name.rb +122 -0
- data/lib/rom/relation/wrap.rb +64 -0
- data/lib/rom/relation.rb +625 -0
- data/lib/rom/repository/class_interface.rb +162 -0
- data/lib/rom/repository/relation_reader.rb +48 -0
- data/lib/rom/repository/root.rb +75 -0
- data/lib/rom/repository/session.rb +60 -0
- data/lib/rom/repository.rb +179 -0
- data/lib/rom/schema/associations_dsl.rb +222 -0
- data/lib/rom/schema/inferrer.rb +106 -0
- data/lib/rom/schema.rb +471 -0
- data/lib/rom/settings.rb +141 -0
- data/lib/rom/setup.rb +297 -0
- data/lib/rom/struct.rb +99 -0
- data/lib/rom/struct_compiler.rb +114 -0
- data/lib/rom/support/configurable.rb +213 -0
- data/lib/rom/support/inflector.rb +31 -0
- data/lib/rom/support/memoizable.rb +61 -0
- data/lib/rom/support/notifications.rb +238 -0
- data/lib/rom/transaction.rb +26 -0
- data/lib/rom/transformer.rb +46 -0
- data/lib/rom/types.rb +74 -0
- data/lib/rom/version.rb +1 -1
- data/lib/rom-changeset.rb +4 -0
- data/lib/rom-core.rb +3 -0
- data/lib/rom-repository.rb +4 -0
- data/lib/rom.rb +3 -3
- metadata +302 -23
data/lib/rom/memory.rb
ADDED
@@ -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
|
data/lib/rom/pipeline.rb
ADDED
@@ -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
|