rom-repository 1.4.0 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -6
- data/{LICENSE.txt → LICENSE} +1 -1
- data/README.md +18 -1
- data/lib/rom-repository.rb +1 -2
- data/lib/rom/repository.rb +9 -216
- data/lib/rom/repository/class_interface.rb +16 -33
- data/lib/rom/repository/relation_reader.rb +46 -0
- data/lib/rom/repository/root.rb +3 -59
- data/lib/rom/repository/version.rb +1 -1
- metadata +9 -98
- data/.gitignore +0 -3
- data/.rspec +0 -3
- data/.travis.yml +0 -27
- data/.yardopts +0 -2
- data/Gemfile +0 -38
- data/Rakefile +0 -19
- data/lib/rom/open_struct.rb +0 -35
- data/lib/rom/repository/changeset.rb +0 -155
- data/lib/rom/repository/changeset/associated.rb +0 -100
- data/lib/rom/repository/changeset/create.rb +0 -16
- data/lib/rom/repository/changeset/delete.rb +0 -17
- data/lib/rom/repository/changeset/pipe.rb +0 -97
- data/lib/rom/repository/changeset/restricted.rb +0 -28
- data/lib/rom/repository/changeset/stateful.rb +0 -282
- data/lib/rom/repository/changeset/update.rb +0 -82
- data/lib/rom/repository/command_compiler.rb +0 -257
- data/lib/rom/repository/command_proxy.rb +0 -26
- data/lib/rom/repository/header_builder.rb +0 -65
- data/lib/rom/repository/mapper_builder.rb +0 -23
- data/lib/rom/repository/relation_proxy.rb +0 -337
- data/lib/rom/repository/relation_proxy/combine.rb +0 -320
- data/lib/rom/repository/relation_proxy/wrap.rb +0 -78
- data/lib/rom/repository/struct_builder.rb +0 -83
- data/lib/rom/struct.rb +0 -113
- data/log/.gitkeep +0 -0
- data/rom-repository.gemspec +0 -23
- data/spec/integration/changeset_spec.rb +0 -193
- data/spec/integration/command_macros_spec.rb +0 -191
- data/spec/integration/command_spec.rb +0 -228
- data/spec/integration/multi_adapter_spec.rb +0 -73
- data/spec/integration/repository/aggregate_spec.rb +0 -58
- data/spec/integration/repository_spec.rb +0 -406
- data/spec/integration/root_repository_spec.rb +0 -106
- data/spec/integration/typed_structs_spec.rb +0 -64
- data/spec/shared/database.rb +0 -79
- data/spec/shared/mappers.rb +0 -35
- data/spec/shared/models.rb +0 -41
- data/spec/shared/plugins.rb +0 -66
- data/spec/shared/relations.rb +0 -115
- data/spec/shared/repo.rb +0 -86
- data/spec/shared/seeds.rb +0 -30
- data/spec/shared/structs.rb +0 -140
- data/spec/spec_helper.rb +0 -83
- data/spec/support/mapper_registry.rb +0 -9
- data/spec/support/mutant.rb +0 -10
- data/spec/unit/changeset/associate_spec.rb +0 -120
- data/spec/unit/changeset/map_spec.rb +0 -111
- data/spec/unit/changeset_spec.rb +0 -186
- data/spec/unit/relation_proxy_spec.rb +0 -202
- data/spec/unit/repository/changeset_spec.rb +0 -197
- data/spec/unit/repository/inspect_spec.rb +0 -18
- data/spec/unit/repository/session_spec.rb +0 -251
- data/spec/unit/repository/transaction_spec.rb +0 -42
- data/spec/unit/session_spec.rb +0 -46
- data/spec/unit/struct_builder_spec.rb +0 -128
@@ -1,82 +0,0 @@
|
|
1
|
-
require 'rom/repository/changeset/restricted'
|
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 `by_pk` relation view.
|
10
|
-
# This means the underlying adapter must provide this view, or you
|
11
|
-
# you need to implement it yourself in your relations if you want to
|
12
|
-
# use Update changesets.
|
13
|
-
#
|
14
|
-
# @see Changeset::Stateful
|
15
|
-
#
|
16
|
-
# @api public
|
17
|
-
class Update < Stateful
|
18
|
-
include Restricted
|
19
|
-
|
20
|
-
command_type :update
|
21
|
-
|
22
|
-
# Commit update changeset if there's a diff
|
23
|
-
#
|
24
|
-
# This returns original tuple if there's no diff
|
25
|
-
#
|
26
|
-
# @return [Hash]
|
27
|
-
#
|
28
|
-
# @see Changeset#commit
|
29
|
-
#
|
30
|
-
# @api public
|
31
|
-
def commit
|
32
|
-
diff? ? super : original
|
33
|
-
end
|
34
|
-
|
35
|
-
# Return original tuple that this changeset may update
|
36
|
-
#
|
37
|
-
# @return [Hash]
|
38
|
-
#
|
39
|
-
# @api public
|
40
|
-
def original
|
41
|
-
@original ||= Hash(relation.one)
|
42
|
-
end
|
43
|
-
|
44
|
-
# Return true if there's a diff between original and changeset data
|
45
|
-
#
|
46
|
-
# @return [TrueClass, FalseClass]
|
47
|
-
#
|
48
|
-
# @api public
|
49
|
-
def diff?
|
50
|
-
! diff.empty?
|
51
|
-
end
|
52
|
-
|
53
|
-
# Return if there's no diff between the original and changeset data
|
54
|
-
#
|
55
|
-
# @return [TrueClass, FalseClass]
|
56
|
-
#
|
57
|
-
# @api public
|
58
|
-
def clean?
|
59
|
-
diff.empty?
|
60
|
-
end
|
61
|
-
|
62
|
-
# Calculate the diff between the original and changeset data
|
63
|
-
#
|
64
|
-
# @return [Hash]
|
65
|
-
#
|
66
|
-
# @api public
|
67
|
-
def diff
|
68
|
-
@diff ||=
|
69
|
-
begin
|
70
|
-
data = pipe.for_diff(__data__)
|
71
|
-
data_tuple = data.to_a
|
72
|
-
data_keys = data.keys & original.keys
|
73
|
-
|
74
|
-
new_tuple = data_tuple.to_a.select { |(k, _)| data_keys.include?(k) }
|
75
|
-
ori_tuple = original.to_a.select { |(k, _)| data_keys.include?(k) }
|
76
|
-
|
77
|
-
Hash[new_tuple - (new_tuple & ori_tuple)]
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
@@ -1,257 +0,0 @@
|
|
1
|
-
require 'dry/core/inflector'
|
2
|
-
require 'dry/core/cache'
|
3
|
-
|
4
|
-
require 'rom/commands'
|
5
|
-
require 'rom/repository/command_proxy'
|
6
|
-
|
7
|
-
module ROM
|
8
|
-
class Repository
|
9
|
-
# Builds commands for relations.
|
10
|
-
#
|
11
|
-
# This class is used by repositories to automatically create commands for
|
12
|
-
# their relations. This is used both by `Repository#command` method and
|
13
|
-
# `commands` repository class macros.
|
14
|
-
#
|
15
|
-
# @api private
|
16
|
-
class CommandCompiler
|
17
|
-
extend Dry::Core::Cache
|
18
|
-
|
19
|
-
# Return a specific command type for a given adapter and relation AST
|
20
|
-
#
|
21
|
-
# This class holds its own registry where all generated commands are being
|
22
|
-
# stored
|
23
|
-
#
|
24
|
-
# CommandProxy is returned for complex command graphs as they expect root
|
25
|
-
# relation name to be present in the input, which we don't want to have
|
26
|
-
# in repositories. It might be worth looking into removing this requirement
|
27
|
-
# from rom core Command::Graph API.
|
28
|
-
#
|
29
|
-
# @overload [](container, type, adapter, ast, plugins, options)
|
30
|
-
#
|
31
|
-
# @param container [ROM::Container] container where relations are stored
|
32
|
-
# @param type [Symbol] The type of command
|
33
|
-
# @param adapter [Symbol] The adapter identifier
|
34
|
-
# @param ast [Array] The AST representation of a relation
|
35
|
-
# @param plugins [Array<Symbol>] A list of optional command plugins that should be used
|
36
|
-
#
|
37
|
-
# @return [Command, CommandProxy]
|
38
|
-
#
|
39
|
-
# @api private
|
40
|
-
def self.[](*args)
|
41
|
-
fetch_or_store(args.hash) do
|
42
|
-
container, type, adapter, ast, plugins, options = args
|
43
|
-
|
44
|
-
graph_opts = new(type, adapter, container, registry, plugins, options).visit(ast)
|
45
|
-
|
46
|
-
command = ROM::Commands::Graph.build(registry, graph_opts)
|
47
|
-
|
48
|
-
if command.graph?
|
49
|
-
CommandProxy.new(command)
|
50
|
-
elsif command.lazy?
|
51
|
-
command.unwrap
|
52
|
-
else
|
53
|
-
command
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# @api private
|
59
|
-
def self.registry
|
60
|
-
@__registry__ ||= Hash.new { |h, k| h[k] = {} }
|
61
|
-
end
|
62
|
-
|
63
|
-
# @!attribute [r] id
|
64
|
-
# @return [Symbol] The command type registry identifier
|
65
|
-
attr_reader :id
|
66
|
-
|
67
|
-
# @!attribute [r] adapter
|
68
|
-
# @return [Symbol] The adapter identifier ie :sql or :http
|
69
|
-
attr_reader :adapter
|
70
|
-
|
71
|
-
# @!attribute [r] container
|
72
|
-
# @return [ROM::Container] rom container with relations and gateways
|
73
|
-
attr_reader :container
|
74
|
-
|
75
|
-
# @!attribute [r] registry
|
76
|
-
# @return [Hash] local registry where commands will be stored during compilation
|
77
|
-
attr_reader :registry
|
78
|
-
|
79
|
-
# @!attribute [r] plugins
|
80
|
-
# @return [Array<Symbol>] a list of optional plugins that will be enabled for commands
|
81
|
-
attr_reader :plugins
|
82
|
-
|
83
|
-
# @!attribute [r] options
|
84
|
-
# @return [Hash] Additional command options
|
85
|
-
attr_reader :options
|
86
|
-
|
87
|
-
# @api private
|
88
|
-
def initialize(id, adapter, container, registry, plugins, options)
|
89
|
-
@id = id
|
90
|
-
@adapter = adapter
|
91
|
-
@registry = registry
|
92
|
-
@container = container
|
93
|
-
@plugins = Array(plugins)
|
94
|
-
@options = options
|
95
|
-
end
|
96
|
-
|
97
|
-
# @api private
|
98
|
-
def type
|
99
|
-
@_type ||= Commands.const_get(Dry::Core::Inflector.classify(id))[adapter]
|
100
|
-
rescue NameError
|
101
|
-
nil
|
102
|
-
end
|
103
|
-
|
104
|
-
# @api private
|
105
|
-
def visit(ast, *args)
|
106
|
-
name, node = ast
|
107
|
-
__send__(:"visit_#{name}", node, *args)
|
108
|
-
end
|
109
|
-
|
110
|
-
private
|
111
|
-
|
112
|
-
# @api private
|
113
|
-
def visit_relation(node, parent_relation = nil)
|
114
|
-
name, meta, header = node
|
115
|
-
other = visit(header, name)
|
116
|
-
|
117
|
-
if type
|
118
|
-
register_command(name, type, meta, parent_relation)
|
119
|
-
|
120
|
-
default_mapping =
|
121
|
-
if meta[:combine_type] == :many
|
122
|
-
name
|
123
|
-
else meta[:combine_type] == :one
|
124
|
-
{ Dry::Core::Inflector.singularize(name).to_sym => name }
|
125
|
-
end
|
126
|
-
|
127
|
-
mapping =
|
128
|
-
if parent_relation
|
129
|
-
associations = container.relations[parent_relation].associations
|
130
|
-
|
131
|
-
assoc =
|
132
|
-
if associations.key?(meta[:combine_name])
|
133
|
-
associations[meta[:combine_name]]
|
134
|
-
elsif associations.key?(name)
|
135
|
-
associations[name]
|
136
|
-
end
|
137
|
-
|
138
|
-
if assoc
|
139
|
-
{ assoc.target.key => assoc.target.dataset }
|
140
|
-
else
|
141
|
-
default_mapping
|
142
|
-
end
|
143
|
-
else
|
144
|
-
default_mapping
|
145
|
-
end
|
146
|
-
|
147
|
-
if other.size > 0
|
148
|
-
[mapping, [type, other]]
|
149
|
-
else
|
150
|
-
[mapping, type]
|
151
|
-
end
|
152
|
-
else
|
153
|
-
registry[name][id] = container.commands[name][id]
|
154
|
-
[name, id]
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
# @api private
|
159
|
-
def visit_header(node, *args)
|
160
|
-
node.map { |n| visit(n, *args) }.compact
|
161
|
-
end
|
162
|
-
|
163
|
-
# @api private
|
164
|
-
def visit_attribute(*args)
|
165
|
-
nil
|
166
|
-
end
|
167
|
-
|
168
|
-
# Build a command object for a specific relation
|
169
|
-
#
|
170
|
-
# The command will be prepared for handling associations if it's a combined
|
171
|
-
# relation. Additional plugins will be enabled if they are configured for
|
172
|
-
# this compiler.
|
173
|
-
#
|
174
|
-
# @param [Symbol] rel_name A relation identifier from the container registry
|
175
|
-
# @param [Symbol] type The command type
|
176
|
-
# @param [Hash] meta Meta information from relation AST
|
177
|
-
# @param [Symbol] parent_relation Optional parent relation identifier
|
178
|
-
#
|
179
|
-
# @return [ROM::Command]
|
180
|
-
#
|
181
|
-
# @api private
|
182
|
-
def register_command(rel_name, type, meta, parent_relation = nil)
|
183
|
-
relation = container.relations[rel_name]
|
184
|
-
|
185
|
-
type.create_class(rel_name, type) do |klass|
|
186
|
-
klass.result(meta.fetch(:combine_type, result))
|
187
|
-
|
188
|
-
if meta[:combine_type]
|
189
|
-
setup_associates(klass, relation, meta, parent_relation)
|
190
|
-
end
|
191
|
-
|
192
|
-
finalize_command_class(klass, relation)
|
193
|
-
|
194
|
-
registry[rel_name][type] = klass.build(relation, input: relation.input_schema)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
# Return default result type
|
199
|
-
#
|
200
|
-
# @return [Symbol]
|
201
|
-
#
|
202
|
-
# @api private
|
203
|
-
def result
|
204
|
-
options.fetch(:result, :one)
|
205
|
-
end
|
206
|
-
|
207
|
-
# Sets up `associates` plugin for a given command class and relation
|
208
|
-
#
|
209
|
-
# @param [Class] klass The command class
|
210
|
-
# @param [Relation] relation The relation for the command
|
211
|
-
#
|
212
|
-
# @api private
|
213
|
-
def setup_associates(klass, relation, meta, parent_relation)
|
214
|
-
assoc_name =
|
215
|
-
if relation.associations.key?(parent_relation)
|
216
|
-
parent_relation
|
217
|
-
else
|
218
|
-
singular_name = Dry::Core::Inflector.singularize(parent_relation).to_sym
|
219
|
-
singular_name if relation.associations.key?(singular_name)
|
220
|
-
end
|
221
|
-
|
222
|
-
if assoc_name
|
223
|
-
klass.associates(assoc_name)
|
224
|
-
else
|
225
|
-
keys = meta[:keys].invert.to_a.flatten
|
226
|
-
klass.associates(parent_relation, key: keys)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
# Setup a command class for a specific relation
|
231
|
-
#
|
232
|
-
# Every gateway may provide custom command extensions via
|
233
|
-
# `Gateway#extend_command_class`. Furthermore, restrictible commands like
|
234
|
-
# `Update` or `Delete` will be extended with relation view methods, so things
|
235
|
-
# like `delete_user.by_id(1).call` becomes available.
|
236
|
-
#
|
237
|
-
# @param [Class] klass The command class
|
238
|
-
# @param [Relation] relation The command relation
|
239
|
-
#
|
240
|
-
# @return [Class]
|
241
|
-
#
|
242
|
-
# @api private
|
243
|
-
def finalize_command_class(klass, relation)
|
244
|
-
# TODO: this is a copy-paste from rom's FinalizeCommands, we are missing
|
245
|
-
# an interface!
|
246
|
-
gateway = container.gateways[relation.class.gateway]
|
247
|
-
gateway.extend_command_class(klass, relation.dataset)
|
248
|
-
|
249
|
-
klass.extend_for_relation(relation) if klass.restrictable
|
250
|
-
|
251
|
-
plugins.each do |plugin|
|
252
|
-
klass.use(plugin)
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
end
|
257
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
require 'dry/core/inflector'
|
2
|
-
|
3
|
-
module ROM
|
4
|
-
class Repository
|
5
|
-
# TODO: look into making command graphs work without the root key in the input
|
6
|
-
# so that we can get rid of this wrapper
|
7
|
-
#
|
8
|
-
# @api private
|
9
|
-
class CommandProxy
|
10
|
-
attr_reader :command, :root
|
11
|
-
|
12
|
-
def initialize(command)
|
13
|
-
@command = command
|
14
|
-
@root = Dry::Core::Inflector.singularize(command.name.relation).to_sym
|
15
|
-
end
|
16
|
-
|
17
|
-
def call(input)
|
18
|
-
command.call(root => input)
|
19
|
-
end
|
20
|
-
|
21
|
-
def >>(other)
|
22
|
-
self.class.new(command >> other)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,65 +0,0 @@
|
|
1
|
-
require 'rom/header'
|
2
|
-
require 'rom/repository/struct_builder'
|
3
|
-
|
4
|
-
module ROM
|
5
|
-
class Repository
|
6
|
-
# @api private
|
7
|
-
class HeaderBuilder
|
8
|
-
attr_reader :struct_builder
|
9
|
-
|
10
|
-
def initialize(struct_namespace: nil, **options)
|
11
|
-
@struct_builder = StructBuilder.new(struct_namespace)
|
12
|
-
end
|
13
|
-
|
14
|
-
def call(ast)
|
15
|
-
Header.coerce(*visit(ast))
|
16
|
-
end
|
17
|
-
alias_method :[], :call
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def visit(node)
|
22
|
-
name, node = node
|
23
|
-
__send__("visit_#{name}", node)
|
24
|
-
end
|
25
|
-
|
26
|
-
def visit_relation(node)
|
27
|
-
relation_name, meta, header = node
|
28
|
-
name = meta[:combine_name] || relation_name
|
29
|
-
|
30
|
-
model = meta.fetch(:model) do
|
31
|
-
if meta[:combine_name]
|
32
|
-
false
|
33
|
-
else
|
34
|
-
struct_builder[name, header]
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
options = [visit(header), model: model]
|
39
|
-
|
40
|
-
if meta[:combine_type]
|
41
|
-
type = meta[:combine_type] == :many ? :array : :hash
|
42
|
-
keys = meta.fetch(:keys)
|
43
|
-
|
44
|
-
[name, combine: true, type: type, keys: keys, header: Header.coerce(*options)]
|
45
|
-
elsif meta[:wrap]
|
46
|
-
[name, wrap: true, type: :hash, header: Header.coerce(*options)]
|
47
|
-
else
|
48
|
-
options
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def visit_header(node)
|
53
|
-
node.map { |attribute| visit(attribute) }
|
54
|
-
end
|
55
|
-
|
56
|
-
def visit_attribute(attr)
|
57
|
-
if attr.wrapped?
|
58
|
-
[attr.name, from: attr.alias]
|
59
|
-
else
|
60
|
-
[attr.name]
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'dry/core/cache'
|
2
|
-
require 'rom/mapper'
|
3
|
-
require 'rom/repository/header_builder'
|
4
|
-
|
5
|
-
module ROM
|
6
|
-
class Repository
|
7
|
-
# @api private
|
8
|
-
class MapperBuilder
|
9
|
-
extend Dry::Core::Cache
|
10
|
-
|
11
|
-
attr_reader :header_builder
|
12
|
-
|
13
|
-
def initialize(options = EMPTY_HASH)
|
14
|
-
@header_builder = HeaderBuilder.new(options)
|
15
|
-
end
|
16
|
-
|
17
|
-
def call(ast)
|
18
|
-
fetch_or_store(ast) { Mapper.build(header_builder[ast]) }
|
19
|
-
end
|
20
|
-
alias_method :[], :call
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|