mongodoc 0.0.0 → 0.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.
@@ -0,0 +1,485 @@
1
+ # This awesomeness is taken from Mongoid, Thanks Durran!
2
+
3
+ module MongoDoc #:nodoc:
4
+ # The +Criteria+ class is the core object needed in Mongoid to retrieve
5
+ # objects from the database. It is a DSL that essentially sets up the
6
+ # selector and options arguments that get passed on to a <tt>Mongo::Collection</tt>
7
+ # in the Ruby driver. Each method on the +Criteria+ returns self to they
8
+ # can be chained in order to create a readable criterion to be executed
9
+ # against the database.
10
+ #
11
+ # Example setup:
12
+ #
13
+ # <tt>criteria = Criteria.new</tt>
14
+ #
15
+ # <tt>criteria.select(:field => "value").only(:field).skip(20).limit(20)</tt>
16
+ #
17
+ # <tt>criteria.execute</tt>
18
+ class Criteria
19
+ SORT_REVERSALS = {
20
+ :asc => :desc,
21
+ :ascending => :descending,
22
+ :desc => :asc,
23
+ :descending => :ascending
24
+ }
25
+
26
+ include Enumerable
27
+
28
+ attr_reader :klass, :options, :selector
29
+
30
+ # Returns true if the supplied +Enumerable+ or +Criteria+ is equal to the results
31
+ # of this +Criteria+ or the criteria itself.
32
+ #
33
+ # This will force a database load when called if an enumerable is passed.
34
+ #
35
+ # Options:
36
+ #
37
+ # other: The other +Enumerable+ or +Criteria+ to compare to.
38
+ def ==(other)
39
+ case other
40
+ when Criteria
41
+ self.selector == other.selector && self.options == other.options
42
+ when Enumerable
43
+ @collection ||= execute
44
+ return (@collection == other)
45
+ else
46
+ return false
47
+ end
48
+ end
49
+
50
+ AGGREGATE_REDUCE = "function(obj, prev) { prev.count++; }"
51
+ # Aggregate the criteria. This will take the internally built selector and options
52
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
53
+ # collection itself will be retrieved from the class provided, and once the
54
+ # query has returned it will provided a grouping of keys with counts.
55
+ #
56
+ # Example:
57
+ #
58
+ # <tt>criteria.select(:field1).where(:field1 => "Title").aggregate(Person)</tt>
59
+ def aggregate(use_klass = nil)
60
+ aggregating_klass = use_klass ? use_klass : klass
61
+ aggregating_klass.collection.group(options[:fields], selector, { :count => 0 }, AGGREGATE_REDUCE)
62
+ end
63
+
64
+ # Adds a criterion to the +Criteria+ that specifies values that must all
65
+ # be matched in order to return results. Similar to an "in" clause but the
66
+ # underlying conditional logic is an "AND" and not an "OR". The MongoDB
67
+ # conditional operator that will be used is "$all".
68
+ #
69
+ # Options:
70
+ #
71
+ # selections: A +Hash+ where the key is the field name and the value is an
72
+ # +Array+ of values that must all match.
73
+ #
74
+ # Example:
75
+ #
76
+ # <tt>criteria.every(:field => ["value1", "value2"])</tt>
77
+ #
78
+ # <tt>criteria.every(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
79
+ #
80
+ # Returns: <tt>self</tt>
81
+ def every(selections = {})
82
+ selections.each { |key, value| selector[key] = { "$all" => value } }; self
83
+ end
84
+
85
+ # Get the count of matching documents in the database for the +Criteria+.
86
+ #
87
+ # Example:
88
+ #
89
+ # <tt>criteria.count</tt>
90
+ #
91
+ # Returns: <tt>Integer</tt>
92
+ def count
93
+ @count ||= klass.collection.find(selector, options.dup).count
94
+ end
95
+
96
+ # Iterate over each +Document+ in the results and pass each document to the
97
+ # block.
98
+ #
99
+ # Example:
100
+ #
101
+ # <tt>criteria.each { |doc| p doc }</tt>
102
+ def each(&block)
103
+ @collection ||= execute
104
+ if block_given?
105
+ @collection.each(&block)
106
+ else
107
+ self
108
+ end
109
+ end
110
+
111
+ # Adds a criterion to the +Criteria+ that specifies values that are not allowed
112
+ # to match any document in the database. The MongoDB conditional operator that
113
+ # will be used is "$ne".
114
+ #
115
+ # Options:
116
+ #
117
+ # excludes: A +Hash+ where the key is the field name and the value is a
118
+ # value that must not be equal to the corresponding field value in the database.
119
+ #
120
+ # Example:
121
+ #
122
+ # <tt>criteria.excludes(:field => "value1")</tt>
123
+ #
124
+ # <tt>criteria.excludes(:field1 => "value1", :field2 => "value1")</tt>
125
+ #
126
+ # Returns: <tt>self</tt>
127
+ def excludes(exclusions = {})
128
+ exclusions.each { |key, value| selector[key] = { "$ne" => value } }; self
129
+ end
130
+
131
+ # Adds a criterion to the +Criteria+ that specifies additional options
132
+ # to be passed to the Ruby driver, in the exact format for the driver.
133
+ #
134
+ # Options:
135
+ #
136
+ # extras: A +Hash+ that gets set to the driver options.
137
+ #
138
+ # Example:
139
+ #
140
+ # <tt>criteria.extras(:limit => 20, :skip => 40)</tt>
141
+ #
142
+ # Returns: <tt>self</tt>
143
+ def extras(extras)
144
+ options.merge!(extras)
145
+ filter_options
146
+ self
147
+ end
148
+
149
+ # Return the first result for the +Criteria+.
150
+ #
151
+ # Example:
152
+ #
153
+ # <tt>Criteria.select(:name).where(:name = "Chrissy").one</tt>
154
+ def one
155
+ klass.collection.find_one(selector, options.dup)
156
+ end
157
+ alias :first :one
158
+
159
+ GROUP_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
160
+ # Groups the criteria. This will take the internally built selector and options
161
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
162
+ # collection itself will be retrieved from the class provided, and once the
163
+ # query has returned it will provided a grouping of keys with objects.
164
+ #
165
+ # Example:
166
+ #
167
+ # <tt>criteria.select(:field1).where(:field1 => "Title").group(Person)</tt>
168
+ def group(use_klass = nil)
169
+ (use_klass || klass).collection.group(
170
+ options[:fields],
171
+ selector,
172
+ { :group => [] },
173
+ GROUP_REDUCE
174
+ ).collect {|docs| docs["group"] = MongoDoc::BSON.decode(docs["group"]); docs }
175
+ end
176
+
177
+ # Adds a criterion to the +Criteria+ that specifies values where any can
178
+ # be matched in order to return results. This is similar to an SQL "IN"
179
+ # clause. The MongoDB conditional operator that will be used is "$in".
180
+ #
181
+ # Options:
182
+ #
183
+ # inclusions: A +Hash+ where the key is the field name and the value is an
184
+ # +Array+ of values that any can match.
185
+ #
186
+ # Example:
187
+ #
188
+ # <tt>criteria.in(:field => ["value1", "value2"])</tt>
189
+ #
190
+ # <tt>criteria.in(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
191
+ #
192
+ # Returns: <tt>self</tt>
193
+ def in(inclusions = {})
194
+ inclusions.each { |key, value| selector[key] = { "$in" => value } }; self
195
+ end
196
+
197
+ # Adds a criterion to the +Criteria+ that specifies an id that must be matched.
198
+ #
199
+ # Options:
200
+ #
201
+ # object_id: A +String+ representation of a <tt>Mongo::ObjectID</tt>
202
+ #
203
+ # Example:
204
+ #
205
+ # <tt>criteria.id("4ab2bc4b8ad548971900005c")</tt>
206
+ #
207
+ # Returns: <tt>self</tt>
208
+ def id(object_id)
209
+ selector[:_id] = object_id; self
210
+ end
211
+
212
+ # Create the new +Criteria+ object. This will initialize the selector
213
+ # and options hashes, as well as the type of criteria.
214
+ #
215
+ # Options:
216
+ #
217
+ # type: One of :all, :first:, or :last
218
+ # klass: The class to execute on.
219
+ def initialize(klass)
220
+ @selector, @options, @klass = {}, {}, klass
221
+ end
222
+
223
+ # Return the last result for the +Criteria+. Essentially does a find_one on
224
+ # the collection with the sorting reversed. If no sorting parameters have
225
+ # been provided it will default to ids.
226
+ #
227
+ # Example:
228
+ #
229
+ # <tt>Criteria.select(:name).where(:name = "Chrissy").last</tt>
230
+ def last
231
+ opts = options.dup
232
+ sorting = opts[:sort]
233
+ sorting = [[:_id, :asc]] unless sorting
234
+ opts[:sort] = sorting.collect { |option| [ option.first, Criteria.invert(option.last) ] }
235
+ klass.collection.find_one(selector, opts)
236
+ end
237
+
238
+ # Adds a criterion to the +Criteria+ that specifies the maximum number of
239
+ # results to return. This is mostly used in conjunction with <tt>skip()</tt>
240
+ # to handle paginated results.
241
+ #
242
+ # Options:
243
+ #
244
+ # value: An +Integer+ specifying the max number of results. Defaults to 20.
245
+ #
246
+ # Example:
247
+ #
248
+ # <tt>criteria.limit(100)</tt>
249
+ #
250
+ # Returns: <tt>self</tt>
251
+ def limit(value = 20)
252
+ options[:limit] = value; self
253
+ end
254
+
255
+ # Merges another object into this +Criteria+. The other object may be a
256
+ # +Criteria+ or a +Hash+. This is used to combine multiple scopes together,
257
+ # where a chained scope situation may be desired.
258
+ #
259
+ # Options:
260
+ #
261
+ # other: The +Criteria+ or +Hash+ to merge with.
262
+ #
263
+ # Example:
264
+ #
265
+ # <tt>criteria.merge({ :conditions => { :title => "Sir" } })</tt>
266
+ def merge(other)
267
+ selector.update(other.selector)
268
+ options.update(other.options)
269
+ end
270
+
271
+ # Used for chaining +Criteria+ scopes together in the for of class methods
272
+ # on the +Document+ the criteria is for.
273
+ #
274
+ # Options:
275
+ #
276
+ # name: The name of the class method on the +Document+ to chain.
277
+ # args: The arguments passed to the method.
278
+ #
279
+ # Example:
280
+ #
281
+ # class Person < Mongoid::Document
282
+ # field :title
283
+ # field :terms, :type => Boolean, :default => false
284
+ #
285
+ # class << self
286
+ # def knights
287
+ # all(:conditions => { :title => "Sir" })
288
+ # end
289
+ #
290
+ # def accepted
291
+ # all(:conditions => { :terms => true })
292
+ # end
293
+ # end
294
+ # end
295
+ #
296
+ # Person.accepted.knights #returns a merged criteria of the 2 scopes.
297
+ #
298
+ # Returns: <tt>Criteria</tt>
299
+ def method_missing(name, *args)
300
+ new_scope = klass.send(name)
301
+ new_scope.merge(self)
302
+ new_scope
303
+ end
304
+
305
+ # Adds a criterion to the +Criteria+ that specifies values where none
306
+ # should match in order to return results. This is similar to an SQL "NOT IN"
307
+ # clause. The MongoDB conditional operator that will be used is "$nin".
308
+ #
309
+ # Options:
310
+ #
311
+ # exclusions: A +Hash+ where the key is the field name and the value is an
312
+ # +Array+ of values that none can match.
313
+ #
314
+ # Example:
315
+ #
316
+ # <tt>criteria.not_in(:field => ["value1", "value2"])</tt>
317
+ #
318
+ # <tt>criteria.not_in(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
319
+ #
320
+ # Returns: <tt>self</tt>
321
+ def not_in(exclusions)
322
+ exclusions.each { |key, value| selector[key] = { "$nin" => value } }; self
323
+ end
324
+
325
+ # Returns the offset option. If a per_page option is in the list then it
326
+ # will replace it with a skip parameter and return the same value. Defaults
327
+ # to 20 if nothing was provided.
328
+ def offset
329
+ options[:skip]
330
+ end
331
+
332
+ # Adds a criterion to the +Criteria+ that specifies the sort order of
333
+ # the returned documents in the database. Similar to a SQL "ORDER BY".
334
+ #
335
+ # Options:
336
+ #
337
+ # params: An +Array+ of [field, direction] sorting pairs.
338
+ #
339
+ # Example:
340
+ #
341
+ # <tt>criteria.order_by([[:field1, :asc], [:field2, :desc]])</tt>
342
+ #
343
+ # Returns: <tt>self</tt>
344
+ def order_by(params = [])
345
+ options[:sort] = params; self
346
+ end
347
+
348
+ # Either returns the page option and removes it from the options, or
349
+ # returns a default value of 1.
350
+ def page
351
+ if options[:skip] && options[:limit]
352
+ (options[:skip].to_i + options[:limit].to_i) / options[:limit].to_i
353
+ else
354
+ 1
355
+ end
356
+ end
357
+
358
+ # Executes the +Criteria+ and paginates the results.
359
+ #
360
+ # Example:
361
+ #
362
+ # <tt>criteria.paginate</tt>
363
+ def paginate
364
+ @collection ||= execute
365
+ WillPaginate::Collection.create(page, per_page, count) do |pager|
366
+ pager.replace(@collection.to_a)
367
+ end
368
+ end
369
+
370
+ # Returns the number of results per page or the default of 20.
371
+ def per_page
372
+ (options[:limit] || 20).to_i
373
+ end
374
+
375
+ # Adds a criterion to the +Criteria+ that specifies the fields that will
376
+ # get returned from the Document. Used mainly for list views that do not
377
+ # require all fields to be present. This is similar to SQL "SELECT" values.
378
+ #
379
+ # Options:
380
+ #
381
+ # args: A list of field names to retrict the returned fields to.
382
+ #
383
+ # Example:
384
+ #
385
+ # <tt>criteria.select(:field1, :field2, :field3)</tt>
386
+ #
387
+ # Returns: <tt>self</tt>
388
+ def select(*args)
389
+ options[:fields] = args.flatten if args.any?; self
390
+ end
391
+
392
+ # Adds a criterion to the +Criteria+ that specifies how many results to skip
393
+ # when returning Documents. This is mostly used in conjunction with
394
+ # <tt>limit()</tt> to handle paginated results, and is similar to the
395
+ # traditional "offset" parameter.
396
+ #
397
+ # Options:
398
+ #
399
+ # value: An +Integer+ specifying the number of results to skip. Defaults to 0.
400
+ #
401
+ # Example:
402
+ #
403
+ # <tt>criteria.skip(20)</tt>
404
+ #
405
+ # Returns: <tt>self</tt>
406
+ def skip(value = 0)
407
+ options[:skip] = value; self
408
+ end
409
+
410
+ # Translate the supplied arguments into a +Criteria+ object.
411
+ #
412
+ # If the passed in args is a single +String+, then it will
413
+ # construct an id +Criteria+ from it.
414
+ #
415
+ # If the passed in args are a type and a hash, then it will construct
416
+ # the +Criteria+ with the proper selector, options, and type.
417
+ #
418
+ # Options:
419
+ #
420
+ # args: either a +String+ or a +Symbol+, +Hash combination.
421
+ #
422
+ # Example:
423
+ #
424
+ # <tt>Criteria.translate(Person, "4ab2bc4b8ad548971900005c")</tt>
425
+ #
426
+ # <tt>Criteria.translate(Person, :conditions => { :field => "value"}, :limit => 20)</tt>
427
+ #
428
+ # Returns a new +Criteria+ object.
429
+ def self.translate(klass, params = {})
430
+ return new(klass).id(params).one if params.is_a?(String)
431
+ return new(klass).where(params.delete(:conditions)).extras(params)
432
+ end
433
+
434
+ # Adds a criterion to the +Criteria+ that specifies values that must
435
+ # be matched in order to return results. This is similar to a SQL "WHERE"
436
+ # clause. This is the actual selector that will be provided to MongoDB,
437
+ # similar to the Javascript object that is used when performing a find()
438
+ # in the MongoDB console.
439
+ #
440
+ # Options:
441
+ #
442
+ # selectior: A +Hash+ that must match the attributes of the +Document+.
443
+ #
444
+ # Example:
445
+ #
446
+ # <tt>criteria.where(:field1 => "value1", :field2 => 15)</tt>
447
+ #
448
+ # Returns: <tt>self</tt>
449
+ def where(add_selector = {})
450
+ selector.merge!(add_selector); self
451
+ end
452
+
453
+ protected
454
+ # Execute the criteria. This will take the internally built selector and options
455
+ # and pass them on to the Ruby driver's +find()+ method on the collection. The
456
+ # collection itself will be retrieved from the class provided.
457
+ #
458
+ # Returns either a cursor or an empty array.
459
+ def execute
460
+ cursor = @klass.collection.find(selector, options.dup)
461
+ if cursor
462
+ @count = cursor.count
463
+ cursor
464
+ else
465
+ []
466
+ end
467
+ end
468
+
469
+ # Filters the unused options out of the options +Hash+. Currently this
470
+ # takes into account the "page" and "per_page" options that would be passed
471
+ # in if using will_paginate.
472
+ def filter_options
473
+ page_num = options.delete(:page)
474
+ per_page_num = options.delete(:per_page)
475
+ if (page_num || per_page_num)
476
+ options[:limit] = (per_page_num || 20).to_i
477
+ options[:skip] = (page_num || 1).to_i * options[:limit] - options[:limit]
478
+ end
479
+ end
480
+
481
+ def self.invert(order)
482
+ SORT_REVERSALS[order]
483
+ end
484
+ end
485
+ end
@@ -0,0 +1,24 @@
1
+ module MongoDoc
2
+ class Cursor
3
+ attr_accessor :_cursor
4
+ delegate :close, :closed?, :count, :explain, :limit, :query_options_hash, :query_opts, :skip, :sort, :to => :_cursor
5
+
6
+ def initialize(cursor)
7
+ self._cursor = cursor
8
+ end
9
+
10
+ def each
11
+ _cursor.each do |next_object|
12
+ yield MongoDoc::BSON.decode(next_object)
13
+ end
14
+ end
15
+
16
+ def next_object
17
+ MongoDoc::BSON.decode(_cursor.next_object)
18
+ end
19
+
20
+ def to_a
21
+ MongoDoc::BSON.decode(_cursor.to_a)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,138 @@
1
+ require 'mongodoc/bson'
2
+ require 'mongodoc/query'
3
+ require 'mongodoc/attributes'
4
+ require 'mongodoc/criteria'
5
+
6
+ module MongoDoc
7
+ class DocumentInvalidError < RuntimeError; end
8
+ class NotADocumentError < RuntimeError; end
9
+
10
+ class Document
11
+ extend MongoDoc::Attributes
12
+ include Validatable
13
+
14
+ attr_accessor :_id
15
+ alias :id :_id
16
+ alias :to_param :_id
17
+
18
+ def initialize(attrs = {})
19
+ self.attributes = attrs
20
+ end
21
+
22
+ def ==(other)
23
+ return false unless self.class === other
24
+ self.class._attributes.all? {|var| self.send(var) == other.send(var)}
25
+ end
26
+
27
+ def attributes=(attrs)
28
+ attrs.each do |key, value|
29
+ send("#{key}=", value)
30
+ end
31
+ end
32
+
33
+ def new_record?
34
+ _id.nil?
35
+ end
36
+
37
+ def save(validate = true)
38
+ return _root.save(validate) if _root
39
+ return _save(false) unless validate and not valid?
40
+ false
41
+ end
42
+
43
+ def save!
44
+ return _root.save! if _root
45
+ raise DocumentInvalidError unless valid?
46
+ _save(true)
47
+ end
48
+
49
+ def to_bson(*args)
50
+ {MongoDoc::BSON::CLASS_KEY => self.class.name}.tap do |bson_hash|
51
+ bson_hash['_id'] = _id unless _id.nil?
52
+ self.class._attributes.each do |name|
53
+ bson_hash[name.to_s] = send(name).to_bson(args)
54
+ end
55
+ end
56
+ end
57
+
58
+ def update_attributes(attrs)
59
+ self.attributes = attrs
60
+ return _propose_update_attributes(self, path_to_root(attrs), false) if valid?
61
+ false
62
+ end
63
+
64
+ def update_attributes!(attrs)
65
+ self.attributes = attrs
66
+ raise DocumentInvalidError unless valid?
67
+ _propose_update_attributes(self, path_to_root(attrs), true)
68
+ end
69
+
70
+ class << self
71
+ def bson_create(bson_hash, options = {})
72
+ new.tap do |obj|
73
+ bson_hash.each do |name, value|
74
+ obj.send("#{name}=", MongoDoc::BSON.decode(value, options))
75
+ end
76
+ end
77
+ end
78
+
79
+ def collection
80
+ @collection ||= MongoDoc::Collection.new(collection_name)
81
+ end
82
+
83
+ def collection_name
84
+ self.to_s.tableize.gsub('/', '.')
85
+ end
86
+
87
+ def count
88
+ collection.count
89
+ end
90
+
91
+ def create(attrs = {})
92
+ instance = new(attrs)
93
+ _create(instance, false) if instance.valid?
94
+ instance
95
+ end
96
+
97
+ def create!(attrs = {})
98
+ instance = new(attrs)
99
+ raise MongoDoc::DocumentInvalidError unless instance.valid?
100
+ _create(instance, true)
101
+ instance
102
+ end
103
+
104
+ def criteria
105
+ Criteria.new(self)
106
+ end
107
+
108
+ def find_one(id)
109
+ MongoDoc::BSON.decode(collection.find_one(id))
110
+ end
111
+ end
112
+
113
+ protected
114
+
115
+ def _collection
116
+ self.class.collection
117
+ end
118
+
119
+ def _propose_update_attributes(src, attrs, safe)
120
+ return _parent.send(:_propose_update_attributes, src, attrs, safe) if _parent
121
+ _update_attributes(attrs, safe)
122
+ end
123
+
124
+ def _save(safe)
125
+ self._id = _collection.save(self, :safe => safe)
126
+ end
127
+
128
+ def _update_attributes(attrs, safe)
129
+ _collection.update({'_id' => self._id}, MongoDoc::Query.set_modifier(attrs), :safe => safe)
130
+ end
131
+
132
+ class << self
133
+ def _create(instance, safe)
134
+ instance._id = collection.insert(instance, :safe => safe)
135
+ end
136
+ end
137
+ end
138
+ end
@@ -1,36 +1,34 @@
1
1
  module MongoDoc
2
- module Document
3
- class ParentProxy
4
- attr_reader :assoc_name, :_parent
2
+ class ParentProxy
3
+ attr_reader :assoc_name, :_parent
5
4
 
6
- def initialize(parent, assoc_name)
7
- raise ArgumentError.new('ParentProxy requires a parent') if parent.nil?
8
- raise ArgumentError.new('ParentProxy require an association name') if assoc_name.blank?
9
- @_parent = parent
10
- @assoc_name = assoc_name
11
- end
5
+ def initialize(parent, assoc_name)
6
+ raise ArgumentError.new('ParentProxy requires a parent') if parent.nil?
7
+ raise ArgumentError.new('ParentProxy require an association name') if assoc_name.blank?
8
+ @_parent = parent
9
+ @assoc_name = assoc_name
10
+ end
12
11
 
13
- def path_to_root(attrs)
14
- assoc_attrs = attrs.inject({}) do |assoc_attrs, (key, value)|
15
- assoc_attrs["#{assoc_name}.#{key}"] = value
16
- assoc_attrs
17
- end
18
- _parent.path_to_root(assoc_attrs)
12
+ def path_to_root(attrs)
13
+ assoc_attrs = attrs.inject({}) do |assoc_attrs, (key, value)|
14
+ assoc_attrs["#{assoc_name}.#{key}"] = value
15
+ assoc_attrs
19
16
  end
17
+ _parent.path_to_root(assoc_attrs)
18
+ end
20
19
 
21
- private
20
+ private
22
21
 
23
- def method_missing(method, *args)
24
- unless @_parent.respond_to?(method)
25
- message = "undefined method `#{method.to_s}' for proxied \"#{@_parent}\":#{@_parent.class.to_s}"
26
- raise NoMethodError, message
27
- end
22
+ def method_missing(method, *args)
23
+ unless @_parent.respond_to?(method)
24
+ message = "undefined method `#{method.to_s}' for proxied \"#{@_parent}\":#{@_parent.class.to_s}"
25
+ raise NoMethodError, message
26
+ end
28
27
 
29
- if block_given?
30
- @_parent.send(method, *args) { |*block_args| yield(*block_args) }
31
- else
32
- @_parent.send(method, *args)
33
- end
28
+ if block_given?
29
+ @_parent.send(method, *args) { |*block_args| yield(*block_args) }
30
+ else
31
+ @_parent.send(method, *args)
34
32
  end
35
33
  end
36
34
  end