rom 5.4.2 → 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 -71
- 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
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rom/relation/graph"
|
4
|
+
|
5
|
+
module ROM
|
6
|
+
class Changeset
|
7
|
+
# Namespace for changeset extensions
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
module Extensions
|
11
|
+
# Changeset extenions for combined relations
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
class Relation::Graph
|
15
|
+
# Build a changeset for a combined relation
|
16
|
+
#
|
17
|
+
# @raise NotImplementedError
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def changeset(*)
|
21
|
+
raise NotImplementedError, "Changeset doesn't support combined relations yet"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rom/changeset/pipe_registry"
|
4
|
+
|
5
|
+
module ROM
|
6
|
+
class Changeset
|
7
|
+
# Composable data transformation pipe used by default in changesets
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Pipe < Dry::Transformer[PipeRegistry]
|
11
|
+
extend Initializer
|
12
|
+
|
13
|
+
define!(&:identity)
|
14
|
+
|
15
|
+
param :processor, optional: true
|
16
|
+
|
17
|
+
option :use_for_diff, optional: true, default: -> { true }
|
18
|
+
option :diff_processor, default: -> { self[processor] }
|
19
|
+
|
20
|
+
def self.new(*args, **opts)
|
21
|
+
if args.empty?
|
22
|
+
initialize(**opts)
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.initialize(**opts)
|
29
|
+
transformer = allocate
|
30
|
+
transformer.__send__(:initialize, dsl.(transformer), **opts)
|
31
|
+
transformer
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.[](name_or_proc)
|
35
|
+
container[name_or_proc]
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](*args)
|
39
|
+
self.class[*args]
|
40
|
+
end
|
41
|
+
|
42
|
+
def bind(context)
|
43
|
+
if processor.is_a?(Proc)
|
44
|
+
bound_processor = self[-> *args { context.instance_exec(*args, &processor) }]
|
45
|
+
bound_diff_processor = self[-> *args { context.instance_exec(*args, &diff_processor) }]
|
46
|
+
|
47
|
+
new(bound_processor, diff_processor: bound_diff_processor)
|
48
|
+
else
|
49
|
+
self
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def compose(other, for_diff: other.is_a?(self.class) ? other.use_for_diff : false)
|
54
|
+
new_proc = processor >> other
|
55
|
+
|
56
|
+
if for_diff
|
57
|
+
diff_proc = diff_processor >> (
|
58
|
+
other.is_a?(self.class) ? other.diff_processor : other
|
59
|
+
)
|
60
|
+
|
61
|
+
new(new_proc, use_for_diff: true, diff_processor: diff_proc)
|
62
|
+
else
|
63
|
+
new(new_proc)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
alias_method :>>, :compose
|
67
|
+
|
68
|
+
def call(data)
|
69
|
+
processor.call(data)
|
70
|
+
end
|
71
|
+
|
72
|
+
def for_diff(data)
|
73
|
+
use_for_diff ? diff_processor.call(data) : data
|
74
|
+
end
|
75
|
+
|
76
|
+
def new(processor, **opts)
|
77
|
+
self.class.new(processor, **options, **opts)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/transformer/all"
|
4
|
+
require "dry/transformer/registry"
|
5
|
+
|
6
|
+
module ROM
|
7
|
+
class Changeset
|
8
|
+
# Dry::Transformer Registry useful for pipe
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
module PipeRegistry
|
12
|
+
extend Dry::Transformer::Registry
|
13
|
+
|
14
|
+
import Dry::Transformer::Coercions
|
15
|
+
import Dry::Transformer::HashTransformations
|
16
|
+
|
17
|
+
def self.add_timestamps(data)
|
18
|
+
now = Time.now
|
19
|
+
Hash(created_at: now, updated_at: now).merge(data)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.touch(data)
|
23
|
+
Hash(updated_at: Time.now).merge(data)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,285 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rom/changeset/pipe"
|
4
|
+
|
5
|
+
module ROM
|
6
|
+
class Changeset
|
7
|
+
# Stateful changesets carry data and can transform it into
|
8
|
+
# a different structure compatible with a persistence backend
|
9
|
+
#
|
10
|
+
# @abstract
|
11
|
+
class Stateful < Changeset
|
12
|
+
# Default no-op pipe
|
13
|
+
EMPTY_PIPE = Pipe.new(use_for_diff: false).freeze
|
14
|
+
|
15
|
+
# @!attribute [r] __data__
|
16
|
+
# @return [Hash] The relation data
|
17
|
+
# @api private
|
18
|
+
option :__data__, optional: true
|
19
|
+
|
20
|
+
# @!attribute [r] pipe
|
21
|
+
# @return [Changeset::Pipe] data transformation pipe
|
22
|
+
# @api private
|
23
|
+
option :pipe, reader: false, optional: true
|
24
|
+
|
25
|
+
# Define a changeset mapping
|
26
|
+
#
|
27
|
+
# Subsequent mapping definitions will be composed together
|
28
|
+
# and applied in the order they way defined
|
29
|
+
#
|
30
|
+
# @example Transformation DSL
|
31
|
+
# class NewUser < ROM::Changeset::Create
|
32
|
+
# map do
|
33
|
+
# unwrap :address, prefix: true
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# @example Using custom block
|
38
|
+
# class NewUser < ROM::Changeset::Create
|
39
|
+
# map do |tuple|
|
40
|
+
# tuple.merge(created_at: Time.now)
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @example Multiple mappings (executed in the order of definition)
|
45
|
+
# class NewUser < ROM::Changeset::Create
|
46
|
+
# map do
|
47
|
+
# unwrap :address, prefix: true
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# map do |tuple|
|
51
|
+
# tuple.merge(created_at: Time.now)
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# @return [Array<Pipe>, Dry::Transformer::Function>]
|
56
|
+
#
|
57
|
+
# @see https://github.com/dry-rb/dry-transformer
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
def self.map(**options, &block)
|
61
|
+
if block.parameters.empty?
|
62
|
+
pipes << Class.new(Pipe).define!(&block).new(**options)
|
63
|
+
else
|
64
|
+
pipes << Pipe.new(block, **options)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Define a changeset mapping excluded from diffs
|
69
|
+
#
|
70
|
+
# @see Changeset::Stateful.map
|
71
|
+
# @see Changeset::Stateful#extend
|
72
|
+
#
|
73
|
+
# @return [Array<Pipe>, Dry::Transformer::Function>]
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def self.extend(*, &block)
|
77
|
+
if block
|
78
|
+
map(use_for_diff: false, &block)
|
79
|
+
else
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Build default pipe object
|
85
|
+
#
|
86
|
+
# This can be overridden in a custom changeset subclass
|
87
|
+
#
|
88
|
+
# @return [Pipe]
|
89
|
+
def self.default_pipe(context)
|
90
|
+
pipes.empty? ? EMPTY_PIPE : pipes.map { |p| p.bind(context) }.reduce(:>>)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @api private
|
94
|
+
def self.inherited(klass)
|
95
|
+
return if klass == ROM::Changeset
|
96
|
+
|
97
|
+
super
|
98
|
+
klass.instance_variable_set(:@__pipes__, pipes.dup)
|
99
|
+
end
|
100
|
+
|
101
|
+
# @api private
|
102
|
+
def self.pipes
|
103
|
+
@__pipes__
|
104
|
+
end
|
105
|
+
@__pipes__ = EMPTY_ARRAY
|
106
|
+
|
107
|
+
# Pipe changeset's data using custom steps define on the pipe
|
108
|
+
#
|
109
|
+
# @overload map(*steps)
|
110
|
+
# Apply mapping using built-in transformations
|
111
|
+
#
|
112
|
+
# @example
|
113
|
+
# changeset.map(:add_timestamps)
|
114
|
+
#
|
115
|
+
# @param [Array<Symbol>] steps A list of mapping steps
|
116
|
+
#
|
117
|
+
# @overload map(&block)
|
118
|
+
# Apply mapping using a custom block
|
119
|
+
#
|
120
|
+
# @example
|
121
|
+
# changeset.map { |tuple| tuple.merge(created_at: Time.now) }
|
122
|
+
#
|
123
|
+
# @overload map(*steps, &block)
|
124
|
+
# Apply mapping using built-in transformations and a custom block
|
125
|
+
#
|
126
|
+
# @example
|
127
|
+
# changeset.map(:add_timestamps) { |tuple| tuple.merge(status: 'published') }
|
128
|
+
#
|
129
|
+
# @param [Array<Symbol>] steps A list of mapping steps
|
130
|
+
#
|
131
|
+
# @return [Changeset]
|
132
|
+
#
|
133
|
+
# @api public
|
134
|
+
def map(*steps, &block)
|
135
|
+
extend(*steps, for_diff: true, &block)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Pipe changeset's data using custom steps define on the pipe.
|
139
|
+
# You should use #map instead except updating timestamp fields.
|
140
|
+
# Calling changeset.extend builds a pipe that excludes certain
|
141
|
+
# steps for generating the diff. Currently the only place where
|
142
|
+
# it is used is update changesets with the `:touch` step, i.e.
|
143
|
+
# `changeset.extend(:touch).diff` will exclude `:updated_at`
|
144
|
+
# from the diff.
|
145
|
+
#
|
146
|
+
# @see Changeset::Stateful#map
|
147
|
+
#
|
148
|
+
# @return [Changeset]
|
149
|
+
#
|
150
|
+
# @api public
|
151
|
+
def extend(*steps, **options, &block)
|
152
|
+
if block
|
153
|
+
if steps.empty?
|
154
|
+
with(pipe: pipe.compose(Pipe.new(block).bind(self), **options))
|
155
|
+
else
|
156
|
+
extend(*steps, **options).extend(**options, &block)
|
157
|
+
end
|
158
|
+
else
|
159
|
+
with(pipe: steps.reduce(pipe.with(**options)) { |a, e| a.compose(pipe[e], **options) })
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Return changeset with data
|
164
|
+
#
|
165
|
+
# @param [Hash] data
|
166
|
+
#
|
167
|
+
# @return [Changeset]
|
168
|
+
#
|
169
|
+
# @api public
|
170
|
+
def data(data)
|
171
|
+
with(__data__: data)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Coerce changeset to a hash
|
175
|
+
#
|
176
|
+
# This will send the data through the pipe
|
177
|
+
#
|
178
|
+
# @return [Hash]
|
179
|
+
#
|
180
|
+
# @api public
|
181
|
+
def to_h
|
182
|
+
pipe.call(__data__)
|
183
|
+
end
|
184
|
+
alias_method :to_hash, :to_h
|
185
|
+
|
186
|
+
# Coerce changeset to an array
|
187
|
+
#
|
188
|
+
# This will send the data through the pipe
|
189
|
+
#
|
190
|
+
# @return [Array]
|
191
|
+
#
|
192
|
+
# @api public
|
193
|
+
def to_a
|
194
|
+
result == :one ? [to_h] : __data__.map { |element| pipe.call(element) }
|
195
|
+
end
|
196
|
+
alias_method :to_ary, :to_a
|
197
|
+
|
198
|
+
# Commit stateful changeset
|
199
|
+
#
|
200
|
+
# @see Changeset#commit
|
201
|
+
#
|
202
|
+
# @api public
|
203
|
+
def commit
|
204
|
+
command.call(self)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Associate a changeset with another changeset or hash-like object
|
208
|
+
#
|
209
|
+
# @example with another changeset
|
210
|
+
# new_user = users.changeset(name: 'Jane')
|
211
|
+
# new_task = users.changeset(:tasks, title: 'A task')
|
212
|
+
#
|
213
|
+
# new_task.associate(new_user, :users)
|
214
|
+
#
|
215
|
+
# @example with a hash-like object
|
216
|
+
# user = users.users.by_pk(1).one
|
217
|
+
# new_task = users.changeset(:tasks, title: 'A task')
|
218
|
+
#
|
219
|
+
# new_task.associate(user, :users)
|
220
|
+
#
|
221
|
+
# @param [#to_hash, Changeset] other Other changeset or hash-like object
|
222
|
+
# @param [Symbol] name The association identifier from schema
|
223
|
+
#
|
224
|
+
# @api public
|
225
|
+
def associate(other, name = Associated.infer_assoc_name(other))
|
226
|
+
Associated.new(self, associations: {name => other})
|
227
|
+
end
|
228
|
+
|
229
|
+
# Return command result type
|
230
|
+
#
|
231
|
+
# @return [Symbol]
|
232
|
+
#
|
233
|
+
# @api private
|
234
|
+
def result
|
235
|
+
__data__.is_a?(Array) ? :many : :one
|
236
|
+
end
|
237
|
+
|
238
|
+
# Return string representation of the changeset
|
239
|
+
#
|
240
|
+
# @return [String]
|
241
|
+
#
|
242
|
+
# @api public
|
243
|
+
def inspect
|
244
|
+
%(#<#{self.class} relation=#{relation.name.inspect} data=#{__data__}>)
|
245
|
+
end
|
246
|
+
|
247
|
+
# @api private
|
248
|
+
def command_compiler_options
|
249
|
+
super.merge(result: result)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Data transformation pipe
|
253
|
+
#
|
254
|
+
# @return [Changeset::Pipe]
|
255
|
+
#
|
256
|
+
# @api private
|
257
|
+
def pipe
|
258
|
+
@pipe ||= self.class.default_pipe(self)
|
259
|
+
end
|
260
|
+
|
261
|
+
private
|
262
|
+
|
263
|
+
# @api private
|
264
|
+
def respond_to_missing?(meth, include_private = false)
|
265
|
+
super || __data__.respond_to?(meth)
|
266
|
+
end
|
267
|
+
|
268
|
+
# @api private
|
269
|
+
def method_missing(meth, *args, &block)
|
270
|
+
if __data__.respond_to?(meth)
|
271
|
+
response = __data__.__send__(meth, *args, &block)
|
272
|
+
|
273
|
+
if response.is_a?(__data__.class)
|
274
|
+
with(__data__: response)
|
275
|
+
else
|
276
|
+
response
|
277
|
+
end
|
278
|
+
else
|
279
|
+
super
|
280
|
+
end
|
281
|
+
end
|
282
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Changeset
|
5
|
+
# Changeset specialization for update commands
|
6
|
+
#
|
7
|
+
# Update changesets will only execute their commands when
|
8
|
+
# the data is different from the original tuple. Original tuple
|
9
|
+
# is fetched from changeset's relation using `one` method.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# users.by_pk(1).changeset(:update, name: "Jane Doe").commit
|
13
|
+
#
|
14
|
+
# @see Changeset::Stateful
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
class Update < Stateful
|
18
|
+
command_type :update
|
19
|
+
|
20
|
+
# Commit update changeset if there's a diff
|
21
|
+
#
|
22
|
+
# This returns original tuple if there's no diff
|
23
|
+
#
|
24
|
+
# @return [Hash]
|
25
|
+
#
|
26
|
+
# @see Changeset#commit
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def commit
|
30
|
+
diff? ? super : original
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return original tuple that this changeset may update
|
34
|
+
#
|
35
|
+
# @return [Hash]
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def original
|
39
|
+
@original ||= relation.one
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return true if there's a diff between original and changeset data
|
43
|
+
#
|
44
|
+
# @return [TrueClass, FalseClass]
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
def diff?
|
48
|
+
!diff.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return if there's no diff between the original and changeset data
|
52
|
+
#
|
53
|
+
# @return [TrueClass, FalseClass]
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def clean?
|
57
|
+
diff.empty?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Calculate the diff between the original and changeset data
|
61
|
+
#
|
62
|
+
# @return [Hash]
|
63
|
+
#
|
64
|
+
# @api public
|
65
|
+
def diff
|
66
|
+
@diff ||=
|
67
|
+
begin
|
68
|
+
source = original.to_h
|
69
|
+
data = pipe.for_diff(__data__)
|
70
|
+
data_tuple = data.to_a
|
71
|
+
data_keys = data.keys & source.keys
|
72
|
+
|
73
|
+
new_tuple = data_tuple.to_a.select { |k, _| data_keys.include?(k) }
|
74
|
+
ori_tuple = source.to_a.select { |k, _| data_keys.include?(k) }
|
75
|
+
|
76
|
+
(new_tuple - (new_tuple & ori_tuple)).to_h
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/class_attributes"
|
4
|
+
require "dry/core/cache"
|
5
|
+
|
6
|
+
require "rom/constants"
|
7
|
+
require "rom/initializer"
|
8
|
+
|
9
|
+
module ROM
|
10
|
+
# Abstract Changeset class
|
11
|
+
#
|
12
|
+
# If you inherit from this class you need to configure additional settings
|
13
|
+
#
|
14
|
+
# @example define a custom changeset using :upsert command
|
15
|
+
# class NewTag < ROM::Changeset[:tags]
|
16
|
+
# command_type :upsert
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# @abstract
|
20
|
+
class Changeset
|
21
|
+
extend Initializer
|
22
|
+
extend Dry::Core::Cache
|
23
|
+
extend Dry::Core::ClassAttributes
|
24
|
+
|
25
|
+
# @!method self.command_type
|
26
|
+
# Get or set changeset command type
|
27
|
+
#
|
28
|
+
# @overload command_type
|
29
|
+
# Return configured command_type
|
30
|
+
# @return [Symbol]
|
31
|
+
#
|
32
|
+
# @overload command_type(identifier)
|
33
|
+
# Set relation identifier for this changeset
|
34
|
+
# @param [Symbol] identifier The command type identifier
|
35
|
+
# @return [Symbol]
|
36
|
+
defines :command_type
|
37
|
+
|
38
|
+
# @!method self.command_options
|
39
|
+
# Get or set command options
|
40
|
+
#
|
41
|
+
# @overload command_options
|
42
|
+
# Return configured command_options
|
43
|
+
# @return [Hash,nil]
|
44
|
+
#
|
45
|
+
# @overload command_options(**options)
|
46
|
+
# Set command options
|
47
|
+
# @param options [Hash] The command options
|
48
|
+
# @return [Hash]
|
49
|
+
defines :command_options
|
50
|
+
|
51
|
+
# @!method self.command_plugins
|
52
|
+
# Get or set command plugins options
|
53
|
+
#
|
54
|
+
# @overload command_plugins
|
55
|
+
# Return configured command_plugins
|
56
|
+
# @return [Hash,nil]
|
57
|
+
#
|
58
|
+
# @overload command_plugins(**options)
|
59
|
+
# Set command plugin options
|
60
|
+
# @param options [Hash] The command plugin options
|
61
|
+
# @return [Hash]
|
62
|
+
defines :command_plugins
|
63
|
+
|
64
|
+
# @!method self.relation
|
65
|
+
# Get or set changeset relation identifier
|
66
|
+
#
|
67
|
+
# @overload relation
|
68
|
+
# Return configured relation identifier for this changeset
|
69
|
+
# @return [Symbol]
|
70
|
+
#
|
71
|
+
# @overload relation(identifier)
|
72
|
+
# Set relation identifier for this changeset
|
73
|
+
# @param [Symbol] identifier The relation identifier from the ROM container
|
74
|
+
# @return [Symbol]
|
75
|
+
defines :relation
|
76
|
+
|
77
|
+
# @!attribute [r] relation
|
78
|
+
# @return [Relation] The changeset relation
|
79
|
+
param :relation
|
80
|
+
|
81
|
+
# @!attribute [r] command_type
|
82
|
+
# @return [Symbol] a custom command identifier
|
83
|
+
option :command_type, default: -> { self.class.command_type }
|
84
|
+
|
85
|
+
# @!attribute [r] command_options
|
86
|
+
# @return [Hash] Configured options for the command
|
87
|
+
option :command_options, default: -> { self.class.command_options }
|
88
|
+
|
89
|
+
# @!attribute [r] command_plugins
|
90
|
+
# @return [Hash] Configured plugin options for the command
|
91
|
+
option :command_plugins, default: -> { self.class.command_plugins }
|
92
|
+
|
93
|
+
# Set the default command options
|
94
|
+
command_options(mapper: false)
|
95
|
+
|
96
|
+
# Set the default command plugin options
|
97
|
+
command_plugins(EMPTY_HASH)
|
98
|
+
|
99
|
+
# Create a changeset class preconfigured for a specific relation
|
100
|
+
#
|
101
|
+
# @example
|
102
|
+
# class NewUserChangeset < ROM::Changeset::Create[:users]
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# users.changeset(NewUserChangeset).data(name: 'Jane')
|
106
|
+
#
|
107
|
+
# @api public
|
108
|
+
def self.[](relation_name)
|
109
|
+
fetch_or_store([relation_name, self]) {
|
110
|
+
Class.new(self) { relation(relation_name) }
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
# Enable a plugin for the changeset
|
115
|
+
#
|
116
|
+
# @api public
|
117
|
+
def self.use(plugin, **options)
|
118
|
+
ROM.plugins[:changeset].fetch(plugin).enable(self).apply(**options)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Return a new changeset with provided relation
|
122
|
+
#
|
123
|
+
# New options can be provided too
|
124
|
+
#
|
125
|
+
# @param [Relation] relation
|
126
|
+
# @param [Hash] new_options
|
127
|
+
#
|
128
|
+
# @return [Changeset]
|
129
|
+
#
|
130
|
+
# @api public
|
131
|
+
def new(relation, **new_options)
|
132
|
+
self.class.new(relation, **options, **new_options)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Persist changeset
|
136
|
+
#
|
137
|
+
# @example
|
138
|
+
# changeset = users.changeset(name: 'Jane')
|
139
|
+
# changeset.commit
|
140
|
+
# # => { id: 1, name: 'Jane' }
|
141
|
+
#
|
142
|
+
# @return [Hash, Array]
|
143
|
+
#
|
144
|
+
# @api public
|
145
|
+
def commit
|
146
|
+
command.call
|
147
|
+
end
|
148
|
+
|
149
|
+
# Return string representation of the changeset
|
150
|
+
#
|
151
|
+
# @return [String]
|
152
|
+
#
|
153
|
+
# @api public
|
154
|
+
def inspect
|
155
|
+
%(#<#{self.class} relation=#{relation.name.inspect}>)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Return a command for this changeset
|
159
|
+
#
|
160
|
+
# @return [ROM::Command]
|
161
|
+
#
|
162
|
+
# @api private
|
163
|
+
def command
|
164
|
+
relation.command(command_type, **command_compiler_options)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Return configured command compiler options
|
168
|
+
#
|
169
|
+
# @return [Hash]
|
170
|
+
#
|
171
|
+
# @api private
|
172
|
+
def command_compiler_options
|
173
|
+
command_options.merge(use: command_plugins.keys, plugins_options: command_plugins)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
require "rom/changeset/stateful"
|
179
|
+
require "rom/changeset/associated"
|
180
|
+
|
181
|
+
require "rom/changeset/create"
|
182
|
+
require "rom/changeset/update"
|
183
|
+
require "rom/changeset/delete"
|
184
|
+
|
185
|
+
require "rom/plugins"
|