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.
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