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,100 +0,0 @@
1
- require 'rom/initializer'
2
-
3
- module ROM
4
- class Changeset
5
- # Associated changesets automatically set up FKs
6
- #
7
- # @api public
8
- class Associated
9
- extend Initializer
10
-
11
- # @!attribute [r] left
12
- # @return [Changeset::Create] Child changeset
13
- param :left
14
-
15
- # @!attribute [r] associations
16
- # @return [Array] List of association identifiers from relation schema
17
- option :associations
18
-
19
- # Infer association name from an object with a schema
20
- #
21
- # This expects other to be an object with a schema that includes a primary key
22
- # attribute with :source meta information. This makes it work with both structs
23
- # and relations
24
- #
25
- # @see Stateful#associate
26
- #
27
- # @api private
28
- def self.infer_assoc_name(other)
29
- schema = other.class.schema
30
- attrs = schema.is_a?(Hash) ? schema.values : schema
31
- pk = attrs.detect { |attr| attr.meta[:primary_key] }
32
-
33
- if pk
34
- pk.meta[:source].relation
35
- else
36
- raise ArgumentError, "can't infer association name for #{other}"
37
- end
38
- end
39
-
40
- # Commit changeset's composite command
41
- #
42
- # @example
43
- # task_changeset = task_repo.
44
- # changeset(title: 'Task One').
45
- # associate(user, :user).
46
- # commit
47
- # # {:id => 1, :user_id => 1, title: 'Task One'}
48
- #
49
- # @return [Array<Hash>, Hash]
50
- #
51
- # @api public
52
- def commit
53
- command.call
54
- end
55
-
56
- # @api public
57
- def associate(other, name = Associated.infer_assoc_name(other))
58
- self.class.new(left, associations: associations.merge(name => other))
59
- end
60
-
61
- # Create a composed command
62
- #
63
- # @example using existing parent data
64
- # user_changeset = user_repo.changeset(name: 'Jane')
65
- # task_changeset = task_repo.changeset(title: 'Task One')
66
- #
67
- # user = user_repo.create(user_changeset)
68
- # task = task_repo.create(task_changeset.associate(user, :user))
69
- #
70
- # @example saving both parent and child in one go
71
- # user_changeset = user_repo.changeset(name: 'Jane')
72
- # task_changeset = task_repo.changeset(title: 'Task One')
73
- #
74
- # task = task_repo.create(task_changeset.associate(user, :user))
75
- #
76
- # This works *only* with parent => child(ren) changeset hierarchy
77
- #
78
- # @return [ROM::Command::Composite]
79
- #
80
- # @api public
81
- def command
82
- associations.reduce(left.command.curry(left)) do |a, (assoc, other)|
83
- case other
84
- when Changeset
85
- a >> other.command.with_association(assoc).curry(other)
86
- when Associated
87
- a >> other.command.with_association(assoc)
88
- else
89
- a.with_association(assoc, parent: other)
90
- end
91
- end
92
- end
93
-
94
- # @api private
95
- def relation
96
- left.relation
97
- end
98
- end
99
- end
100
- end
@@ -1,16 +0,0 @@
1
- module ROM
2
- class Changeset
3
- # Changeset specialization for create commands
4
- #
5
- # @see Changeset::Stateful
6
- #
7
- # @api public
8
- class Create < Stateful
9
- command_type :create
10
-
11
- def command
12
- super.new(relation)
13
- end
14
- end
15
- end
16
- end
@@ -1,17 +0,0 @@
1
- require 'rom/repository/changeset/restricted'
2
-
3
- module ROM
4
- class Changeset
5
- # Changeset specialization for delete commands
6
- #
7
- # Delete changesets will execute delete command for its relation, which
8
- # means proper restricted relations should be used with this changeset.
9
- #
10
- # @api public
11
- class Delete < Changeset
12
- include Restricted
13
-
14
- command_type :delete
15
- end
16
- end
17
- end
@@ -1,97 +0,0 @@
1
- require 'transproc/registry'
2
- require 'transproc/transformer'
3
-
4
- module ROM
5
- class Changeset
6
- # Transproc Registry useful for pipe
7
- #
8
- # @api private
9
- module PipeRegistry
10
- extend Transproc::Registry
11
-
12
- import Transproc::HashTransformations
13
-
14
- def self.add_timestamps(data)
15
- now = Time.now
16
- data.merge(created_at: now, updated_at: now)
17
- end
18
-
19
- def self.touch(data)
20
- data.merge(updated_at: Time.now)
21
- end
22
- end
23
-
24
- # Composable data transformation pipe used by default in changesets
25
- #
26
- # @api private
27
- class Pipe < Transproc::Transformer[PipeRegistry]
28
- extend Initializer
29
-
30
- param :processor, default: -> { self.class.transproc }
31
- option :use_for_diff, optional: true, default: -> { true }
32
- option :diff_processor, optional: true, default: -> { use_for_diff ? processor : nil }
33
-
34
- def self.[](name)
35
- container[name]
36
- end
37
-
38
- def [](name)
39
- self.class[name]
40
- end
41
-
42
- def bind(context)
43
- return self unless processor.is_a?(Proc) || diff_processor.is_a?(Proc)
44
-
45
- new(bind_processor(processor, context), diff_processor: bind_processor(diff_processor, context))
46
- end
47
-
48
- def compose(other, use_for_diff: other.is_a?(Pipe) ? other.use_for_diff : false)
49
- new_proc = processor ? processor >> other : other
50
-
51
- if use_for_diff
52
- diff_proc = diff_processor ? diff_processor >> other : other
53
- new(new_proc, diff_processor: diff_proc)
54
- else
55
- new(new_proc)
56
- end
57
- end
58
- alias_method :>>, :compose
59
-
60
- def call(data)
61
- if processor
62
- processor.call(data)
63
- else
64
- data
65
- end
66
- end
67
-
68
- def for_diff(data)
69
- if diff_processor
70
- diff_processor.call(data)
71
- else
72
- data
73
- end
74
- end
75
-
76
- def with(opts)
77
- if opts.empty?
78
- self
79
- else
80
- Pipe.new(processor, options.merge(opts))
81
- end
82
- end
83
-
84
- def new(processor, opts = EMPTY_HASH)
85
- Pipe.new(processor, options.merge(opts))
86
- end
87
-
88
- def bind_processor(processor, context)
89
- if processor.is_a?(Proc)
90
- self[-> *args { context.instance_exec(*args, &processor) }]
91
- else
92
- processor
93
- end
94
- end
95
- end
96
- end
97
- end
@@ -1,28 +0,0 @@
1
- module ROM
2
- class Changeset
3
- module Restricted
4
- # Return a command restricted by the changeset's relation
5
- #
6
- # @see Changeset#command
7
- #
8
- # @api private
9
- def command
10
- super.new(relation)
11
- end
12
-
13
- # Restrict changeset's relation by its PK
14
- #
15
- # @example
16
- # repo.changeset(UpdateUser).by_pk(1).data(name: "Jane")
17
- #
18
- # @param [Object] pk
19
- #
20
- # @return [Changeset]
21
- #
22
- # @api public
23
- def by_pk(pk, data = EMPTY_HASH)
24
- new(relation.by_pk(pk), __data__: data)
25
- end
26
- end
27
- end
28
- end
@@ -1,282 +0,0 @@
1
- require 'rom/repository/changeset/pipe'
2
-
3
- module ROM
4
- class Changeset
5
- # Stateful changesets carry data and can transform it into
6
- # a different structure compatible with a persistence backend
7
- #
8
- # @abstract
9
- class Stateful < Changeset
10
- # Default no-op pipe
11
- EMPTY_PIPE = Pipe.new.freeze
12
-
13
- # @!attribute [r] __data__
14
- # @return [Hash] The relation data
15
- # @api private
16
- option :__data__, optional: true
17
-
18
- # @!attribute [r] pipe
19
- # @return [Changeset::Pipe] data transformation pipe
20
- # @api private
21
- option :pipe, reader: false, optional: true
22
-
23
- # Define a changeset mapping
24
- #
25
- # Subsequent mapping definitions will be composed together
26
- # and applied in the order they way defined
27
- #
28
- # @example Transformation DSL
29
- # class NewUser < ROM::Changeset::Create
30
- # map do
31
- # unwrap :address, prefix: true
32
- # end
33
- # end
34
- #
35
- # @example Using custom block
36
- # class NewUser < ROM::Changeset::Create
37
- # map do |tuple|
38
- # tuple.merge(created_at: Time.now)
39
- # end
40
- # end
41
- #
42
- # @example Multiple mappings (executed in the order of definition)
43
- # class NewUser < ROM::Changeset::Create
44
- # map do
45
- # unwrap :address, prefix: true
46
- # end
47
- #
48
- # map do |tuple|
49
- # tuple.merge(created_at: Time.now)
50
- # end
51
- # end
52
- #
53
- # @return [Array<Pipe>, Transproc::Function>]
54
- #
55
- # @see https://github.com/solnic/transproc Transproc
56
- #
57
- # @api public
58
- def self.map(options = EMPTY_HASH, &block)
59
- if block.parameters.empty?
60
- pipes << Class.new(Pipe, &block).new(options)
61
- else
62
- pipes << Pipe.new(block, options)
63
- end
64
- end
65
-
66
- # Define a changeset mapping excluded from diffs
67
- #
68
- # @see Changeset::Stateful.map
69
- # @see Changeset::Stateful#extend
70
- #
71
- # @return [Array<Pipe>, Transproc::Function>]
72
- #
73
- # @api public
74
- def self.extend(*, &block)
75
- if block
76
- map(use_for_diff: false, &block)
77
- else
78
- super
79
- end
80
- end
81
-
82
- # Build default pipe object
83
- #
84
- # This can be overridden in a custom changeset subclass
85
- #
86
- # @return [Pipe]
87
- def self.default_pipe(context)
88
- pipes.size > 0 ? pipes.map { |p| p.bind(context) }.reduce(:>>) : EMPTY_PIPE
89
- end
90
-
91
- # @api private
92
- def self.inherited(klass)
93
- return if klass == ROM::Changeset
94
- super
95
- klass.instance_variable_set(:@__pipes__, pipes ? pipes.dup : EMPTY_ARRAY)
96
- end
97
-
98
- # @api private
99
- def self.pipes
100
- @__pipes__
101
- end
102
-
103
- # Pipe changeset's data using custom steps define on the pipe
104
- #
105
- # @overload map(*steps)
106
- # Apply mapping using built-in transformations
107
- #
108
- # @example
109
- # changeset.map(:add_timestamps)
110
- #
111
- # @param [Array<Symbol>] steps A list of mapping steps
112
- #
113
- # @overload map(&block)
114
- # Apply mapping using a custom block
115
- #
116
- # @example
117
- # changeset.map { |tuple| tuple.merge(created_at: Time.now) }
118
- #
119
- # @overload map(*steps, &block)
120
- # Apply mapping using built-in transformations and a custom block
121
- #
122
- # @example
123
- # changeset.map(:add_timestamps) { |tuple| tuple.merge(status: 'published') }
124
- #
125
- # @param [Array<Symbol>] steps A list of mapping steps
126
- #
127
- # @return [Changeset]
128
- #
129
- # @api public
130
- def map(*steps, &block)
131
- extend(*steps, use_for_diff: true, &block)
132
- end
133
-
134
- # Pipe changeset's data using custom steps define on the pipe.
135
- # You should use #map instead except updating timestamp fields.
136
- # Calling changeset.extend builds a pipe that excludes certain
137
- # steps for generating the diff. Currently the only place where
138
- # it is used is update changesets with the `:touch` step, i.e.
139
- # `changeset.extend(:touch).diff` will exclude `:updated_at`
140
- # from the diff.
141
- #
142
- # @see Changeset::Stateful#map
143
- #
144
- # @return [Changeset]
145
- #
146
- # @api public
147
- def extend(*steps, use_for_diff: false, **opts, &block)
148
- options = { use_for_diff: use_for_diff, **opts }
149
-
150
- if block
151
- if steps.size > 0
152
- extend(*steps, options).extend(options, &block)
153
- else
154
- with(pipe: pipe.compose(Pipe.new(block).bind(self), options))
155
- end
156
- else
157
- with(pipe: steps.reduce(pipe.with(options)) { |a, e| a.compose(pipe[e], options) })
158
- end
159
- end
160
-
161
- # Return changeset with data
162
- #
163
- # @param [Hash] data
164
- #
165
- # @return [Changeset]
166
- #
167
- # @api public
168
- def data(data)
169
- with(__data__: data)
170
- end
171
-
172
- # Coerce changeset to a hash
173
- #
174
- # This will send the data through the pipe
175
- #
176
- # @return [Hash]
177
- #
178
- # @api public
179
- def to_h
180
- pipe.call(__data__)
181
- end
182
- alias_method :to_hash, :to_h
183
-
184
- # Coerce changeset to an array
185
- #
186
- # This will send the data through the pipe
187
- #
188
- # @return [Array]
189
- #
190
- # @api public
191
- def to_a
192
- result == :one ? [to_h] : __data__.map { |element| pipe.call(element) }
193
- end
194
- alias_method :to_ary, :to_a
195
-
196
- # Commit stateful changeset
197
- #
198
- # @see Changeset#commit
199
- #
200
- # @api public
201
- def commit
202
- command.call(self)
203
- end
204
-
205
- # Associate a changeset with another changeset or hash-like object
206
- #
207
- # @example with another changeset
208
- # new_user = user_repo.changeset(name: 'Jane')
209
- # new_task = user_repo.changeset(:tasks, title: 'A task')
210
- #
211
- # new_task.associate(new_user, :users)
212
- #
213
- # @example with a hash-like object
214
- # user = user_repo.users.by_pk(1).one
215
- # new_task = user_repo.changeset(:tasks, title: 'A task')
216
- #
217
- # new_task.associate(user, :users)
218
- #
219
- # @param [#to_hash, Changeset] other Other changeset or hash-like object
220
- # @param [Symbol] assoc The association identifier from schema
221
- #
222
- # @api public
223
- def associate(other, name = Associated.infer_assoc_name(other))
224
- Associated.new(self, associations: { name => other })
225
- end
226
-
227
- # Return command result type
228
- #
229
- # @return [Symbol]
230
- #
231
- # @api private
232
- def result
233
- __data__.is_a?(Array) ? :many : :one
234
- end
235
-
236
- # @api public
237
- def command
238
- command_compiler.(command_type, relation_identifier, DEFAULT_COMMAND_OPTS.merge(result: result))
239
- end
240
-
241
- # Return string representation of the changeset
242
- #
243
- # @return [String]
244
- #
245
- # @api public
246
- def inspect
247
- %(#<#{self.class} relation=#{relation.name.inspect} data=#{__data__}>)
248
- end
249
-
250
- # Data transformation pipe
251
- #
252
- # @return [Changeset::Pipe]
253
- #
254
- # @api private
255
- def pipe
256
- @pipe ||= self.class.default_pipe(self)
257
- end
258
-
259
- private
260
-
261
- # @api private
262
- def respond_to_missing?(meth, include_private = false)
263
- super || __data__.respond_to?(meth)
264
- end
265
-
266
- # @api private
267
- def method_missing(meth, *args, &block)
268
- if __data__.respond_to?(meth)
269
- response = __data__.__send__(meth, *args, &block)
270
-
271
- if response.is_a?(__data__.class)
272
- with(__data__: response)
273
- else
274
- response
275
- end
276
- else
277
- super
278
- end
279
- end
280
- end
281
- end
282
- end