neo4j-core 4.0.7 → 5.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,8 +12,11 @@ module Neo4j
12
12
 
13
13
  class Clause
14
14
  include CypherTranslator
15
+ UNDERSCORE = '_'
16
+ COMMA_SPACE = ', '
17
+ AND = ' AND '
15
18
 
16
- attr_reader :params
19
+ attr_accessor :params, :arg
17
20
 
18
21
  def initialize(arg, options = {})
19
22
  @arg = arg
@@ -54,19 +57,23 @@ module Neo4j
54
57
  label = label_from_key_and_value(key, value, options[:prefer] || :var)
55
58
  attributes = attributes_from_key_and_value(key, value)
56
59
 
57
- "(#{var}#{format_label(label)}#{attributes_string(attributes)})"
60
+ prefix_value = value
61
+ if value.is_a?(Hash)
62
+ prefix_value = if value.values.any? { |v| v.is_a?(Hash) }
63
+ value.keys.join(UNDERSCORE)
64
+ end
65
+ end
66
+
67
+ prefix_array = [key, prefix_value].tap(&:compact!).join(UNDERSCORE)
68
+ formatted_attributes = attributes_string(attributes, "#{prefix_array}#{UNDERSCORE}")
69
+ "(#{var}#{format_label(label)}#{formatted_attributes})"
58
70
  end
59
71
 
60
72
  def var_from_key_and_value(key, value, prefer = :var)
61
73
  case value
62
- when String, Symbol, Class, Module
63
- key
74
+ when String, Symbol, Class, Module, NilClass, Array then key
64
75
  when Hash
65
- if value.values.none? { |v| v.is_a?(Hash) }
66
- key if prefer == :var
67
- else
68
- key
69
- end
76
+ key if _use_key_for_var?(value, prefer)
70
77
  else
71
78
  fail ArgError, value
72
79
  end
@@ -74,22 +81,29 @@ module Neo4j
74
81
 
75
82
  def label_from_key_and_value(key, value, prefer = :var)
76
83
  case value
77
- when String, Symbol
78
- value
79
- when Class, Module
80
- defined?(value::CYPHER_LABEL) ? value::CYPHER_LABEL : value.name
84
+ when String, Symbol, Array, NilClass then value
85
+ when Class, Module then value.name
81
86
  when Hash
82
87
  if value.values.map(&:class) == [Hash]
83
88
  value.first.first
84
89
  else
85
- key if value.values.none? { |v| v.is_a?(Hash) } && prefer == :label
90
+ key if !_use_key_for_var?(value, prefer)
86
91
  end
87
92
  else
88
93
  fail ArgError, value
89
94
  end
90
95
  end
91
96
 
92
- def attributes_from_key_and_value(key, value)
97
+ def _use_key_for_var?(value, prefer)
98
+ _nested_value_hash?(value) || prefer == :var
99
+ end
100
+
101
+ def _nested_value_hash?(value)
102
+ value.values.any? { |v| v.is_a?(Hash) }
103
+ end
104
+
105
+
106
+ def attributes_from_key_and_value(_key, value)
93
107
  return nil unless value.is_a?(Hash)
94
108
 
95
109
  if value.values.map(&:class) == [Hash]
@@ -100,28 +114,41 @@ module Neo4j
100
114
  end
101
115
 
102
116
  class << self
103
- attr_reader :keyword
117
+ def keyword
118
+ self::KEYWORD
119
+ end
120
+
121
+ def keyword_downcase
122
+ keyword.downcase
123
+ end
104
124
 
105
125
  def from_args(args, options = {})
106
- args.flatten.map do |arg|
107
- new(arg, options) if !arg.respond_to?(:empty?) || !arg.empty?
108
- end.compact
126
+ args.flatten!
127
+ args.map { |arg| from_arg(arg, options) }.tap(&:compact!)
128
+ end
129
+
130
+ def from_arg(arg, options = {})
131
+ new(arg, options) if !arg.respond_to?(:empty?) || !arg.empty?
109
132
  end
110
133
 
111
134
  def to_cypher(clauses)
135
+ @question_mark_param_index = 1
136
+
112
137
  string = clause_string(clauses)
113
138
  string.strip!
114
139
 
115
- "#{@keyword} #{string}" if string.size > 0
140
+ "#{keyword} #{string}" if string.size > 0
116
141
  end
117
142
  end
118
143
 
119
144
  private
120
145
 
121
146
  def key_value_string(key, value, previous_keys = [], force_equals = false)
122
- param = (previous_keys << key).join('_')
123
- param.gsub!(/[^a-z0-9]+/i, '_')
147
+ param = (previous_keys << key).join(UNDERSCORE)
148
+ param.tr_s!('^a-zA-Z0-9', UNDERSCORE)
124
149
  param.gsub!(/^_+|_+$/, '')
150
+
151
+ value = value.first if value.is_a?(Array) && value.size == 1
125
152
  @params[param.to_sym] = value
126
153
 
127
154
  if !value.is_a?(Array) || force_equals
@@ -131,34 +158,39 @@ module Neo4j
131
158
  end
132
159
  end
133
160
 
134
- def format_label(label_string)
135
- label_string = label_string.to_s
136
- label_string.strip!
137
- if !label_string.empty? && label_string[0] != ':'
138
- label_string = "`#{label_string}`" unless label_string.match(' ')
139
- label_string = ":#{label_string}"
161
+ def format_label(label_arg)
162
+ if label_arg.is_a?(Array)
163
+ return label_arg.map { |arg| format_label(arg) }.join
140
164
  end
141
- label_string
165
+
166
+ label_arg = label_arg.to_s
167
+ label_arg.strip!
168
+ if !label_arg.empty? && label_arg[0] != ':'
169
+ label_arg = "`#{label_arg}`" unless label_arg.match(' ')
170
+ label_arg = ":#{label_arg}"
171
+ end
172
+ label_arg
142
173
  end
143
174
 
144
- def attributes_string(attributes)
175
+ def attributes_string(attributes, prefix = '')
145
176
  return '' if not attributes
146
177
 
147
178
  attributes_string = attributes.map do |key, value|
148
- v = if value.nil?
149
- 'null'
150
- else
151
- value.to_s.match(/^{.+}$/) ? value : value.inspect
152
- end
153
- "#{key}: #{v}"
154
- end.join(', ')
179
+ if value.to_s.match(/^{.+}$/)
180
+ "#{key}: #{value}"
181
+ else
182
+ param_key = "#{prefix}#{key}".gsub('::', '_')
183
+ @params[param_key.to_sym] = value
184
+ "#{key}: {#{param_key}}"
185
+ end
186
+ end.join(Clause::COMMA_SPACE)
155
187
 
156
188
  " {#{attributes_string}}"
157
189
  end
158
190
  end
159
191
 
160
192
  class StartClause < Clause
161
- @keyword = 'START'
193
+ KEYWORD = 'START'
162
194
 
163
195
  def from_symbol(value)
164
196
  from_string(value.to_s)
@@ -175,33 +207,20 @@ module Neo4j
175
207
 
176
208
  class << self
177
209
  def clause_string(clauses)
178
- clauses.map!(&:value).join(', ')
210
+ clauses.map!(&:value).join(Clause::COMMA_SPACE)
179
211
  end
180
212
  end
181
213
  end
182
214
 
183
215
  class WhereClause < Clause
184
- @keyword = 'WHERE'
216
+ KEYWORD = 'WHERE'
185
217
 
186
218
  def from_key_and_value(key, value, previous_keys = [])
187
219
  case value
188
- when Hash
189
- value.map do |k, v|
190
- if k.to_sym == :neo_id
191
- clause_id = "neo_id_#{v}"
192
- @params[clause_id] = v.to_i
193
- "ID(#{key}) = {#{clause_id}}"
194
- else
195
- "#{key}.#{from_key_and_value(k, v, previous_keys + [key])}"
196
- end
197
- end.join(' AND ')
198
- when NilClass
199
- "#{key} IS NULL"
200
- when Regexp
201
- pattern = (value.casefold? ? '(?i)' : '') + value.source
202
- "#{key} =~ #{escape_value(pattern.gsub(/\\/, '\\\\\\'))}"
203
- when Array
204
- key_value_string(key, value, previous_keys)
220
+ when Hash then hash_key_value_string(key, value, previous_keys)
221
+ when NilClass then "#{key} IS NULL"
222
+ when Regexp then regexp_key_value_string(key, value)
223
+ when Array then key_value_string(key, value, previous_keys)
205
224
  else
206
225
  key_value_string(key, value, previous_keys)
207
226
  end
@@ -209,14 +228,62 @@ module Neo4j
209
228
 
210
229
  class << self
211
230
  def clause_string(clauses)
212
- clauses.map(&:value).flatten.map {|value| "(#{value})" }.join(' AND ')
231
+ clauses.map!(&:value).tap(&:flatten!).map! { |value| "(#{value})" }.join(Clause::AND)
232
+ end
233
+ end
234
+
235
+ private
236
+
237
+ def hash_key_value_string(key, value, previous_keys)
238
+ value.map do |k, v|
239
+ if k.to_sym == :neo_id
240
+ v = Array(v).map { |item| (item.respond_to?(:neo_id) ? item.neo_id : item).to_i }
241
+ key_value_string("ID(#{key})", v)
242
+ else
243
+ "#{key}.#{from_key_and_value(k, v, previous_keys + [key])}"
244
+ end
245
+ end.join(AND)
246
+ end
247
+
248
+ def regexp_key_value_string(key, value)
249
+ pattern = (value.casefold? ? '(?i)' : '') + value.source
250
+ "#{key} =~ #{escape_value(pattern.gsub(/\\/, '\\\\\\'))}"
251
+ end
252
+
253
+ class << self
254
+ ARG_HAS_QUESTION_MARK_REGEX = /(^|\s)\?(\s|$)/
255
+
256
+ def from_args(args, options = {})
257
+ query_string, params = args
258
+
259
+ if query_string.is_a?(String) && (query_string.match(ARG_HAS_QUESTION_MARK_REGEX) || params.is_a?(Hash))
260
+ if !params.is_a?(Hash)
261
+ question_mark_params_param = self.question_mark_params_param
262
+ query_string.gsub!(ARG_HAS_QUESTION_MARK_REGEX, "\\1{#{question_mark_params_param}}\\2")
263
+ params = {question_mark_params_param.to_sym => params}
264
+ end
265
+
266
+ clause = from_arg(query_string, options).tap do |clause|
267
+ clause.params.merge!(params)
268
+ end
269
+
270
+ [clause]
271
+ else
272
+ super
273
+ end
274
+ end
275
+
276
+ def question_mark_params_param
277
+ result = "question_mark_param#{@question_mark_param_index}"
278
+ @question_mark_param_index += 1
279
+ result
213
280
  end
214
281
  end
215
282
  end
216
283
 
217
284
 
218
285
  class MatchClause < Clause
219
- @keyword = 'MATCH'
286
+ KEYWORD = 'MATCH'
220
287
 
221
288
  def from_symbol(value)
222
289
  from_string(value.to_s)
@@ -228,17 +295,17 @@ module Neo4j
228
295
 
229
296
  class << self
230
297
  def clause_string(clauses)
231
- clauses.map!(&:value).join(', ')
298
+ clauses.map!(&:value).join(Clause::COMMA_SPACE)
232
299
  end
233
300
  end
234
301
  end
235
302
 
236
303
  class OptionalMatchClause < MatchClause
237
- @keyword = 'OPTIONAL MATCH'
304
+ KEYWORD = 'OPTIONAL MATCH'
238
305
  end
239
306
 
240
307
  class WithClause < Clause
241
- @keyword = 'WITH'
308
+ KEYWORD = 'WITH'
242
309
 
243
310
  def from_symbol(value)
244
311
  from_string(value.to_s)
@@ -250,23 +317,23 @@ module Neo4j
250
317
 
251
318
  class << self
252
319
  def clause_string(clauses)
253
- clauses.map!(&:value).join(', ')
320
+ clauses.map!(&:value).join(Clause::COMMA_SPACE)
254
321
  end
255
322
  end
256
323
  end
257
324
 
258
325
  class UsingClause < Clause
259
- @keyword = 'USING'
326
+ KEYWORD = 'USING'
260
327
 
261
328
  class << self
262
329
  def clause_string(clauses)
263
- clauses.map!(&:value).join(" #{@keyword} ")
330
+ clauses.map!(&:value).join(" #{keyword} ")
264
331
  end
265
332
  end
266
333
  end
267
334
 
268
335
  class CreateClause < Clause
269
- @keyword = 'CREATE'
336
+ KEYWORD = 'CREATE'
270
337
 
271
338
  def from_string(value)
272
339
  value
@@ -298,15 +365,15 @@ module Neo4j
298
365
  end
299
366
 
300
367
  class CreateUniqueClause < CreateClause
301
- @keyword = 'CREATE UNIQUE'
368
+ KEYWORD = 'CREATE UNIQUE'
302
369
  end
303
370
 
304
371
  class MergeClause < CreateClause
305
- @keyword = 'MERGE'
372
+ KEYWORD = 'MERGE'
306
373
  end
307
374
 
308
375
  class DeleteClause < Clause
309
- @keyword = 'DELETE'
376
+ KEYWORD = 'DELETE'
310
377
 
311
378
  def from_symbol(value)
312
379
  from_string(value.to_s)
@@ -314,13 +381,13 @@ module Neo4j
314
381
 
315
382
  class << self
316
383
  def clause_string(clauses)
317
- clauses.map!(&:value).join(', ')
384
+ clauses.map!(&:value).join(Clause::COMMA_SPACE)
318
385
  end
319
386
  end
320
387
  end
321
388
 
322
389
  class OrderClause < Clause
323
- @keyword = 'ORDER BY'
390
+ KEYWORD = 'ORDER BY'
324
391
 
325
392
  def from_symbol(value)
326
393
  from_string(value.to_s)
@@ -332,38 +399,32 @@ module Neo4j
332
399
  "#{key}.#{value}"
333
400
  when Array
334
401
  value.map do |v|
335
- if v.is_a?(Hash)
336
- from_key_and_value(key, v)
337
- else
338
- "#{key}.#{v}"
339
- end
402
+ v.is_a?(Hash) ? from_key_and_value(key, v) : "#{key}.#{v}"
340
403
  end
341
404
  when Hash
342
- value.map do |k, v|
343
- "#{key}.#{k} #{v.upcase}"
344
- end
405
+ value.map { |k, v| "#{key}.#{k} #{v.upcase}" }
345
406
  end
346
407
  end
347
408
 
348
409
  class << self
349
410
  def clause_string(clauses)
350
- clauses.map!(&:value).join(', ')
411
+ clauses.map!(&:value).join(Clause::COMMA_SPACE)
351
412
  end
352
413
  end
353
414
  end
354
415
 
355
416
  class LimitClause < Clause
356
- @keyword = 'LIMIT'
417
+ KEYWORD = 'LIMIT'
357
418
 
358
419
  def from_string(value)
359
- clause_id = "#{self.class.keyword.downcase}_#{value}"
360
- @params[clause_id] = value.to_i
420
+ clause_id = "#{self.class.keyword_downcase}_#{value}"
421
+ @params[clause_id.to_sym] = value.to_i
361
422
  "{#{clause_id}}"
362
423
  end
363
424
 
364
425
  def from_integer(value)
365
- clause_id = "#{self.class.keyword.downcase}_#{value}"
366
- @params[clause_id] = value
426
+ clause_id = "#{self.class.keyword_downcase}_#{value}"
427
+ @params[clause_id.to_sym] = value
367
428
  "{#{clause_id}}"
368
429
  end
369
430
 
@@ -375,17 +436,17 @@ module Neo4j
375
436
  end
376
437
 
377
438
  class SkipClause < Clause
378
- @keyword = 'SKIP'
439
+ KEYWORD = 'SKIP'
379
440
 
380
441
  def from_string(value)
381
- clause_id = "#{self.class.keyword.downcase}_#{value}"
382
- @params[clause_id] = value.to_i
442
+ clause_id = "#{self.class.keyword_downcase}_#{value}"
443
+ @params[clause_id.to_sym] = value.to_i
383
444
  "{#{clause_id}}"
384
445
  end
385
446
 
386
447
  def from_integer(value)
387
- clause_id = "#{self.class.keyword.downcase}_#{value}"
388
- @params[clause_id] = value
448
+ clause_id = "#{self.class.keyword_downcase}_#{value}"
449
+ @params[clause_id.to_sym] = value
389
450
  "{#{clause_id}}"
390
451
  end
391
452
 
@@ -397,21 +458,20 @@ module Neo4j
397
458
  end
398
459
 
399
460
  class SetClause < Clause
400
- @keyword = 'SET'
461
+ KEYWORD = 'SET'
401
462
 
402
463
  def from_key_and_value(key, value)
403
464
  case value
404
- when String, Symbol
405
- "#{key} = #{value}"
465
+ when String, Symbol then "#{key}:`#{value}`"
406
466
  when Hash
407
467
  if @options[:set_props]
408
- attribute_string = value.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')
468
+ attribute_string = value.map { |k, v| "#{k}: #{v.inspect}" }.join(Clause::COMMA_SPACE)
409
469
  "#{key} = {#{attribute_string}}"
410
470
  else
411
- value.map do |k, v|
412
- key_value_string("#{key}.`#{k}`", v, ['setter'], true)
413
- end
471
+ value.map { |k, v| key_value_string("#{key}.`#{k}`", v, ['setter'], true) }
414
472
  end
473
+ when Array then value.map { |v| from_key_and_value(key, v) }
474
+ when NilClass then []
415
475
  else
416
476
  fail ArgError, value
417
477
  end
@@ -419,13 +479,13 @@ module Neo4j
419
479
 
420
480
  class << self
421
481
  def clause_string(clauses)
422
- clauses.map!(&:value).join(', ')
482
+ clauses.map!(&:value).join(Clause::COMMA_SPACE)
423
483
  end
424
484
  end
425
485
  end
426
486
 
427
487
  class OnCreateSetClause < SetClause
428
- @keyword = 'ON CREATE SET'
488
+ KEYWORD = 'ON CREATE SET'
429
489
 
430
490
  def initialize(*args)
431
491
  super
@@ -434,20 +494,24 @@ module Neo4j
434
494
  end
435
495
 
436
496
  class OnMatchSetClause < OnCreateSetClause
437
- @keyword = 'ON MATCH SET'
497
+ KEYWORD = 'ON MATCH SET'
438
498
  end
439
499
 
440
500
  class RemoveClause < Clause
441
- @keyword = 'REMOVE'
501
+ KEYWORD = 'REMOVE'
442
502
 
443
503
  def from_key_and_value(key, value)
444
504
  case value
445
505
  when /^:/
446
- "#{key}:#{value[1..-1]}"
506
+ "#{key}:`#{value[1..-1]}`"
447
507
  when String
448
508
  "#{key}.#{value}"
449
509
  when Symbol
450
- "#{key}:#{value}"
510
+ "#{key}:`#{value}`"
511
+ when Array
512
+ value.map do |v|
513
+ from_key_and_value(key, v)
514
+ end
451
515
  else
452
516
  fail ArgError, value
453
517
  end
@@ -455,13 +519,13 @@ module Neo4j
455
519
 
456
520
  class << self
457
521
  def clause_string(clauses)
458
- clauses.map!(&:value).join(', ')
522
+ clauses.map!(&:value).join(Clause::COMMA_SPACE)
459
523
  end
460
524
  end
461
525
  end
462
526
 
463
527
  class UnwindClause < Clause
464
- @keyword = 'UNWIND'
528
+ KEYWORD = 'UNWIND'
465
529
 
466
530
  def from_key_and_value(key, value)
467
531
  case value
@@ -482,7 +546,7 @@ module Neo4j
482
546
  end
483
547
 
484
548
  class ReturnClause < Clause
485
- @keyword = 'RETURN'
549
+ KEYWORD = 'RETURN'
486
550
 
487
551
  def from_symbol(value)
488
552
  from_string(value.to_s)
@@ -493,9 +557,13 @@ module Neo4j
493
557
  when Array
494
558
  value.map do |v|
495
559
  from_key_and_value(key, v)
496
- end.join(', ')
560
+ end.join(Clause::COMMA_SPACE)
497
561
  when String, Symbol
498
- "#{key}.#{value}"
562
+ if value.to_sym == :neo_id
563
+ "ID(#{key})"
564
+ else
565
+ "#{key}.#{value}"
566
+ end
499
567
  else
500
568
  fail ArgError, value
501
569
  end
@@ -503,7 +571,7 @@ module Neo4j
503
571
 
504
572
  class << self
505
573
  def clause_string(clauses)
506
- clauses.map!(&:value).join(', ')
574
+ clauses.map!(&:value).join(Clause::COMMA_SPACE)
507
575
  end
508
576
  end
509
577
  end
@@ -2,8 +2,7 @@ module Neo4j
2
2
  module Core
3
3
  module QueryFindInBatches
4
4
  def find_in_batches(node_var, prop_var, options = {})
5
- invalid_keys = options.keys.map(&:to_sym) - [:batch_size]
6
- fail ArgumentError, "Invalid keys: #{invalid_keys.join(', ')}" if not invalid_keys.empty?
5
+ validate_find_in_batches_options!(options)
7
6
 
8
7
  batch_size = options.delete(:batch_size) || 1000
9
8
 
@@ -13,15 +12,7 @@ module Neo4j
13
12
 
14
13
  while records.any?
15
14
  records_size = records.size
16
- primary_key_offset = begin
17
- records.last.send(node_var).send(prop_var)
18
- rescue NoMethodError
19
- begin
20
- records.last.send(node_var)[prop_var.to_sym]
21
- rescue NoMethodError
22
- records.last.send("#{node_var}.#{prop_var}") # In case we're explicitly returning it
23
- end
24
- end
15
+ primary_key_offset = primary_key_offset(records.last, node_var, prop_var)
25
16
 
26
17
  yield records
27
18
 
@@ -36,6 +27,23 @@ module Neo4j
36
27
  batch.each { |result| yield result }
37
28
  end
38
29
  end
30
+
31
+ private
32
+
33
+ def validate_find_in_batches_options!(options)
34
+ invalid_keys = options.keys.map(&:to_sym) - [:batch_size]
35
+ fail ArgumentError, "Invalid keys: #{invalid_keys.join(', ')}" if not invalid_keys.empty?
36
+ end
37
+
38
+ def primary_key_offset(last_record, node_var, prop_var)
39
+ last_record.send(node_var).send(prop_var)
40
+ rescue NoMethodError
41
+ begin
42
+ last_record.send(node_var)[prop_var.to_sym]
43
+ rescue NoMethodError
44
+ last_record.send("#{node_var}.#{prop_var}") # In case we're explicitly returning it
45
+ end
46
+ end
39
47
  end
40
48
  end
41
49
  end
@@ -1,5 +1,5 @@
1
1
  module Neo4j
2
2
  module Core
3
- VERSION = '4.0.7'
3
+ VERSION = '5.0.0.rc.1'
4
4
  end
5
5
  end
data/lib/neo4j-core.rb CHANGED
@@ -1,3 +1,6 @@
1
+ require 'ext/kernel'
2
+
3
+ require 'ostruct'
1
4
  require 'forwardable'
2
5
  require 'fileutils'
3
6
 
@@ -12,26 +12,31 @@ module Neo4j
12
12
  include Enumerable
13
13
 
14
14
  # @return the original result from the Neo4j Cypher Engine, once forward read only !
15
- attr_reader :source
15
+ attr_reader :source, :unwrapped
16
16
 
17
- def initialize(source, query)
17
+ def initialize(source, query, unwrapped = nil)
18
18
  @source = source
19
- @struct = Struct.new(*source.columns.to_a.map(&:to_sym))
19
+ @struct = Struct.new(*source.columns.to_a.map!(&:to_sym)) unless source.columns.empty?
20
20
  @unread = true
21
21
  @query = query
22
+ @unwrapped = unwrapped
22
23
  end
23
24
 
24
25
  def to_s
25
26
  @query
26
27
  end
27
28
 
29
+ def unwrapped?
30
+ !!unwrapped
31
+ end
32
+
28
33
  def inspect
29
34
  "Enumerable query: '#{@query}'"
30
35
  end
31
36
 
32
37
  # @return [Array<Symbol>] the columns in the query result
33
38
  def columns
34
- @source.columns.map(&:to_sym)
39
+ @source.columns.map!(&:to_sym)
35
40
  end
36
41
 
37
42
  def each
@@ -40,13 +45,23 @@ module Neo4j
40
45
  if block_given?
41
46
  @source.each do |row|
42
47
  yield(row.each_with_object(@struct.new) do |(column, value), result|
43
- result[column.to_sym] = (value.respond_to?(:wrapper) ? value.wrapper : value)
48
+ result[column.to_sym] = unwrap(value)
44
49
  end)
45
50
  end
46
51
  else
47
52
  Enumerator.new(self)
48
53
  end
49
54
  end
55
+
56
+ private
57
+
58
+ def unwrap(value)
59
+ if !value.nil? && value.respond_to?(:to_a)
60
+ value.map { |v| unwrap(v) }
61
+ else
62
+ (!value.respond_to?(:wrapper) || unwrapped?) ? value : value.wrapper
63
+ end
64
+ end
50
65
  end
51
66
  end
52
67
  end