rom-repository 1.4.0 → 2.0.0.beta1

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -6
  3. data/{LICENSE.txt → LICENSE} +1 -1
  4. data/README.md +18 -1
  5. data/lib/rom-repository.rb +1 -2
  6. data/lib/rom/repository.rb +9 -216
  7. data/lib/rom/repository/class_interface.rb +16 -33
  8. data/lib/rom/repository/relation_reader.rb +46 -0
  9. data/lib/rom/repository/root.rb +3 -59
  10. data/lib/rom/repository/version.rb +1 -1
  11. metadata +9 -98
  12. data/.gitignore +0 -3
  13. data/.rspec +0 -3
  14. data/.travis.yml +0 -27
  15. data/.yardopts +0 -2
  16. data/Gemfile +0 -38
  17. data/Rakefile +0 -19
  18. data/lib/rom/open_struct.rb +0 -35
  19. data/lib/rom/repository/changeset.rb +0 -155
  20. data/lib/rom/repository/changeset/associated.rb +0 -100
  21. data/lib/rom/repository/changeset/create.rb +0 -16
  22. data/lib/rom/repository/changeset/delete.rb +0 -17
  23. data/lib/rom/repository/changeset/pipe.rb +0 -97
  24. data/lib/rom/repository/changeset/restricted.rb +0 -28
  25. data/lib/rom/repository/changeset/stateful.rb +0 -282
  26. data/lib/rom/repository/changeset/update.rb +0 -82
  27. data/lib/rom/repository/command_compiler.rb +0 -257
  28. data/lib/rom/repository/command_proxy.rb +0 -26
  29. data/lib/rom/repository/header_builder.rb +0 -65
  30. data/lib/rom/repository/mapper_builder.rb +0 -23
  31. data/lib/rom/repository/relation_proxy.rb +0 -337
  32. data/lib/rom/repository/relation_proxy/combine.rb +0 -320
  33. data/lib/rom/repository/relation_proxy/wrap.rb +0 -78
  34. data/lib/rom/repository/struct_builder.rb +0 -83
  35. data/lib/rom/struct.rb +0 -113
  36. data/log/.gitkeep +0 -0
  37. data/rom-repository.gemspec +0 -23
  38. data/spec/integration/changeset_spec.rb +0 -193
  39. data/spec/integration/command_macros_spec.rb +0 -191
  40. data/spec/integration/command_spec.rb +0 -228
  41. data/spec/integration/multi_adapter_spec.rb +0 -73
  42. data/spec/integration/repository/aggregate_spec.rb +0 -58
  43. data/spec/integration/repository_spec.rb +0 -406
  44. data/spec/integration/root_repository_spec.rb +0 -106
  45. data/spec/integration/typed_structs_spec.rb +0 -64
  46. data/spec/shared/database.rb +0 -79
  47. data/spec/shared/mappers.rb +0 -35
  48. data/spec/shared/models.rb +0 -41
  49. data/spec/shared/plugins.rb +0 -66
  50. data/spec/shared/relations.rb +0 -115
  51. data/spec/shared/repo.rb +0 -86
  52. data/spec/shared/seeds.rb +0 -30
  53. data/spec/shared/structs.rb +0 -140
  54. data/spec/spec_helper.rb +0 -83
  55. data/spec/support/mapper_registry.rb +0 -9
  56. data/spec/support/mutant.rb +0 -10
  57. data/spec/unit/changeset/associate_spec.rb +0 -120
  58. data/spec/unit/changeset/map_spec.rb +0 -111
  59. data/spec/unit/changeset_spec.rb +0 -186
  60. data/spec/unit/relation_proxy_spec.rb +0 -202
  61. data/spec/unit/repository/changeset_spec.rb +0 -197
  62. data/spec/unit/repository/inspect_spec.rb +0 -18
  63. data/spec/unit/repository/session_spec.rb +0 -251
  64. data/spec/unit/repository/transaction_spec.rb +0 -42
  65. data/spec/unit/session_spec.rb +0 -46
  66. 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