rom-repository 1.0.2 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3d225169d8e6601c42a19f9e8504363fb766b49b
4
- data.tar.gz: 2277ca38edb2706b648fabbd30da68cbbb9adb3c
3
+ metadata.gz: 14c56e0b7faccfcb4ed7d1b7b8506e3f5adc6384
4
+ data.tar.gz: 2c9e669ae2e2e863a033c9bc9169589d98200d87
5
5
  SHA512:
6
- metadata.gz: fb36f1ef4cd751ddb8de93a54018b6c057db268c00f66131a5911f7b3f632e1db159952fc6be2043a486ed6c31dedb6aced2933df57a3b9cb74768d2fa09f346
7
- data.tar.gz: 73ce2e1fbcedc63495fd7b6bcd959a938427d9b16774396450dd04b26a64585a4f4f56033cf7763c29f9ca0e94cdb72517e01b2aa24920f0c7894bb60a1bfa4b
6
+ metadata.gz: ac27d7fd92fe6385817f18063573da18f98f710d391d2dcf369cfc0fd07b4540582d6190aadd2d7cfd39b89a073541c7aa2defcb2d5c03e896238a405c6f8d27
7
+ data.tar.gz: 6e6af0a5a553888023c7d1164ab9e864572a9c3ac659f76b9220e484e8e9db06dae704c7fda3e244c8cef87fe050722891307fcc6379f26fecd0ac1936b2c105
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ # v1.1.0 2017-02-16
2
+
3
+ ### Added
4
+
5
+ * Mapping to `ROM::Struct` can be disabled via `auto_struct` option (solnic)
6
+ * You can now use custom mappers along with auto-mapping via `map_with(:my_mapper, auto_map: true)` (solnic)
7
+ * `wrap` can be used along with association names ie `users.wrap(:address)` (solnic)
8
+ * `#node` relation method that can be used to adjust graph nodes produced by `aggregate` or `combine` (solnic)
9
+
10
+ ## Fixed
11
+
12
+ * Structs raise nicer `NoMethodError` (flash-gordon)
13
+ * Custom model set on a node relation is respected when loading aggregates (solnic)
14
+
15
+ [Compare v1.0.2...v1.1.0](https://github.com/rom-rb/rom-repository/compare/v1.0.2...v1.1.0)
16
+
1
17
  # v1.0.2 2017-02-13
2
18
 
3
19
  ### Fixed
data/Gemfile CHANGED
@@ -11,7 +11,7 @@ group :development do
11
11
  end
12
12
 
13
13
  group :test do
14
- gem 'rom-sql'
14
+ gem 'rom-sql', git: 'https://github.com/rom-rb/rom-sql.git', branch: 'master'
15
15
  gem 'rspec'
16
16
  gem 'dry-struct'
17
17
  gem 'byebug', platforms: :mri
@@ -1,11 +1,11 @@
1
1
  require 'dry/core/deprecations'
2
2
 
3
+ require 'rom/initializer'
3
4
  require 'rom/repository/class_interface'
4
5
  require 'rom/repository/mapper_builder'
5
6
  require 'rom/repository/relation_proxy'
6
7
  require 'rom/repository/command_compiler'
7
8
 
8
- require 'rom/repository/root'
9
9
  require 'rom/repository/changeset'
10
10
  require 'rom/repository/session'
11
11
 
@@ -63,10 +63,32 @@ module ROM
63
63
  }.freeze
64
64
 
65
65
  extend ClassInterface
66
+ extend Initializer
67
+ extend Dry::Core::ClassAttributes
68
+
69
+ # @!method self.auto_struct
70
+ # Get or set auto_struct setting
71
+ #
72
+ # When disabled, rom structs won't be created
73
+ #
74
+ # @overload auto_struct
75
+ # Return auto_struct setting value
76
+ # @return [TrueClass,FalseClass]
77
+ #
78
+ # @overload auto_struct(value)
79
+ # Set auto_struct value
80
+ # @return [Class]
81
+ defines :auto_struct
82
+
83
+ auto_struct true
84
+
85
+ # @!attribute [r] container
86
+ # @return [ROM::Container] The container used to set up a repo
87
+ param :container, allow: ROM::Container
66
88
 
67
89
  # @!attribute [r] container
68
90
  # @return [ROM::Container] The container used to set up a repo
69
- attr_reader :container
91
+ option :auto_struct, optional: true, default: -> repo { repo.class.auto_struct }
70
92
 
71
93
  # @!attribute [r] relations
72
94
  # @return [RelationRegistry] The relation proxy registry used by a repo
@@ -86,15 +108,17 @@ module ROM
86
108
  # @param container [ROM::Container] The rom container with relations and optional commands
87
109
  #
88
110
  # @api public
89
- def initialize(container)
90
- @container = container
111
+ def initialize(container, opts = EMPTY_HASH)
112
+ super
113
+
91
114
  @mappers = MapperBuilder.new
115
+
92
116
  @relations = RelationRegistry.new do |registry, relations|
93
117
  self.class.relations.each do |name|
94
118
  relation = container.relation(name)
95
119
 
96
120
  proxy = RelationProxy.new(
97
- relation, name: name, mappers: mappers, registry: registry
121
+ relation, name: name, mappers: mappers, registry: registry, auto_struct: auto_struct
98
122
  )
99
123
 
100
124
  instance_variable_set("@#{name}", proxy)
@@ -102,6 +126,7 @@ module ROM
102
126
  relations[name] = proxy
103
127
  end
104
128
  end
129
+
105
130
  @command_compiler = method(:command)
106
131
  end
107
132
 
@@ -327,3 +352,5 @@ module ROM
327
352
  end
328
353
  end
329
354
  end
355
+
356
+ require 'rom/repository/root'
@@ -7,7 +7,7 @@ module ROM
7
7
  class HeaderBuilder
8
8
  attr_reader :struct_builder
9
9
 
10
- def initialize
10
+ def initialize(options = EMPTY_HASH)
11
11
  @struct_builder = StructBuilder.new
12
12
  end
13
13
 
@@ -18,12 +18,12 @@ module ROM
18
18
 
19
19
  private
20
20
 
21
- def visit(node, *args)
21
+ def visit(node)
22
22
  name, node = node
23
- __send__("visit_#{name}", node, *args)
23
+ __send__("visit_#{name}", node)
24
24
  end
25
25
 
26
- def visit_relation(node, meta = {})
26
+ def visit_relation(node)
27
27
  relation_name, meta, header = node
28
28
  name = meta[:combine_name] || relation_name
29
29
 
@@ -31,7 +31,7 @@ module ROM
31
31
  struct_builder[meta.fetch(:dataset), header]
32
32
  end
33
33
 
34
- options = [visit(header, meta), model: model]
34
+ options = [visit(header), model: model]
35
35
 
36
36
  if meta[:combine_type]
37
37
  type = meta[:combine_type] == :many ? :array : :hash
@@ -45,12 +45,12 @@ module ROM
45
45
  end
46
46
  end
47
47
 
48
- def visit_header(node, meta = {})
49
- node.map { |attribute| visit(attribute, meta) }
48
+ def visit_header(node)
49
+ node.map { |attribute| visit(attribute) }
50
50
  end
51
51
 
52
- def visit_attribute(attr, meta = {})
53
- if meta[:wrap]
52
+ def visit_attribute(attr)
53
+ if attr.wrapped?
54
54
  [attr.name, from: attr.alias]
55
55
  else
56
56
  [attr.name]
@@ -30,6 +30,7 @@ module ROM
30
30
  option :mappers, reader: true, default: proc { MapperBuilder.new }
31
31
  option :meta, reader: true, default: proc { EMPTY_HASH }
32
32
  option :registry, type: RelationRegistryType, default: proc { RelationRegistry.new }, reader: true
33
+ option :auto_struct, optional: true
33
34
 
34
35
  # Relation name
35
36
  #
@@ -68,20 +69,78 @@ module ROM
68
69
  #
69
70
  # @param [Array<Symbol>] mappers A list of mapper identifiers
70
71
  #
72
+ # @overload map_with(*mappers, auto_map: true)
73
+ # Map tuples using auto-mapping and custom registered mappers
74
+ #
75
+ # If `auto_map` is enabled, your mappers will be applied after performing
76
+ # default auto-mapping. This means that you can compose complex relations
77
+ # and have them auto-mapped, and use much simpler custom mappers to adjust
78
+ # resulting data according to your requirements.
79
+ #
80
+ # @example
81
+ # users.map_with(:my_mapper, :my_other_mapper, auto_map: true)
82
+ #
83
+ # @param [Array<Symbol>] mappers A list of mapper identifiers
84
+ #
71
85
  # @return [RelationProxy] A new relation proxy with pipelined relation
72
86
  #
73
87
  # @api public
74
- def map_with(*names)
88
+ def map_with(*names, **opts)
75
89
  if names.size == 1 && names[0].is_a?(Class)
76
90
  with(meta: meta.merge(model: names[0]))
77
91
  elsif names.size > 1 && names.any? { |name| name.is_a?(Class) }
78
92
  raise ArgumentError, 'using custom mappers and a model is not supported'
79
93
  else
80
- names.reduce(self) { |a, e| a >> relation.mappers[e] }
94
+ if opts[:auto_map]
95
+ mappers = [mapper, *names.map { |name| relation.mappers[name] }]
96
+ mappers.reduce(self) { |a, e| a >> e }
97
+ else
98
+ names.reduce(self) { |a, e| a >> relation.mappers[e] }
99
+ end
81
100
  end
82
101
  end
83
102
  alias_method :as, :map_with
84
103
 
104
+ # Return a new graph with adjusted node returned from a block
105
+ #
106
+ # @example with a node identifier
107
+ # aggregate(:tasks).node(:tasks) { |tasks| tasks.prioritized }
108
+ #
109
+ # @example with a nested path
110
+ # aggregate(tasks: :tags).node(tasks: :tags) { |tags| tags.where(name: 'red') }
111
+ #
112
+ # @param [Symbol] name The node relation name
113
+ #
114
+ # @yieldparam [RelationProxy] The relation node
115
+ # @yieldreturn [RelationProxy] The new relation node
116
+ #
117
+ # @return [RelationProxy]
118
+ #
119
+ # @api public
120
+ def node(name, &block)
121
+ if name.is_a?(Symbol) && !nodes.map { |n| n.name.relation }.include?(name)
122
+ raise ArgumentError, "#{name.inspect} is not a valid aggregate node name"
123
+ end
124
+
125
+ new_nodes = nodes.map { |node|
126
+ case name
127
+ when Symbol
128
+ name == node.name.relation ? yield(node) : node
129
+ when Hash
130
+ other, *rest = name.flatten(1)
131
+ if other == node.name.relation
132
+ nodes.detect { |n| n.name.relation == other }.node(*rest, &block)
133
+ else
134
+ node
135
+ end
136
+ else
137
+ node
138
+ end
139
+ }
140
+
141
+ with_nodes(new_nodes)
142
+ end
143
+
85
144
  # Return a string representation of this relation proxy
86
145
  #
87
146
  # @return [String]
@@ -147,6 +206,7 @@ module ROM
147
206
  attr_ast = schema.map { |attr| [:attribute, attr] }
148
207
 
149
208
  meta = self.meta.merge(dataset: base_name.dataset)
209
+ meta.update(model: false) unless meta[:model] || auto_struct
150
210
  meta.delete(:wraps)
151
211
 
152
212
  header = attr_ast + nodes_ast + wraps_ast
@@ -155,6 +215,17 @@ module ROM
155
215
  end
156
216
  end
157
217
 
218
+ # TODO: add a proper interface to `Relation::Graph` and `Relation::Curried` to rom core
219
+ #
220
+ # @api private
221
+ def with_nodes(nodes)
222
+ if relation.curried?
223
+ __new__(relation.send(:__new__, relation.relation.class.new(relation.root, nodes)))
224
+ else
225
+ __new__(relation.class.new(relation.root, nodes))
226
+ end
227
+ end
228
+
158
229
  # @api private
159
230
  def respond_to_missing?(meth, _include_private = false)
160
231
  relation.respond_to?(meth) || super
@@ -164,7 +235,11 @@ module ROM
164
235
 
165
236
  # @api private
166
237
  def schema
167
- meta[:wrap] ? relation.schema.wrap.qualified : relation.schema.reject(&:wrapped?)
238
+ if meta[:wrap]
239
+ relation.schema.wrap
240
+ else
241
+ relation.schema.reject(&:wrapped?)
242
+ end
168
243
  end
169
244
 
170
245
  # @api private
@@ -15,16 +15,15 @@ module ROM
15
15
  # @return [RelationProxy]
16
16
  #
17
17
  # @api public
18
- def wrap(options)
19
- wraps = options.map { |(name, (relation, keys))|
20
- relation.wrapped(name, keys)
21
- }
18
+ def wrap(*names, **options)
19
+ new_wraps = wraps_from_names(names) + wraps_from_options(options)
22
20
 
23
- relation = wraps.reduce(self) { |a, e|
24
- a.relation.for_wrap(e.meta.fetch(:keys), e.base_name.relation)
21
+ relation = new_wraps.reduce(self) { |a, e|
22
+ name = e.meta[:wrap_from_assoc] ? e.meta[:combine_name] : e.base_name.relation
23
+ a.relation.for_wrap(e.meta.fetch(:keys), name)
25
24
  }
26
25
 
27
- __new__(relation, meta: { wraps: wraps })
26
+ __new__(relation, meta: { wraps: wraps + new_wraps })
28
27
  end
29
28
 
30
29
  # Shortcut to wrap parents
@@ -52,8 +51,26 @@ module ROM
52
51
  # @return [RelationProxy]
53
52
  #
54
53
  # @api private
55
- def wrapped(name, keys)
56
- with(name: name, meta: { keys: keys, wrap: true, combine_name: name })
54
+ def wrapped(name, keys, wrap_from_assoc = false)
55
+ with(
56
+ name: name,
57
+ meta: {
58
+ keys: keys, wrap_from_assoc: wrap_from_assoc, wrap: true, combine_name: name
59
+ }
60
+ )
61
+ end
62
+
63
+ # @api private
64
+ def wraps_from_options(options)
65
+ options.map { |(name, (relation, keys))| relation.wrapped(name, keys) }
66
+ end
67
+
68
+ # @api private
69
+ def wraps_from_names(names)
70
+ names.map { |name|
71
+ assoc = associations[name]
72
+ registry[assoc.target].wrapped(name, assoc.combine_keys(__registry__), true)
73
+ }
57
74
  end
58
75
  end
59
76
  end
@@ -29,8 +29,6 @@ module ROM
29
29
  #
30
30
  # @api public
31
31
  class Root < Repository
32
- extend Dry::Core::ClassAttributes
33
-
34
32
  # @!method self.root
35
33
  # Get or set repository root relation identifier
36
34
  #
@@ -59,7 +57,7 @@ module ROM
59
57
  end
60
58
 
61
59
  # @see Repository#initialize
62
- def initialize(container)
60
+ def initialize(container, opts = EMPTY_HASH)
63
61
  super
64
62
  @root = relations[self.class.root]
65
63
  end
@@ -40,12 +40,19 @@ module ROM
40
40
  relation_name, meta, header = node
41
41
  name = meta[:combine_name] || relation_name.relation
42
42
 
43
- model = call(name, header)
43
+ model = meta.fetch(:model) { call(name, header) }
44
+
45
+ member =
46
+ if model < Dry::Struct
47
+ model
48
+ else
49
+ Dry::Types::Definition.new(model).constructor(&model.method(:new))
50
+ end
44
51
 
45
52
  if meta[:combine_type] == :many
46
- [name, Types::Array.member(model)]
53
+ [name, Types::Array.member(member)]
47
54
  else
48
- [name, model.optional]
55
+ [name, member.optional]
49
56
  end
50
57
  end
51
58
 
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  class Repository
3
- VERSION = '1.0.2'.freeze
3
+ VERSION = '1.1.0'.freeze
4
4
  end
5
5
  end
data/lib/rom/struct.rb CHANGED
@@ -62,5 +62,18 @@ module ROM
62
62
  def fetch(name)
63
63
  __send__(name)
64
64
  end
65
+
66
+ private
67
+
68
+ def method_missing(m, *args)
69
+ inspected = inspect
70
+ trace = caller
71
+
72
+ # This is how MRI currently works
73
+ # see func name_err_mesg_to_str in error.c
74
+ name = inspected.size > 65 ? to_s : inspected
75
+
76
+ raise NoMethodError.new("undefined method `#{ m }' for #{ name }", m, args).tap { |e| e.set_backtrace(trace) }
77
+ end
65
78
  end
66
79
  end
@@ -0,0 +1,49 @@
1
+ RSpec.describe ROM::Repository::Root, '#aggregate' do
2
+ subject(:repo) do
3
+ Class.new(ROM::Repository[:users]) do
4
+ relations :tasks, :posts, :labels
5
+ end.new(rom)
6
+ end
7
+
8
+ include_context 'database'
9
+ include_context 'relations'
10
+ include_context 'seeds'
11
+
12
+ it 'exposes nodes via `node` method' do
13
+ jane = repo.
14
+ aggregate(:posts).
15
+ node(:posts) { |posts| posts.where(title: 'Another one') }.
16
+ where(name: 'Jane').one
17
+
18
+ expect(jane.name).to eql('Jane')
19
+ expect(jane.posts).to be_empty
20
+
21
+ repo.posts.insert author_id: 1, title: 'Another one'
22
+
23
+ jane = repo.
24
+ aggregate(:posts).
25
+ node(:posts) { |posts| posts.where(title: 'Another one') }.
26
+ where(name: 'Jane').one
27
+
28
+ expect(jane.name).to eql('Jane')
29
+ expect(jane.posts.size).to be(1)
30
+ expect(jane.posts[0].title).to eql('Another one')
31
+ end
32
+
33
+ it 'exposes nested nodes via `node` method' do
34
+ jane = repo.
35
+ aggregate(posts: :labels).
36
+ node(posts: :labels) { |labels| labels.where(name: 'red') }.
37
+ where(name: 'Jane').one
38
+
39
+ expect(jane.name).to eql('Jane')
40
+ expect(jane.posts.size).to be(1)
41
+ expect(jane.posts[0].labels.size).to be(1)
42
+ expect(jane.posts[0].labels[0].name).to eql('red')
43
+ end
44
+
45
+ it 'raises arg error when invalid relation name was passed to `node` method' do
46
+ expect { repo.aggregate(:posts).node(:poztz) {} }.
47
+ to raise_error(ArgumentError, ':poztz is not a valid aggregate node name')
48
+ end
49
+ end
@@ -70,6 +70,13 @@ RSpec.describe 'ROM repository' do
70
70
  expect(repo.tag_with_wrapped_task.first).to eql(tag_with_task)
71
71
  end
72
72
 
73
+ it 'loads multiple wraps' do
74
+ post_label = repo.posts_labels.wrap(:post).wrap(:label).to_a.first
75
+
76
+ expect(post_label.label_id).to be(post_label.label.id)
77
+ expect(post_label.post_id).to be(post_label.post.id)
78
+ end
79
+
73
80
  it 'loads an aggregate via custom fks' do
74
81
  jane = repo.aggregate(many: repo.posts).where(name: 'Jane').one
75
82
 
@@ -135,7 +142,7 @@ RSpec.describe 'ROM repository' do
135
142
  expect(jane.labels[1].name).to eql('blue')
136
143
  end
137
144
 
138
- it 'loads children and its parents via wrap' do
145
+ it 'loads children and its parents via wrap_parent' do
139
146
  posts = repo.posts.wrap_parent(author: repo.users)
140
147
 
141
148
  label = repo.labels.combine(many: { posts: posts }).first
@@ -146,6 +153,15 @@ RSpec.describe 'ROM repository' do
146
153
  expect(label.posts[0].author.name).to eql('Jane')
147
154
  end
148
155
 
156
+ it 'loads children and its parents via wrap and association name' do
157
+ label = repo.labels.combine(many: { posts: repo.posts.wrap(:author) }).first
158
+
159
+ expect(label.name).to eql('red')
160
+ expect(label.posts.size).to be(1)
161
+ expect(label.posts[0].title).to eql('Hello From Jane')
162
+ expect(label.posts[0].author.name).to eql('Jane')
163
+ end
164
+
149
165
  it 'loads a parent via custom fks' do
150
166
  post = repo.posts.combine(:author).where(title: 'Hello From Jane').one
151
167
 
@@ -243,4 +259,81 @@ RSpec.describe 'ROM repository' do
243
259
  expect(repo.dummy.to_a).to eql([])
244
260
  end
245
261
  end
262
+
263
+ describe 'mapping without structs' do
264
+ shared_context 'plain hash mapping' do
265
+ describe '#one' do
266
+ it 'returns a hash' do
267
+ expect(repo.users.limit(1).one).to eql(id: 1, name: 'Jane')
268
+ end
269
+
270
+ it 'returns a nested hash for an aggregate' do
271
+ expect(repo.aggregate(:posts).limit(1).one).
272
+ to eql(id: 1, name: 'Jane', posts: [{ id: 1, author_id: 1, title: 'Hello From Jane', body: 'Jane Post'}])
273
+ end
274
+ end
275
+ end
276
+
277
+ context 'with auto_struct disabled upon initialization' do
278
+ subject(:repo) do
279
+ repo_class.new(rom, auto_struct: false)
280
+ end
281
+
282
+ include_context 'plain hash mapping'
283
+ end
284
+
285
+ context 'with auto_struct disabled at the class level' do
286
+ before do
287
+ repo_class.auto_struct(false)
288
+ end
289
+
290
+ include_context 'plain hash mapping'
291
+ end
292
+ end
293
+
294
+ describe 'using custom mappers along with auto-mapping' do
295
+ before do
296
+ configuration.mappers do
297
+ define(:users) do
298
+ register_as :embed_address
299
+
300
+ def call(rel)
301
+ rel.map { |tuple| Hash(tuple).merge(mapped: true) }
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+ it 'auto-maps and applies a custom mapper' do
308
+ jane = repo.users.combine(:posts).map_with(:embed_address, auto_map: true).to_a.first
309
+
310
+ expect(jane).
311
+ to eql(id:1, name: 'Jane', mapped: true, posts: [{ id: 1, author_id: 1, title: 'Hello From Jane', body: 'Jane Post' }])
312
+ end
313
+ end
314
+
315
+ describe 'using a custom model for a node' do
316
+ before do
317
+ class Test::Post < OpenStruct; end
318
+ end
319
+
320
+ it 'uses provided model for the member type' do
321
+ jane = repo.users.combine(many: repo.posts.as(Test::Post)).where(name: 'Jane').one
322
+
323
+ expect(jane.name).to eql('Jane')
324
+ expect(jane.posts.size).to be(1)
325
+ expect(jane.posts[0]).to be_instance_of(Test::Post)
326
+ expect(jane.posts[0].title).to eql('Hello From Jane')
327
+ expect(jane.posts[0].body).to eql('Jane Post')
328
+ end
329
+
330
+ it 'uses provided model for the attribute type' do
331
+ jane = repo.users.combine(one: repo.posts.as(Test::Post)).where(name: 'Jane').one
332
+
333
+ expect(jane.name).to eql('Jane')
334
+ expect(jane.post).to be_instance_of(Test::Post)
335
+ expect(jane.post.title).to eql('Hello From Jane')
336
+ expect(jane.post.body).to eql('Jane Post')
337
+ end
338
+ end
246
339
  end
@@ -134,7 +134,8 @@ RSpec.describe 'loading proxy' do
134
134
  [:attribute, users.schema[:name]],
135
135
  [:relation, [
136
136
  :tasks,
137
- { dataset: :tasks, keys: { id: :user_id }, combine_type: :many, combine_name: :user_tasks },
137
+ { dataset: :tasks, keys: { id: :user_id },
138
+ combine_type: :many, combine_name: :user_tasks },
138
139
  [:header, [
139
140
  [:attribute, tasks.schema[:id]],
140
141
  [:attribute, tasks.schema[:user_id]],
@@ -149,7 +150,7 @@ RSpec.describe 'loading proxy' do
149
150
  relation = tags.wrap_parent(task: tasks)
150
151
 
151
152
  tags_schema = tags.schema.qualified
152
- tasks_schema = tasks.schema.wrap.qualified
153
+ tasks_schema = tasks.schema.wrap
153
154
 
154
155
  expect(relation.to_ast).to eql(
155
156
  [:relation, [
@@ -161,7 +162,8 @@ RSpec.describe 'loading proxy' do
161
162
  [:attribute, tags_schema[:name]],
162
163
  [:relation, [
163
164
  :tasks,
164
- { dataset: :tasks, keys: { id: :task_id }, wrap: true, combine_name: :task },
165
+ { dataset: :tasks, keys: { id: :task_id },
166
+ wrap_from_assoc: false, wrap: true, combine_name: :task },
165
167
  [:header, [
166
168
  [:attribute, tasks_schema[:id]],
167
169
  [:attribute, tasks_schema[:user_id]],
@@ -69,4 +69,30 @@ RSpec.describe 'struct builder', '#call' do
69
69
  Dry::Struct::Error, /:name is missing/
70
70
  )
71
71
  end
72
+
73
+ context 'name errors' do
74
+ let(:struct) { builder.class.cache[input.hash] }
75
+
76
+ context 'missing method on instance' do
77
+ it 'uses inspect and class name for small structs' do
78
+ user = struct.new(id: 1, name: 'Jane')
79
+
80
+ expect { user.missing }.
81
+ to raise_error(
82
+ NoMethodError,
83
+ %r{undefined method `missing' for #<ROM::Struct\[User\] id=1 name="Jane">}
84
+ )
85
+ end
86
+
87
+ it 'uses class name in name errors' do
88
+ user = struct.new(id: 1, name: 'J' * 50)
89
+
90
+ expect { user.missing }.
91
+ to raise_error(
92
+ NoMethodError,
93
+ %r{undefined method `missing' for #<ROM::Struct\[User\]:0x\h+>}
94
+ )
95
+ end
96
+ end
97
+ end
72
98
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom-repository
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-13 00:00:00.000000000 Z
11
+ date: 2017-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rom
@@ -145,6 +145,7 @@ files:
145
145
  - spec/integration/command_macros_spec.rb
146
146
  - spec/integration/command_spec.rb
147
147
  - spec/integration/multi_adapter_spec.rb
148
+ - spec/integration/repository/aggregate_spec.rb
148
149
  - spec/integration/repository_spec.rb
149
150
  - spec/integration/root_repository_spec.rb
150
151
  - spec/integration/typed_structs_spec.rb
@@ -188,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
188
189
  version: '0'
189
190
  requirements: []
190
191
  rubyforge_project:
191
- rubygems_version: 2.6.10
192
+ rubygems_version: 2.6.9
192
193
  signing_key:
193
194
  specification_version: 4
194
195
  summary: Repository abstraction for rom-rb