draper 1.0.0.beta6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.travis.yml +6 -0
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +20 -0
  4. data/Gemfile +11 -0
  5. data/README.md +14 -17
  6. data/Rakefile +5 -3
  7. data/draper.gemspec +2 -2
  8. data/lib/draper.rb +2 -1
  9. data/lib/draper/automatic_delegation.rb +50 -0
  10. data/lib/draper/collection_decorator.rb +26 -7
  11. data/lib/draper/decoratable.rb +71 -32
  12. data/lib/draper/decorated_association.rb +11 -7
  13. data/lib/draper/decorator.rb +114 -148
  14. data/lib/draper/delegation.rb +13 -0
  15. data/lib/draper/finders.rb +9 -6
  16. data/lib/draper/helper_proxy.rb +4 -3
  17. data/lib/draper/lazy_helpers.rb +10 -6
  18. data/lib/draper/railtie.rb +5 -4
  19. data/lib/draper/tasks/test.rake +22 -0
  20. data/lib/draper/test/devise_helper.rb +34 -0
  21. data/lib/draper/test/minitest_integration.rb +2 -3
  22. data/lib/draper/test/rspec_integration.rb +4 -59
  23. data/lib/draper/test_case.rb +33 -0
  24. data/lib/draper/version.rb +1 -1
  25. data/lib/draper/view_helpers.rb +4 -3
  26. data/lib/generators/decorator/templates/decorator.rb +7 -25
  27. data/lib/generators/mini_test/decorator_generator.rb +20 -0
  28. data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
  29. data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
  30. data/lib/generators/test_unit/templates/decorator_test.rb +1 -1
  31. data/spec/draper/collection_decorator_spec.rb +25 -3
  32. data/spec/draper/decorated_association_spec.rb +18 -7
  33. data/spec/draper/decorator_spec.rb +125 -165
  34. data/spec/draper/finders_spec.rb +0 -13
  35. data/spec/dummy/app/controllers/localized_urls.rb +1 -1
  36. data/spec/dummy/app/controllers/posts_controller.rb +3 -9
  37. data/spec/dummy/app/decorators/post_decorator.rb +4 -1
  38. data/spec/dummy/config/application.rb +3 -3
  39. data/spec/dummy/config/environments/development.rb +4 -4
  40. data/spec/dummy/config/environments/test.rb +2 -2
  41. data/spec/dummy/lib/tasks/test.rake +10 -0
  42. data/spec/dummy/mini_test/mini_test_integration_test.rb +46 -0
  43. data/spec/dummy/spec/decorators/post_decorator_spec.rb +2 -2
  44. data/spec/dummy/spec/decorators/rspec_integration_spec.rb +19 -0
  45. data/spec/dummy/spec/mailers/post_mailer_spec.rb +2 -2
  46. data/spec/dummy/spec/spec_helper.rb +0 -1
  47. data/spec/generators/decorator/decorator_generator_spec.rb +43 -2
  48. data/spec/integration/integration_spec.rb +2 -2
  49. data/spec/spec_helper.rb +17 -21
  50. data/spec/support/active_record.rb +0 -13
  51. data/spec/support/dummy_app.rb +4 -3
  52. metadata +26 -23
  53. data/lib/draper/security.rb +0 -48
  54. data/lib/draper/tasks/tu.rake +0 -5
  55. data/lib/draper/test/test_unit_integration.rb +0 -18
  56. data/spec/draper/security_spec.rb +0 -158
  57. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
  58. data/spec/dummy/lib/tasks/spec.rake +0 -5
  59. data/spec/minitest-rails/spec_type_spec.rb +0 -63
@@ -4,6 +4,12 @@ rvm:
4
4
  - rbx-19mode
5
5
  - jruby-19mode
6
6
  - ruby-head
7
+ env:
8
+ - "RAILS_VERSION=3.2"
9
+ - "RAILS_VERSION=3.1"
10
+ - "RAILS_VERSION=3.0"
11
+ - "RAILS_VERSION=master"
7
12
  matrix:
8
13
  allow_failures:
9
14
  - rvm: ruby-head
15
+ - env: "RAILS_VERSION=master"
data/.yardopts CHANGED
@@ -1 +1 @@
1
- yardoc 'lib/draper/**/*.rb' -m markdown
1
+ yardoc 'lib/draper/**/*.rb' -m markdown --no-private
@@ -1,5 +1,25 @@
1
1
  # Draper Changelog
2
2
 
3
+ ## 1.0.0
4
+
5
+ * Infer collection decorators. [https://github.com/drapergem/draper/commit/e8253df7dc6c90a542444c0f4ef289909fce4f90](https://github.com/drapergem/draper/commit/e8253df7dc6c90a542444c0f4ef289909fce4f90)
6
+
7
+ * Prevent calls to `scoped` on decorated associations. [https://github.com/drapergem/draper/commit/5dcc6c31ecf408753158d15fed9fb23fbfdc3734](https://github.com/drapergem/draper/commit/5dcc6c31ecf408753158d15fed9fb23fbfdc3734)
8
+
9
+ * Add `helper` method to tests. [https://github.com/drapergem/draper/commit/551961e72ee92355bc9c848bedfcc573856d12b0](https://github.com/drapergem/draper/commit/551961e72ee92355bc9c848bedfcc573856d12b0)
10
+
11
+ * Inherit method security. [https://github.com/drapergem/draper/commit/1865ed3e3b2b34853689a60b59b8ce9145674d1d](https://github.com/drapergem/draper/commit/1865ed3e3b2b34853689a60b59b8ce9145674d1d)
12
+
13
+ * Test against all versions of Rails 3. [https://github.com/drapergem/draper/commit/1865ed3e3b2b34853689a60b59b8ce9145674d1d](https://github.com/drapergem/draper/commit/1865ed3e3b2b34853689a60b59b8ce9145674d1d)
14
+
15
+ * Pretend to be `instance_of?(source.class)` [https://github.com/drapergem/draper/commit/30d209f990847e84b221ac798e84b976f5775cc0](https://github.com/drapergem/draper/commit/30d209f990847e84b221ac798e84b976f5775cc0)
16
+
17
+ * Remove security from `Decorator`. Do manual delegation with `:delegate`. [https://github.com/drapergem/draper/commit/c6f8aaa2b2bd4679738050aede2503aa8e9db130](https://github.com/drapergem/draper/commit/c6f8aaa2b2bd4679738050aede2503aa8e9db130)
18
+
19
+ * Add generators for MiniTest. [https://github.com/drapergem/draper/commit/1fac02b65b15e32f06e8292cb858c97cb1c1da2c](https://github.com/drapergem/draper/commit/1fac02b65b15e32f06e8292cb858c97cb1c1da2c)
20
+
21
+ * Test against edge rails. [https://github.com/drapergem/draper/commit/e9b71e3cf55a800b48c083ff257a7c1cbe1b601b](https://github.com/drapergem/draper/commit/e9b71e3cf55a800b48c083ff257a7c1cbe1b601b)
22
+
3
23
  ## 1.0.0.beta6
4
24
 
5
25
  * Fix up README to include changes made. [https://github.com/drapergem/draper/commit/5e6e4d11b1e0c07c12b6b1e87053bc3f50ef2ab6](https://github.com/drapergem/draper/commit/5e6e4d11b1e0c07c12b6b1e87053bc3f50ef2ab6)
data/Gemfile CHANGED
@@ -10,3 +10,14 @@ platforms :jruby do
10
10
  gem "minitest", ">= 3.0"
11
11
  gem "activerecord-jdbcsqlite3-adapter", "~> 1.2.2.1"
12
12
  end
13
+
14
+ case ENV["RAILS_VERSION"]
15
+ when "master"
16
+ gem "rails", github: "rails/rails"
17
+ when "3.2", nil
18
+ gem "rails", "~> 3.2.0"
19
+ when "3.1"
20
+ gem "rails", "~> 3.1.0"
21
+ when "3.0"
22
+ gem "rails", "~> 3.0.0"
23
+ end
data/README.md CHANGED
@@ -35,6 +35,8 @@ could be better written as:
35
35
  ```ruby
36
36
  # app/decorators/article_decorator.rb
37
37
  class ArticleDecorator < Draper::Decorator
38
+ delegate_all
39
+
38
40
  def publication_status
39
41
  if published?
40
42
  "Published at #{published_at}"
@@ -49,7 +51,7 @@ class ArticleDecorator < Draper::Decorator
49
51
  end
50
52
  ```
51
53
 
52
- Notice that the `published?` method can be called even though `ArticleDecorator` doesn't define it - the decorator delegates methods to the source model. However, we can override methods like `published_at` to add presentation-specific formatting, in which case we access the underlying model using the `source` method.
54
+ Notice that the `published?` method can be called even though `ArticleDecorator` doesn't define it - thanks to `delegate_all`, the decorator delegates missing methods to the source model. However, we can override methods like `published_at` to add presentation-specific formatting, in which case we access the underlying model using the `source` method.
53
55
 
54
56
  You might have heard this sort of decorator called a "presenter", an "exhibit", a "view model", or even just a "view" (in that nomenclature, what Rails calls "views" are actually "templates"). Whatever you call it, it's a great way to replace procedural helpers like the one above with "real" object-oriented programming.
55
57
 
@@ -64,7 +66,7 @@ Decorators are the ideal place to:
64
66
  Add Draper to your Gemfile:
65
67
 
66
68
  ```ruby
67
- gem 'draper', '~> 1.0'
69
+ gem 'draper', '~> 1.0.0.beta6'
68
70
  ```
69
71
 
70
72
  And run `bundle install` within your app's directory.
@@ -105,6 +107,8 @@ Decorators will delegate methods to the model where possible, which means in mos
105
107
 
106
108
  ```ruby
107
109
  class ArticleDecorator < Draper::Decorator
110
+ delegate_all
111
+
108
112
  def published_at
109
113
  source.published_at.strftime("%A, %B %e")
110
114
  end
@@ -158,14 +162,16 @@ end
158
162
 
159
163
  Draper guesses the decorator used for each item from the name of the collection decorator (`ArticlesDecorator` becomes `ArticleDecorator`). If that fails, it falls back to using each item's `decorate` method. Alternatively, you can specify a decorator by overriding the collection decorator's `decorator_class` method.
160
164
 
161
- Some pagination gems add methods to `ActiveRecord::Relation`. For example, [Kaminari](https://github.com/amatsuda/kaminari)'s `paginate` helper method requires the collection to implement `current_page`, `total_pages`, and `limit_value`. To expose these on a collection decorator, you can simply delegate to `source`:
165
+ Some pagination gems add methods to `ActiveRecord::Relation`. For example, [Kaminari](https://github.com/amatsuda/kaminari)'s `paginate` helper method requires the collection to implement `current_page`, `total_pages`, and `limit_value`. To expose these on a collection decorator, you can simply delegate to the `source`:
162
166
 
163
167
  ```ruby
164
168
  class PaginatingDecorator < Draper::CollectionDecorator
165
- delegate :current_page, :total_pages, :limit_value, to: :source
169
+ delegate :current_page, :total_pages, :limit_value
166
170
  end
167
171
  ```
168
172
 
173
+ The `delegate` method used here is the same as that added by [Active Support](http://api.rubyonrails.org/classes/Module.html#method-i-delegate), except that the `:to` option is not required; it defaults to `:source` when omitted.
174
+
169
175
  ### Handy shortcuts
170
176
 
171
177
  You can automatically decorate associated models:
@@ -193,7 +199,7 @@ so that you can do:
193
199
 
194
200
  ## Testing
195
201
 
196
- Draper supports RSpec and Minitest::Rails out of the box, and should work with Test::Unit as well.
202
+ Draper supports RSpec, MiniTest::Rails, and Test::Unit, and will add the appropriate tests when you generate a decorator.
197
203
 
198
204
  ### RSpec
199
205
 
@@ -229,25 +235,16 @@ and inherit from it instead of directly from `Draper::Decorator`.
229
235
 
230
236
  ### Enforcing an interface between controllers and views
231
237
 
232
- If you want to strictly control which methods are called in your views, you can restrict the methods that the decorator delegates to the model. Use `denies` to blacklist methods:
238
+ The `delegate_all` call at the top of your decorator means that all missing methods will delegated to the source. If you want to strictly control which methods are called in your views, you can choose to only delegate certain methods.
233
239
 
234
240
  ```ruby
235
241
  class ArticleDecorator < Draper::Decorator
236
- # allow everything except `title` and `author` to be delegated
237
- denies :title, :author
242
+ delegate :title, :author
238
243
  end
239
244
  ```
240
245
 
241
- or, better, use `allows` for a whitelist:
242
-
243
- ```ruby
244
- class ArticleDecorator < Draper::Decorator
245
- # only allow `title` and `author` to be delegated to the model
246
- allows :title, :author
247
- end
248
- ```
246
+ As mentioned above for `CollectionDecorator`, the `delegate` method defaults to using `:source` if the `:to` option is omitted.
249
247
 
250
- You can prevent method delegation altogether using `denies_all`.
251
248
 
252
249
  ### Adding context
253
250
 
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ desc "Run all specs"
16
16
  task "spec" => "spec:all"
17
17
 
18
18
  namespace "spec" do
19
- task "all" => ["draper", "generators", "minitest-rails", "integration"]
19
+ task "all" => ["draper", "generators", "integration"]
20
20
 
21
21
  def spec_task(name)
22
22
  desc "Run #{name} specs"
@@ -27,7 +27,6 @@ namespace "spec" do
27
27
 
28
28
  spec_task "draper"
29
29
  spec_task "generators"
30
- spec_task "minitest-rails"
31
30
 
32
31
  desc "Run integration specs"
33
32
  task "integration" => ["db:setup", "integration:all"]
@@ -51,6 +50,8 @@ namespace "spec" do
51
50
  end
52
51
 
53
52
  task "test" do
53
+ puts "Running rake in dummy app"
54
+ ENV["RAILS_ENV"] = "test"
54
55
  run_in_dummy_app "rake"
55
56
  end
56
57
  end
@@ -60,7 +61,8 @@ namespace "db" do
60
61
  desc "Set up databases for integration testing"
61
62
  task "setup" do
62
63
  run_in_dummy_app "rm -f db/*.sqlite3"
63
- run_in_dummy_app "RAILS_ENV=development rake db:schema:load db:seed db:test:prepare"
64
+ run_in_dummy_app "RAILS_ENV=development rake db:schema:load db:seed"
64
65
  run_in_dummy_app "RAILS_ENV=production rake db:schema:load db:seed"
66
+ run_in_dummy_app "RAILS_ENV=test rake db:schema:load"
65
67
  end
66
68
  end
@@ -21,10 +21,10 @@ Gem::Specification.new do |s|
21
21
  s.add_dependency 'request_store', '~> 1.0.3'
22
22
 
23
23
  s.add_development_dependency 'ammeter'
24
- s.add_development_dependency 'rake', '~> 0.9.2'
24
+ s.add_development_dependency 'rake', '>= 0.9.2'
25
25
  s.add_development_dependency 'rspec', '~> 2.12'
26
26
  s.add_development_dependency 'rspec-mocks', '>= 2.12.1'
27
- s.add_development_dependency 'yard'
27
+ s.add_development_dependency 'rspec-rails', '~> 2.12'
28
28
  s.add_development_dependency 'minitest-rails', '~> 0.2'
29
29
  s.add_development_dependency 'capybara'
30
30
  end
@@ -2,13 +2,14 @@ require 'action_view'
2
2
 
3
3
  require 'draper/version'
4
4
  require 'draper/view_helpers'
5
+ require 'draper/delegation'
6
+ require 'draper/automatic_delegation'
5
7
  require 'draper/finders'
6
8
  require 'draper/decorator'
7
9
  require 'draper/helper_proxy'
8
10
  require 'draper/lazy_helpers'
9
11
  require 'draper/decoratable'
10
12
  require 'draper/decorated_association'
11
- require 'draper/security'
12
13
  require 'draper/helper_support'
13
14
  require 'draper/view_context'
14
15
  require 'draper/collection_decorator'
@@ -0,0 +1,50 @@
1
+ module Draper
2
+ module AutomaticDelegation
3
+ extend ActiveSupport::Concern
4
+
5
+ # Delegates missing instance methods to the source object.
6
+ def method_missing(method, *args, &block)
7
+ return super unless delegatable?(method)
8
+
9
+ self.class.delegate method
10
+ send(method, *args, &block)
11
+ end
12
+
13
+ # Checks if the decorator responds to an instance method, or is able to
14
+ # proxy it to the source object.
15
+ def respond_to?(method, include_private = false)
16
+ super || delegatable?(method)
17
+ end
18
+
19
+ # @private
20
+ def delegatable?(method)
21
+ source.respond_to?(method)
22
+ end
23
+
24
+ module ClassMethods
25
+ # Proxies missing class methods to the source class.
26
+ def method_missing(method, *args, &block)
27
+ return super unless delegatable?(method)
28
+
29
+ source_class.send(method, *args, &block)
30
+ end
31
+
32
+ # Checks if the decorator responds to a class method, or is able to proxy
33
+ # it to the source class.
34
+ def respond_to?(method, include_private = false)
35
+ super || delegatable?(method)
36
+ end
37
+
38
+ # @private
39
+ def delegatable?(method)
40
+ source_class? && source_class.respond_to?(method)
41
+ end
42
+ end
43
+
44
+ included do
45
+ private :delegatable?
46
+ private_class_method :delegatable?
47
+ end
48
+
49
+ end
50
+ end
@@ -1,16 +1,26 @@
1
1
  module Draper
2
2
  class CollectionDecorator
3
3
  include Enumerable
4
- include ViewHelpers
4
+ include Draper::ViewHelpers
5
+ extend Draper::Delegation
5
6
 
7
+ # @return [Hash] extra data to be used in user-defined methods, and passed
8
+ # to each item's decorator.
6
9
  attr_accessor :context
7
10
 
8
11
  array_methods = Array.instance_methods - Object.instance_methods
9
12
  delegate :==, :as_json, *array_methods, to: :decorated_collection
10
13
 
11
- # @param source collection to decorate
12
- # @option options [Class] :with the class used to decorate items
13
- # @option options [Hash] :context context available to each item's decorator
14
+ # @param [Enumerable] source
15
+ # collection to decorate.
16
+ # @option options [Class, nil] :with (nil)
17
+ # the decorator class used to decorate each item. When `nil`, it is
18
+ # inferred from the collection decorator class if possible (e.g.
19
+ # `ProductsDecorator` maps to `ProductDecorator`), otherwise each item's
20
+ # {Decoratable#decorate decorate} method will be used.
21
+ # @option options [Hash] :context ({})
22
+ # extra data to be stored in the collection decorator and used in
23
+ # user-defined methods, and passed to each item's decorator.
14
24
  def initialize(source, options = {})
15
25
  options.assert_valid_keys(:with, :context)
16
26
  @source = source
@@ -22,10 +32,14 @@ module Draper
22
32
  alias_method :decorate, :new
23
33
  end
24
34
 
35
+ # @return [Array] the decorated items.
25
36
  def decorated_collection
26
37
  @decorated_collection ||= source.map{|item| decorate_item(item)}
27
38
  end
28
39
 
40
+ # Delegated to the decorated collection when using the block form
41
+ # (`Enumerable#find`) or to the decorator class if not
42
+ # (`ActiveRecord::FinderMethods#find`)
29
43
  def find(*args, &block)
30
44
  if block_given?
31
45
  decorated_collection.find(*args, &block)
@@ -41,7 +55,7 @@ module Draper
41
55
  "inferred decorators"
42
56
  end
43
57
 
44
- "#<CollectionDecorator of #{klass} for #{source.inspect}>"
58
+ "#<#{self.class.name} of #{klass} for #{source.inspect}>"
45
59
  end
46
60
 
47
61
  def context=(value)
@@ -49,18 +63,25 @@ module Draper
49
63
  each {|item| item.context = value } if @decorated_collection
50
64
  end
51
65
 
66
+ # @return [Class] the decorator class used to decorate each item, as set by
67
+ # {#initialize} or as inferred from the collection decorator class (e.g.
68
+ # `ProductsDecorator` maps to `ProductDecorator`).
52
69
  def decorator_class
53
70
  @decorator_class ||= self.class.inferred_decorator_class
54
71
  end
55
72
 
56
73
  protected
57
74
 
75
+ # @return the collection being decorated.
58
76
  attr_reader :source
59
77
 
78
+ # Decorates the given item.
60
79
  def decorate_item(item)
61
80
  item_decorator.call(item, context: context)
62
81
  end
63
82
 
83
+ private
84
+
64
85
  def self.inferred_decorator_class
65
86
  decorator_name = "#{name.chomp("Decorator").singularize}Decorator"
66
87
  decorator_uninferrable if decorator_name == name
@@ -75,8 +96,6 @@ module Draper
75
96
  raise Draper::UninferrableDecoratorError.new(self)
76
97
  end
77
98
 
78
- private
79
-
80
99
  def item_decorator
81
100
  @item_decorator ||= begin
82
101
  decorator_class.method(:decorate)
@@ -1,44 +1,83 @@
1
- module Draper::Decoratable
2
- extend ActiveSupport::Concern
1
+ module Draper
2
+ # Provides shortcuts to decorate objects directly, so you can do
3
+ # `@product.decorate` instead of `ProductDecorator.new(@product)`.
4
+ #
5
+ # This module is included by default into `ActiveRecord::Base` and
6
+ # `Mongoid::Document`, but you're using another ORM, or want to decorate
7
+ # plain old Ruby objects, you can include it manually.
8
+ module Decoratable
9
+ extend ActiveSupport::Concern
3
10
 
4
- def decorate(options = {})
5
- decorator_class.decorate(self, options)
6
- end
7
-
8
- def decorator_class
9
- self.class.decorator_class
10
- end
11
-
12
- def applied_decorators
13
- []
14
- end
11
+ # Decorates the object using the inferred {#decorator_class}.
12
+ # @param [Hash] options
13
+ # see {Decorator#initialize}
14
+ def decorate(options = {})
15
+ decorator_class.decorate(self, options)
16
+ end
15
17
 
16
- def decorated_with?(decorator_class)
17
- false
18
- end
18
+ # (see ClassMethods#decorator_class)
19
+ def decorator_class
20
+ self.class.decorator_class
21
+ end
19
22
 
20
- def decorated?
21
- false
22
- end
23
+ # The list of decorators that have been applied to the object.
24
+ #
25
+ # @return [Array<Class>] `[]`
26
+ def applied_decorators
27
+ []
28
+ end
23
29
 
24
- def ==(other)
25
- super || (other.respond_to?(:source) && self == other.source)
26
- end
30
+ # (see Decorator#decorated_with?)
31
+ # @return [false]
32
+ def decorated_with?(decorator_class)
33
+ false
34
+ end
27
35
 
28
- module ClassMethods
29
- def decorate(options = {})
30
- decorator_class.decorate_collection(self.scoped, options)
36
+ # Checks if this object is decorated.
37
+ #
38
+ # @return [false]
39
+ def decorated?
40
+ false
31
41
  end
32
42
 
33
- def decorator_class
34
- prefix = respond_to?(:model_name) ? model_name : name
35
- "#{prefix}Decorator".constantize
36
- rescue NameError
37
- raise Draper::UninferrableDecoratorError.new(self)
43
+ # Compares with possibly-decorated objects.
44
+ #
45
+ # @return [Boolean]
46
+ def ==(other)
47
+ super || (other.respond_to?(:source) && self == other.source)
38
48
  end
39
49
 
40
- def ===(other)
41
- super || (other.respond_to?(:source) && super(other.source))
50
+ module ClassMethods
51
+
52
+ # Decorates a collection of objects. Used at the end of a scope chain.
53
+ #
54
+ # @example
55
+ # Product.popular.decorate
56
+ # @param [Hash] options
57
+ # see {Decorator.decorate_collection}.
58
+ def decorate(options = {})
59
+ decorator_class.decorate_collection(self.scoped, options)
60
+ end
61
+
62
+ # Infers the decorator class to be used by {Decoratable#decorate} (e.g.
63
+ # `Product` maps to `ProductDecorator`).
64
+ #
65
+ # @return [Class] the inferred decorator class.
66
+ def decorator_class
67
+ prefix = respond_to?(:model_name) ? model_name : name
68
+ "#{prefix}Decorator".constantize
69
+ rescue NameError
70
+ raise Draper::UninferrableDecoratorError.new(self)
71
+ end
72
+
73
+ # Compares with possibly-decorated objects.
74
+ #
75
+ # @return [Boolean]
76
+ def ===(other)
77
+ super || (other.respond_to?(:source) && super(other.source))
78
+ end
79
+
42
80
  end
81
+
43
82
  end
44
83
  end