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