rom-repository 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bac31b2dc1dafd64ec8a3852d1cb9e595c49ded5
4
- data.tar.gz: 9110fc0e2f6d1b742ed89c4f7afb5c9b61ad0cdc
3
+ metadata.gz: db1338c97747a9adc2b9f00febe402d61ac1a6fd
4
+ data.tar.gz: bd6ca3140fe897a0d6b9cccc3082e0fdaf63ed9a
5
5
  SHA512:
6
- metadata.gz: 77b81f8ab9040eeaa5059dee76046f2330ebaddc75c1dad792e1b7e911d42bd4056d364d5b59194208c55d366d3d3e3358376292abfa2580f4e96a86b3ab1e83
7
- data.tar.gz: c6b9d0809edd8b5b8742ff9de4a1761c287f502706416bf698453a1153cea2f122972d0dcbfdc6d06ead0acc55100b9a1f4d230fd13d24bb536e0969b301d4ac
6
+ metadata.gz: 04f9d30b32805886118b5eb36451aeba065cc510d1a92d091c1e99a41585eb821cf69b68fa56b1f049610b390d1403316a662dacfe4ba32b7f65ba6ac60b8e84
7
+ data.tar.gz: 48a3b4136e064e00a562a584555765e8fb47a05388457b3cb31ab3b762dda4dd221299a1b621dfaf3705167412af644c8244c7ebbde22da285a8985f3afb712b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # v0.3.1 2016-07-27
2
+
3
+ Fixed gemspec so that we don't exclude all files with 'bin' in their name, geez (solnic)
4
+
5
+ [Compare v0.3.0...v0.3.1](https://github.com/rom-rb/rom-repository/compare/v0.3.0...v0.3.1)
6
+
1
7
  # v0.3.0 2016-07-27
2
8
 
3
9
  ### Added
@@ -0,0 +1,283 @@
1
+ module ROM
2
+ class Repository
3
+ class RelationProxy
4
+ # Provides convenient methods for composing relations
5
+ #
6
+ # @api public
7
+ module Combine
8
+ # Returns a combine representation of a loading-proxy relation
9
+ #
10
+ # This will carry meta info used to produce a correct AST from a relation
11
+ # so that correct mapper can be generated
12
+ #
13
+ # @return [RelationProxy]
14
+ #
15
+ # @api private
16
+ def combined(name, keys, type)
17
+ meta = { keys: keys, combine_type: type, combine_name: name }
18
+ with(name: name, meta: meta)
19
+ end
20
+
21
+ # Combine with other relations
22
+ #
23
+ # @overload combine(*associations)
24
+ # Composes relations using configured associations
25
+ # @example
26
+ # users.combine(:tasks, :posts)
27
+ # @param *associations [Array<Symbol>] A list of association names
28
+ #
29
+ # @overload combine(options)
30
+ # Composes relations based on options
31
+ #
32
+ # @example
33
+ # # users have-many tasks (name and join-keys inferred, which needs associations in schema)
34
+ # users.combine(many: tasks)
35
+ #
36
+ # # users have-many tasks with custom name (join-keys inferred, which needs associations in schema)
37
+ # users.combine(many: { priority_tasks: tasks.priority })
38
+ #
39
+ # # users have-many tasks with custom view and join keys
40
+ # users.combine(many: { tasks: [tasks.for_users, id: :task_id] })
41
+ #
42
+ # # users has-one task
43
+ # users.combine(one: { task: tasks })
44
+ #
45
+ # @param options [Hash] Options for combine
46
+ # @option :many [Hash] Sets options for "has-many" type of association
47
+ # @option :one [Hash] Sets options for "has-one/belongs-to" type of association
48
+ #
49
+ # @param [Hash] options
50
+ #
51
+ # @return [RelationProxy]
52
+ #
53
+ # @api public
54
+ def combine(*args)
55
+ options = args[0].is_a?(Hash) ? args[0] : args
56
+
57
+ combine_opts = Hash.new { |h, k| h[k] = {} }
58
+
59
+ options.each do |(type, relations)|
60
+ if relations
61
+ combine_opts[type] = combine_opts_from_relations(relations)
62
+ else
63
+ result, curried, keys = combine_opts_for_assoc(type)
64
+ combine_opts[result][type] = [curried, keys]
65
+ end
66
+ end
67
+
68
+ nodes = combine_opts.flat_map do |type, relations|
69
+ relations.map { |name, (relation, keys)|
70
+ relation.combined(name, keys, type)
71
+ }
72
+ end
73
+
74
+ __new__(relation.combine(*nodes))
75
+ end
76
+
77
+ # Shortcut for combining with parents which infers the join keys
78
+ #
79
+ # @example
80
+ # # tasks belong-to users
81
+ # tasks.combine_parents(one: users)
82
+ #
83
+ # # tasks belong-to users with custom user view
84
+ # tasks.combine_parents(one: users.task_owners)
85
+ #
86
+ # @param options [Hash] Combine options hash
87
+ #
88
+ # @return [RelationProxy]
89
+ #
90
+ # @api public
91
+ def combine_parents(options)
92
+ combine_opts = {}
93
+
94
+ options.each do |type, parents|
95
+ combine_opts[type] =
96
+ case parents
97
+ when Hash
98
+ parents.each_with_object({}) { |(name, parent), r|
99
+ keys = combine_keys(parent, relation, :parent)
100
+ r[name] = [parent, keys]
101
+ }
102
+ when Array
103
+ parents.each_with_object({}) { |parent, r|
104
+ tuple_key = parent.combine_tuple_key(type)
105
+ keys = combine_keys(parent, relation, :parent)
106
+ r[tuple_key] = [parent, keys]
107
+ }
108
+ else
109
+ tuple_key = parents.combine_tuple_key(type)
110
+ keys = combine_keys(parents, relation, :parent)
111
+ { tuple_key => [parents, keys] }
112
+ end
113
+ end
114
+
115
+ combine(combine_opts)
116
+ end
117
+
118
+ # Shortcut for combining with children which infers the join keys
119
+ #
120
+ # @example
121
+ # # users have-many tasks
122
+ # users.combine_children(many: tasks)
123
+ #
124
+ # # users have-many tasks with custom mapping (requires associations)
125
+ # users.combine_children(many: { priority_tasks: tasks.priority })
126
+ #
127
+ # @param [Hash] options
128
+ #
129
+ # @return [RelationProxy]
130
+ #
131
+ # @api public
132
+ def combine_children(options)
133
+ combine_opts = {}
134
+
135
+ options.each do |type, children|
136
+ combine_opts[type] =
137
+ case children
138
+ when Hash
139
+ children.each_with_object({}) { |(name, child), r|
140
+ keys = combine_keys(relation, child, :children)
141
+ r[name] = [child, keys]
142
+ }
143
+ when Array
144
+ parents.each_with_object({}) { |child, r|
145
+ tuple_key = parent.combine_tuple_key(type)
146
+ keys = combine_keys(relation, child, :children)
147
+ r[tuple_key] = [parent, keys]
148
+ }
149
+ else
150
+ tuple_key = children.combine_tuple_key(type)
151
+ keys = combine_keys(relation, children, :children)
152
+ { tuple_key => [children, keys] }
153
+ end
154
+ end
155
+
156
+ combine(combine_opts)
157
+ end
158
+
159
+ protected
160
+
161
+ # Infer join/combine keys for a given relation and association type
162
+ #
163
+ # When source has association corresponding to target's name, it'll be
164
+ # used to get the keys. Otherwise we fall back to using default keys based
165
+ # on naming conventions.
166
+ #
167
+ # @param [RelationProxy] relation
168
+ # @param [Symbol] type The type can be either :parent or :children
169
+ #
170
+ # @return [Hash<Symbol=>Symbol>]
171
+ #
172
+ # @api private
173
+ def combine_keys(source, target, type)
174
+ source.associations.try(target.name) { |assoc|
175
+ assoc.combine_keys(__registry__)
176
+ } or infer_combine_keys(source, target, type)
177
+ end
178
+
179
+ # Build combine options from a relation mapping hash passed to `combine`
180
+ #
181
+ # This method will infer combine keys either from defined associations
182
+ # or use the keys provided explicitly for ad-hoc combines
183
+ #
184
+ # It returns a mapping like `name => [preloadable_relation, combine_keys]`
185
+ # and this mapping is used by `combine` to build a full relation graph
186
+ #
187
+ # @api private
188
+ def combine_opts_from_relations(relations)
189
+ relations.each_with_object({}) do |(name, (other, keys)), h|
190
+ h[name] =
191
+ if other.curried?
192
+ [other, keys]
193
+ else
194
+ rel = combine_from_assoc(name, other) { other.combine_method(relation, keys) }
195
+ [rel, keys]
196
+ end
197
+ end
198
+ end
199
+
200
+ # Try to get a preloadable relation from a defined association
201
+ #
202
+ # If association doesn't exist we call the fallback block
203
+ #
204
+ # @return [RelationProxy]
205
+ #
206
+ # @api private
207
+ def combine_from_assoc(name, other, &fallback)
208
+ associations.try(name) { |assoc| other.for_combine(assoc) } or fallback.call
209
+ end
210
+
211
+ # Extract result (either :one or :many), preloadable relation and its keys
212
+ # by using given association name
213
+ #
214
+ # This is used when a flat list of association names was passed to `combine`
215
+ #
216
+ # @api private
217
+ def combine_opts_for_assoc(name)
218
+ assoc = relation.associations[name]
219
+ curried = registry[assoc.target.relation].for_combine(assoc)
220
+ keys = assoc.combine_keys(__registry__)
221
+ [assoc.result, curried, keys]
222
+ end
223
+
224
+ # Build a preloadable relation for relation graph
225
+ #
226
+ # When a given relation defines `for_other_relation` then it will be used
227
+ # to preload `other_relation`. ie `users` relation defines `for_tasks`
228
+ # then when we preload tasks for users, this custom method will be used
229
+ #
230
+ # This *defaults* to the built-in `for_combine` with explicitly provided
231
+ # keys
232
+ #
233
+ # @return [RelationProxy]
234
+ #
235
+ # @api private
236
+ def combine_method(other, keys)
237
+ custom_name = :"for_#{other.name.dataset}"
238
+
239
+ if relation.respond_to?(custom_name)
240
+ __send__(custom_name)
241
+ else
242
+ for_combine(keys)
243
+ end
244
+ end
245
+
246
+ # Infer key under which a combine relation will be loaded
247
+ #
248
+ # This is used in cases like ad-hoc combines where relation was passed
249
+ # in without specifying the key explicitly, ie:
250
+ #
251
+ # tasks.combine_parents(one: users)
252
+ #
253
+ # # ^^^ this will be expanded under-the-hood to:
254
+ # tasks.combine(one: { user: users })
255
+ #
256
+ # @return [Symbol]
257
+ #
258
+ # @api private
259
+ def combine_tuple_key(result)
260
+ if result == :one
261
+ Inflector.singularize(base_name.relation).to_sym
262
+ else
263
+ base_name.relation
264
+ end
265
+ end
266
+
267
+ # Fallback mechanism for `combine_keys` when there's no association defined
268
+ #
269
+ # @api private
270
+ def infer_combine_keys(source, target, type)
271
+ primary_key = source.primary_key
272
+ foreign_key = target.foreign_key(source)
273
+
274
+ if type == :parent
275
+ { foreign_key => primary_key }
276
+ else
277
+ { primary_key => foreign_key }
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  class Repository
3
- VERSION = '0.3.0'.freeze
3
+ VERSION = '0.3.1'.freeze
4
4
  end
5
5
  end
@@ -11,7 +11,7 @@ Gem::Specification.new do |gem|
11
11
  gem.homepage = 'http://rom-rb.org'
12
12
  gem.require_paths = ['lib']
13
13
  gem.version = ROM::Repository::VERSION.dup
14
- gem.files = `git ls-files`.split("\n").reject { |name| name.include?('benchmarks') || name.include?('examples') || name.include?('bin') }
14
+ gem.files = `git ls-files`.split("\n").reject { |name| name.include?('benchmarks') || name.include?('examples') || name.include?('bin/console') }
15
15
  gem.test_files = `git ls-files -- {spec}/*`.split("\n")
16
16
  gem.license = 'MIT'
17
17
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom-repository
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
@@ -107,6 +107,7 @@ files:
107
107
  - lib/rom/repository/header_builder.rb
108
108
  - lib/rom/repository/mapper_builder.rb
109
109
  - lib/rom/repository/relation_proxy.rb
110
+ - lib/rom/repository/relation_proxy/combine.rb
110
111
  - lib/rom/repository/relation_proxy/wrap.rb
111
112
  - lib/rom/repository/root.rb
112
113
  - lib/rom/repository/struct_attributes.rb