drape 1.0.0.beta1

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.
Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +27 -0
  3. data/.gitignore +18 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1159 -0
  6. data/.travis.yml +14 -0
  7. data/.yardopts +1 -0
  8. data/CHANGELOG.md +230 -0
  9. data/CONTRIBUTING.md +20 -0
  10. data/Gemfile +16 -0
  11. data/Guardfile +26 -0
  12. data/LICENSE +7 -0
  13. data/README.md +592 -0
  14. data/Rakefile +76 -0
  15. data/drape.gemspec +31 -0
  16. data/gemfiles/5.0.gemfile +8 -0
  17. data/lib/drape.rb +63 -0
  18. data/lib/drape/automatic_delegation.rb +55 -0
  19. data/lib/drape/collection_decorator.rb +102 -0
  20. data/lib/drape/decoratable.rb +93 -0
  21. data/lib/drape/decoratable/equality.rb +26 -0
  22. data/lib/drape/decorated_association.rb +33 -0
  23. data/lib/drape/decorates_assigned.rb +44 -0
  24. data/lib/drape/decorator.rb +282 -0
  25. data/lib/drape/delegation.rb +13 -0
  26. data/lib/drape/factory.rb +91 -0
  27. data/lib/drape/finders.rb +36 -0
  28. data/lib/drape/helper_proxy.rb +43 -0
  29. data/lib/drape/helper_support.rb +5 -0
  30. data/lib/drape/lazy_helpers.rb +13 -0
  31. data/lib/drape/railtie.rb +69 -0
  32. data/lib/drape/tasks/test.rake +9 -0
  33. data/lib/drape/test/devise_helper.rb +30 -0
  34. data/lib/drape/test/minitest_integration.rb +6 -0
  35. data/lib/drape/test/rspec_integration.rb +20 -0
  36. data/lib/drape/test_case.rb +42 -0
  37. data/lib/drape/undecorate.rb +9 -0
  38. data/lib/drape/version.rb +3 -0
  39. data/lib/drape/view_context.rb +104 -0
  40. data/lib/drape/view_context/build_strategy.rb +46 -0
  41. data/lib/drape/view_helpers.rb +34 -0
  42. data/lib/generators/controller_override.rb +17 -0
  43. data/lib/generators/mini_test/decorator_generator.rb +20 -0
  44. data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
  45. data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
  46. data/lib/generators/rails/decorator_generator.rb +36 -0
  47. data/lib/generators/rails/templates/decorator.rb +19 -0
  48. data/lib/generators/rspec/decorator_generator.rb +9 -0
  49. data/lib/generators/rspec/templates/decorator_spec.rb +4 -0
  50. data/lib/generators/test_unit/decorator_generator.rb +9 -0
  51. data/lib/generators/test_unit/templates/decorator_test.rb +4 -0
  52. data/spec/draper/collection_decorator_spec.rb +305 -0
  53. data/spec/draper/decoratable/equality_spec.rb +10 -0
  54. data/spec/draper/decoratable_spec.rb +203 -0
  55. data/spec/draper/decorated_association_spec.rb +85 -0
  56. data/spec/draper/decorates_assigned_spec.rb +70 -0
  57. data/spec/draper/decorator_spec.rb +828 -0
  58. data/spec/draper/factory_spec.rb +250 -0
  59. data/spec/draper/finders_spec.rb +165 -0
  60. data/spec/draper/helper_proxy_spec.rb +61 -0
  61. data/spec/draper/lazy_helpers_spec.rb +21 -0
  62. data/spec/draper/undecorate_spec.rb +19 -0
  63. data/spec/draper/view_context/build_strategy_spec.rb +116 -0
  64. data/spec/draper/view_context_spec.rb +160 -0
  65. data/spec/draper/view_helpers_spec.rb +8 -0
  66. data/spec/dummy/.gitignore +21 -0
  67. data/spec/dummy/Rakefile +6 -0
  68. data/spec/dummy/app/assets/config/manifest.js +3 -0
  69. data/spec/dummy/app/assets/images/.keep +0 -0
  70. data/spec/dummy/app/assets/javascripts/application.js +16 -0
  71. data/spec/dummy/app/assets/javascripts/cable.js +13 -0
  72. data/spec/dummy/app/assets/javascripts/channels/.keep +0 -0
  73. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  74. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  75. data/spec/dummy/app/channels/application_cable/connection.rb +5 -0
  76. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  77. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  78. data/spec/dummy/app/controllers/posts_controller.rb +21 -0
  79. data/spec/dummy/app/decorators/mongoid_post_decorator.rb +4 -0
  80. data/spec/dummy/app/decorators/post_decorator.rb +59 -0
  81. data/spec/dummy/app/helpers/application_helper.rb +5 -0
  82. data/spec/dummy/app/jobs/application_job.rb +2 -0
  83. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  84. data/spec/dummy/app/mailers/post_mailer.rb +18 -0
  85. data/spec/dummy/app/models/admin.rb +5 -0
  86. data/spec/dummy/app/models/application_record.rb +3 -0
  87. data/spec/dummy/app/models/concerns/.keep +0 -0
  88. data/spec/dummy/app/models/mongoid_post.rb +5 -0
  89. data/spec/dummy/app/models/post.rb +3 -0
  90. data/spec/dummy/app/models/user.rb +5 -0
  91. data/spec/dummy/app/views/layouts/application.html.erb +9 -0
  92. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  93. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  94. data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +31 -0
  95. data/spec/dummy/app/views/posts/_post.html.erb +40 -0
  96. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  97. data/spec/dummy/bin/bundle +3 -0
  98. data/spec/dummy/bin/rails +9 -0
  99. data/spec/dummy/bin/rake +9 -0
  100. data/spec/dummy/bin/setup +34 -0
  101. data/spec/dummy/bin/spring +15 -0
  102. data/spec/dummy/bin/update +29 -0
  103. data/spec/dummy/config.ru +5 -0
  104. data/spec/dummy/config/application.rb +21 -0
  105. data/spec/dummy/config/boot.rb +2 -0
  106. data/spec/dummy/config/cable.yml +10 -0
  107. data/spec/dummy/config/database.yml +25 -0
  108. data/spec/dummy/config/environment.rb +5 -0
  109. data/spec/dummy/config/environments/development.rb +51 -0
  110. data/spec/dummy/config/environments/production.rb +91 -0
  111. data/spec/dummy/config/environments/test.rb +42 -0
  112. data/spec/dummy/config/initializers/active_record_belongs_to_required_by_default.rb +6 -0
  113. data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -0
  114. data/spec/dummy/config/initializers/assets.rb +11 -0
  115. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  116. data/spec/dummy/config/initializers/callback_terminator.rb +6 -0
  117. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  118. data/spec/dummy/config/initializers/devise.rb +3 -0
  119. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  120. data/spec/dummy/config/initializers/inflections.rb +16 -0
  121. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  122. data/spec/dummy/config/initializers/per_form_csrf_tokens.rb +4 -0
  123. data/spec/dummy/config/initializers/request_forgery_protection.rb +4 -0
  124. data/spec/dummy/config/initializers/session_store.rb +3 -0
  125. data/spec/dummy/config/initializers/ssl_options.rb +4 -0
  126. data/spec/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  127. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  128. data/spec/dummy/config/locales/en.yml +23 -0
  129. data/spec/dummy/config/mongoid.yml +16 -0
  130. data/spec/dummy/config/puma.rb +47 -0
  131. data/spec/dummy/config/routes.rb +8 -0
  132. data/spec/dummy/config/secrets.yml +22 -0
  133. data/spec/dummy/config/spring.rb +6 -0
  134. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +7 -0
  135. data/spec/dummy/db/schema.rb +19 -0
  136. data/spec/dummy/db/seeds.rb +2 -0
  137. data/spec/dummy/lib/assets/.keep +0 -0
  138. data/spec/dummy/lib/tasks/.keep +0 -0
  139. data/spec/dummy/lib/tasks/test.rake +16 -0
  140. data/spec/dummy/log/.keep +0 -0
  141. data/spec/dummy/public/404.html +67 -0
  142. data/spec/dummy/public/422.html +67 -0
  143. data/spec/dummy/public/500.html +66 -0
  144. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  145. data/spec/dummy/public/apple-touch-icon.png +0 -0
  146. data/spec/dummy/public/favicon.ico +0 -0
  147. data/spec/dummy/public/robots.txt +5 -0
  148. data/spec/dummy/spec/decorators/active_model_serializers_spec.rb +12 -0
  149. data/spec/dummy/spec/decorators/devise_spec.rb +64 -0
  150. data/spec/dummy/spec/decorators/helpers_spec.rb +21 -0
  151. data/spec/dummy/spec/decorators/post_decorator_spec.rb +66 -0
  152. data/spec/dummy/spec/decorators/spec_type_spec.rb +7 -0
  153. data/spec/dummy/spec/decorators/view_context_spec.rb +22 -0
  154. data/spec/dummy/spec/mailers/post_mailer_spec.rb +33 -0
  155. data/spec/dummy/spec/models/mongoid_post_spec.rb +8 -0
  156. data/spec/dummy/spec/models/post_spec.rb +6 -0
  157. data/spec/dummy/spec/shared_examples/decoratable.rb +26 -0
  158. data/spec/dummy/spec/spec_helper.rb +8 -0
  159. data/spec/dummy/test/controllers/.keep +0 -0
  160. data/spec/dummy/test/fixtures/.keep +0 -0
  161. data/spec/dummy/test/fixtures/files/.keep +0 -0
  162. data/spec/dummy/test/helpers/.keep +0 -0
  163. data/spec/dummy/test/integration/.keep +0 -0
  164. data/spec/dummy/test/mailers/.keep +0 -0
  165. data/spec/dummy/test/models/.keep +0 -0
  166. data/spec/dummy/test/test_helper.rb +10 -0
  167. data/spec/dummy/vendor/assets/javascripts/.keep +0 -0
  168. data/spec/dummy/vendor/assets/stylesheets/.keep +0 -0
  169. data/spec/generators/controller/controller_generator_spec.rb +24 -0
  170. data/spec/generators/decorator/decorator_generator_spec.rb +94 -0
  171. data/spec/integration/integration_spec.rb +68 -0
  172. data/spec/performance/active_record.rb +4 -0
  173. data/spec/performance/benchmark.rb +57 -0
  174. data/spec/performance/decorators.rb +41 -0
  175. data/spec/performance/models.rb +20 -0
  176. data/spec/spec_helper.rb +41 -0
  177. data/spec/support/dummy_app.rb +88 -0
  178. data/spec/support/matchers/have_text.rb +50 -0
  179. data/spec/support/shared_examples/decoratable_equality.rb +40 -0
  180. data/spec/support/shared_examples/view_helpers.rb +39 -0
  181. metadata +497 -0
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require 'support/shared_examples/decoratable_equality'
3
+
4
+ module Drape
5
+ describe Decoratable::Equality do
6
+ describe '#==' do
7
+ it_behaves_like 'decoration-aware #==', Object.new.extend(Decoratable::Equality)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,203 @@
1
+ require 'spec_helper'
2
+ require 'support/shared_examples/decoratable_equality'
3
+
4
+ module Drape
5
+ describe Decoratable do
6
+ describe '#decorate' do
7
+ it 'returns a decorator for self' do
8
+ product = Product.new
9
+ decorator = product.decorate
10
+
11
+ expect(decorator).to be_a ProductDecorator
12
+ expect(decorator.object).to be product
13
+ end
14
+
15
+ it 'accepts context' do
16
+ context = { some: 'context' }
17
+ decorator = Product.new.decorate(context: context)
18
+
19
+ expect(decorator.context).to be context
20
+ end
21
+
22
+ it 'uses the #decorator_class' do
23
+ product = Product.new
24
+ product.stub decorator_class: OtherDecorator
25
+
26
+ expect(product.decorate).to be_an_instance_of OtherDecorator
27
+ end
28
+ end
29
+
30
+ describe '#applied_decorators' do
31
+ it 'returns an empty list' do
32
+ expect(Product.new.applied_decorators).to eq []
33
+ end
34
+ end
35
+
36
+ describe '#decorated_with?' do
37
+ it 'returns false' do
38
+ expect(Product.new).not_to be_decorated_with Decorator
39
+ end
40
+ end
41
+
42
+ describe '#decorated?' do
43
+ it 'returns false' do
44
+ expect(Product.new).not_to be_decorated
45
+ end
46
+ end
47
+
48
+ describe '#decorator_class?' do
49
+ it 'returns true for decoratable model' do
50
+ expect(Product.new.decorator_class?).to be_truthy
51
+ end
52
+
53
+ it 'returns false for non-decoratable model' do
54
+ expect(Model.new.decorator_class?).to be_falsey
55
+ end
56
+ end
57
+
58
+ describe '.decorator_class?' do
59
+ it 'returns true for decoratable model' do
60
+ expect(Product.decorator_class?).to be_truthy
61
+ end
62
+
63
+ it 'returns false for non-decoratable model' do
64
+ expect(Model.decorator_class?).to be_falsey
65
+ end
66
+ end
67
+
68
+ describe '#decorator_class' do
69
+ it 'delegates to .decorator_class' do
70
+ product = Product.new
71
+
72
+ Product.should_receive(:decorator_class).and_return(:some_decorator)
73
+ expect(product.decorator_class).to be :some_decorator
74
+ end
75
+ end
76
+
77
+ describe '#==' do
78
+ it_behaves_like 'decoration-aware #==', Product.new
79
+ end
80
+
81
+ describe '#===' do
82
+ it 'is true when #== is true' do
83
+ product = Product.new
84
+
85
+ product.should_receive(:==).and_return(true)
86
+ expect(product === :anything).to be_truthy
87
+ end
88
+
89
+ it 'is false when #== is false' do
90
+ product = Product.new
91
+
92
+ product.should_receive(:==).and_return(false)
93
+ expect(product === :anything).to be_falsey
94
+ end
95
+ end
96
+
97
+ describe '.====' do
98
+ it 'is true for an instance' do
99
+ expect(Product === Product.new).to be_truthy
100
+ end
101
+
102
+ it 'is true for a derived instance' do
103
+ expect(Product === Class.new(Product).new).to be_truthy
104
+ end
105
+
106
+ it 'is false for an unrelated instance' do
107
+ expect(Product === Model.new).to be_falsey
108
+ end
109
+
110
+ it 'is true for a decorated instance' do
111
+ decorator = double(object: Product.new)
112
+
113
+ expect(Product === decorator).to be_truthy
114
+ end
115
+
116
+ it 'is true for a decorated derived instance' do
117
+ decorator = double(object: Class.new(Product).new)
118
+
119
+ expect(Product === decorator).to be_truthy
120
+ end
121
+
122
+ it 'is false for a decorated unrelated instance' do
123
+ decorator = double(object: Model.new)
124
+
125
+ expect(Product === decorator).to be_falsey
126
+ end
127
+ end
128
+
129
+ describe '.decorate' do
130
+ let(:scoping_method) { Rails::VERSION::MAJOR >= 4 ? :all : :scoped }
131
+
132
+ it 'calls #decorate_collection on .decorator_class' do
133
+ scoped = [Product.new]
134
+ Product.stub scoping_method => scoped
135
+
136
+ Product.decorator_class
137
+ .should_receive(:decorate_collection).with(scoped, with: nil)
138
+ .and_return(:decorated_collection)
139
+
140
+ expect(Product.decorate).to be :decorated_collection
141
+ end
142
+
143
+ it 'accepts options' do
144
+ options = { with: ProductDecorator, context: { some: 'context' } }
145
+ Product.stub scoping_method => []
146
+
147
+ Product.decorator_class.should_receive(:decorate_collection).with([], options)
148
+ Product.decorate(options)
149
+ end
150
+ end
151
+
152
+ describe '.decorator_class' do
153
+ context 'for classes' do
154
+ it 'infers the decorator from the class' do
155
+ expect(Product.decorator_class).to be ProductDecorator
156
+ end
157
+
158
+ context 'without a decorator on its own' do
159
+ it 'infers the decorator from a superclass' do
160
+ expect(SpecialProduct.decorator_class).to be ProductDecorator
161
+ end
162
+ end
163
+ end
164
+
165
+ context 'for ActiveModel classes' do
166
+ it 'infers the decorator from the model name' do
167
+ allow(Product).to receive(:model_name) { 'Other' }
168
+
169
+ expect(Product.decorator_class).to be OtherDecorator
170
+ end
171
+ end
172
+
173
+ context 'in a namespace' do
174
+ context 'for classes' do
175
+ it 'infers the decorator from the class' do
176
+ expect(Namespaced::Product.decorator_class).to be Namespaced::ProductDecorator
177
+ end
178
+ end
179
+
180
+ context 'for ActiveModel classes' do
181
+ it 'infers the decorator from the model name' do
182
+ Namespaced::Product.stub(:model_name).and_return('Namespaced::Other')
183
+
184
+ expect(Namespaced::Product.decorator_class).to be Namespaced::OtherDecorator
185
+ end
186
+ end
187
+ end
188
+
189
+ context "when the decorator can't be inferred" do
190
+ it 'throws an UninferrableDecoratorError' do
191
+ expect { Model.decorator_class }.to raise_error UninferrableDecoratorError
192
+ end
193
+ end
194
+
195
+ context 'when an unrelated NameError is thrown' do
196
+ it 're-raises that error' do
197
+ String.any_instance.stub(:constantize) { Drape::Base }
198
+ expect { Product.decorator_class }.to raise_error NameError, /Drape::Base/
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ module Drape
4
+ describe DecoratedAssociation do
5
+ describe '#initialize' do
6
+ it 'accepts valid options' do
7
+ valid_options = { with: Decorator, scope: :foo, context: {} }
8
+ expect { DecoratedAssociation.new(Decorator.new(Model.new), :association, valid_options) }
9
+ .not_to raise_error
10
+ end
11
+
12
+ it 'rejects invalid options' do
13
+ expect { DecoratedAssociation.new(Decorator.new(Model.new), :association, foo: 'bar') }
14
+ .to raise_error ArgumentError, /Unknown key/
15
+ end
16
+
17
+ it 'creates a factory' do
18
+ options = { with: Decorator, context: { foo: 'bar' } }
19
+
20
+ Factory.should_receive(:new).with(options)
21
+ DecoratedAssociation.new(double, :association, options)
22
+ end
23
+
24
+ describe ':with option' do
25
+ it 'defaults to nil' do
26
+ Factory.should_receive(:new).with(with: nil, context: anything)
27
+ DecoratedAssociation.new(double, :association, {})
28
+ end
29
+ end
30
+
31
+ describe ':context option' do
32
+ it 'defaults to the identity function' do
33
+ Factory.should_receive(:new) do |options|
34
+ options[:context].call(:anything) == :anything
35
+ end
36
+
37
+ DecoratedAssociation.new(double, :association, {})
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#call' do
43
+ it 'calls the factory' do
44
+ factory = double
45
+ Factory.stub new: factory
46
+ associated = double
47
+ owner_context = { foo: 'bar' }
48
+ object = double(association: associated)
49
+ owner = double(object: object, context: owner_context)
50
+ decorated_association = DecoratedAssociation.new(owner, :association, {})
51
+ decorated = double
52
+
53
+ factory.should_receive(:decorate).with(associated, context_args: owner_context).and_return(decorated)
54
+ expect(decorated_association.call).to be decorated
55
+ end
56
+
57
+ it 'memoizes' do
58
+ factory = double
59
+ Factory.stub new: factory
60
+ owner = double(object: double(association: double), context: {})
61
+ decorated_association = DecoratedAssociation.new(owner, :association, {})
62
+ decorated = double
63
+
64
+ factory.should_receive(:decorate).once.and_return(decorated)
65
+ expect(decorated_association.call).to be decorated
66
+ expect(decorated_association.call).to be decorated
67
+ end
68
+
69
+ context 'when the :scope option was given' do
70
+ it 'applies the scope before decoration' do
71
+ factory = double
72
+ Factory.stub new: factory
73
+ scoped = double
74
+ object = double(association: double(applied_scope: scoped))
75
+ owner = double(object: object, context: {})
76
+ decorated_association = DecoratedAssociation.new(owner, :association, scope: :applied_scope)
77
+ decorated = double
78
+
79
+ factory.should_receive(:decorate).with(scoped, anything).and_return(decorated)
80
+ expect(decorated_association.call).to be decorated
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ module Drape
4
+ describe DecoratesAssigned do
5
+ let(:controller_class) do
6
+ Class.new do
7
+ extend DecoratesAssigned
8
+
9
+ def self.helper_method(method)
10
+ helper_methods << method
11
+ end
12
+
13
+ def self.helper_methods
14
+ @helper_methods ||= []
15
+ end
16
+ end
17
+ end
18
+
19
+ describe '.decorates_assigned' do
20
+ it 'adds helper methods' do
21
+ controller_class.decorates_assigned :article, :author
22
+
23
+ expect(controller_class.instance_methods).to include :article
24
+ expect(controller_class.instance_methods).to include :author
25
+
26
+ expect(controller_class.helper_methods).to include :article
27
+ expect(controller_class.helper_methods).to include :author
28
+ end
29
+
30
+ it 'creates a factory' do
31
+ Factory.should_receive(:new).once
32
+ controller_class.decorates_assigned :article, :author
33
+ end
34
+
35
+ it 'passes options to the factory' do
36
+ options = { foo: 'bar' }
37
+
38
+ Factory.should_receive(:new).with(options)
39
+ controller_class.decorates_assigned :article, :author, options
40
+ end
41
+
42
+ describe 'the generated method' do
43
+ it 'decorates the instance variable' do
44
+ object = double
45
+ factory = double
46
+ Factory.stub new: factory
47
+
48
+ controller_class.decorates_assigned :article
49
+ controller = controller_class.new
50
+ controller.instance_variable_set '@article', object
51
+
52
+ factory.should_receive(:decorate).with(object, context_args: controller).and_return(:decorated)
53
+ expect(controller.article).to be :decorated
54
+ end
55
+
56
+ it 'memoizes' do
57
+ factory = double
58
+ Factory.stub new: factory
59
+
60
+ controller_class.decorates_assigned :article
61
+ controller = controller_class.new
62
+
63
+ factory.should_receive(:decorate).once
64
+ controller.article
65
+ controller.article
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,828 @@
1
+ require 'spec_helper'
2
+ require 'support/shared_examples/view_helpers'
3
+
4
+ module Drape
5
+ describe Decorator do
6
+ it_behaves_like 'view helpers', Decorator.new(Model.new)
7
+
8
+ describe '#initialize' do
9
+ describe 'options validation' do
10
+ it 'does not raise error on valid options' do
11
+ valid_options = { context: {} }
12
+ expect { Decorator.new(Model.new, valid_options) }.not_to raise_error
13
+ end
14
+
15
+ it 'raises error on invalid options' do
16
+ expect { Decorator.new(Model.new, foo: 'bar') }.to raise_error ArgumentError, /Unknown key/
17
+ end
18
+ end
19
+
20
+ it 'sets the object' do
21
+ object = Model.new
22
+ decorator = Decorator.new(object)
23
+
24
+ expect(decorator.object).to be object
25
+ end
26
+
27
+ it 'stores context' do
28
+ context = { some: 'context' }
29
+ decorator = Decorator.new(Model.new, context: context)
30
+
31
+ expect(decorator.context).to be context
32
+ end
33
+
34
+ context 'when decorating an instance of itself' do
35
+ it 'applies to the object instead' do
36
+ object = Model.new
37
+ decorated = Decorator.new(object)
38
+ redecorated = Decorator.new(decorated)
39
+
40
+ expect(redecorated.object).to be object
41
+ end
42
+
43
+ context 'with context' do
44
+ it 'overwrites existing context' do
45
+ decorated = Decorator.new(Model.new, context: { some: 'context' })
46
+ new_context = { other: 'context' }
47
+ redecorated = Decorator.new(decorated, context: new_context)
48
+
49
+ expect(redecorated.context).to be new_context
50
+ end
51
+ end
52
+
53
+ context 'without context' do
54
+ it 'preserves existing context' do
55
+ old_context = { some: 'context' }
56
+ decorated = Decorator.new(Model.new, context: old_context)
57
+ redecorated = Decorator.new(decorated)
58
+
59
+ expect(redecorated.context).to be old_context
60
+ end
61
+ end
62
+ end
63
+
64
+ it 'decorates other decorators' do
65
+ decorated = OtherDecorator.new(Model.new)
66
+ redecorated = Decorator.new(decorated)
67
+
68
+ expect(redecorated.object).to be decorated
69
+ end
70
+
71
+ context 'when it has been applied previously' do
72
+ it 'warns' do
73
+ decorated = OtherDecorator.new(Decorator.new(Model.new))
74
+
75
+ warning_message = nil
76
+ Object.any_instance.stub(:warn) { |_instance, message| warning_message = message }
77
+
78
+ expect { Decorator.new(decorated) }.to change { warning_message }
79
+ expect(warning_message).to start_with 'Reapplying Drape::Decorator'
80
+ expect(warning_message).to include caller(1).first
81
+ end
82
+
83
+ it 'decorates anyway' do
84
+ decorated = OtherDecorator.new(Decorator.new(Model.new))
85
+ Object.any_instance.stub(:warn)
86
+ redecorated = Decorator.decorate(decorated)
87
+
88
+ expect(redecorated.object).to be decorated
89
+ end
90
+ end
91
+ end
92
+
93
+ describe '#context=' do
94
+ it 'modifies the context' do
95
+ decorator = Decorator.new(Model.new, context: { some: 'context' })
96
+ new_context = { other: 'context' }
97
+
98
+ decorator.context = new_context
99
+ expect(decorator.context).to be new_context
100
+ end
101
+ end
102
+
103
+ describe '.decorate_collection' do
104
+ describe 'options validation' do
105
+ before { CollectionDecorator.stub(:new) }
106
+
107
+ it 'does not raise error on valid options' do
108
+ valid_options = { with: OtherDecorator, context: {} }
109
+ expect { Decorator.decorate_collection([], valid_options) }.not_to raise_error
110
+ end
111
+
112
+ it 'raises error on invalid options' do
113
+ expect { Decorator.decorate_collection([], foo: 'bar') }.to raise_error ArgumentError, /Unknown key/
114
+ end
115
+ end
116
+
117
+ context 'without a custom collection decorator' do
118
+ it 'creates a CollectionDecorator using itself for each item' do
119
+ object = [Model.new]
120
+
121
+ CollectionDecorator.should_receive(:new).with(object, with: Decorator)
122
+ Decorator.decorate_collection(object)
123
+ end
124
+
125
+ it 'passes options to the collection decorator' do
126
+ options = { with: OtherDecorator, context: { some: 'context' } }
127
+
128
+ CollectionDecorator.should_receive(:new).with([], options)
129
+ Decorator.decorate_collection([], options)
130
+ end
131
+ end
132
+
133
+ context 'with a custom collection decorator' do
134
+ it 'creates a custom collection decorator using itself for each item' do
135
+ object = [Model.new]
136
+
137
+ ProductsDecorator.should_receive(:new).with(object, with: ProductDecorator)
138
+ ProductDecorator.decorate_collection(object)
139
+ end
140
+
141
+ it 'passes options to the collection decorator' do
142
+ options = { with: OtherDecorator, context: { some: 'context' } }
143
+
144
+ ProductsDecorator.should_receive(:new).with([], options)
145
+ ProductDecorator.decorate_collection([], options)
146
+ end
147
+ end
148
+
149
+ context 'when a NameError is thrown' do
150
+ it 're-raises that error' do
151
+ String.any_instance.stub(:constantize) { Drape::DecoratedEnumerableProxy }
152
+ expect { ProductDecorator.decorate_collection([]) }.to raise_error NameError, /Drape::DecoratedEnumerableProxy/
153
+ end
154
+ end
155
+ end
156
+
157
+ describe '.decorates' do
158
+ protect_class Decorator
159
+
160
+ it 'sets .object_class with a symbol' do
161
+ Decorator.decorates :product
162
+
163
+ expect(Decorator.object_class).to be Product
164
+ end
165
+
166
+ it 'sets .object_class with a string' do
167
+ Decorator.decorates 'product'
168
+
169
+ expect(Decorator.object_class).to be Product
170
+ end
171
+
172
+ it 'sets .object_class with a class' do
173
+ Decorator.decorates Product
174
+
175
+ expect(Decorator.object_class).to be Product
176
+ end
177
+ end
178
+
179
+ describe '.object_class' do
180
+ protect_class ProductDecorator
181
+ protect_class Namespaced::ProductDecorator
182
+
183
+ context 'when not set by .decorates' do
184
+ it "raises an UninferrableSourceError for a so-named 'Decorator'" do
185
+ expect { Decorator.object_class }.to raise_error UninferrableSourceError
186
+ end
187
+
188
+ it 'raises an UninferrableSourceError for anonymous decorators' do
189
+ expect { Class.new(Decorator).object_class }.to raise_error UninferrableSourceError
190
+ end
191
+
192
+ it 'raises an UninferrableSourceError for a decorator without a model' do
193
+ skip
194
+ expect { OtherDecorator.object_class }.to raise_error UninferrableSourceError
195
+ end
196
+
197
+ it 'raises an UninferrableSourceError for other naming conventions' do
198
+ expect { ProductPresenter.object_class }.to raise_error UninferrableSourceError
199
+ end
200
+
201
+ it "infers the source for '<Model>Decorator'" do
202
+ expect(ProductDecorator.object_class).to be Product
203
+ end
204
+
205
+ it 'infers namespaced sources' do
206
+ expect(Namespaced::ProductDecorator.object_class).to be Namespaced::Product
207
+ end
208
+
209
+ context 'when an unrelated NameError is thrown' do
210
+ it 're-raises that error' do
211
+ String.any_instance.stub(:constantize) { SomethingThatDoesntExist }
212
+ expect { ProductDecorator.object_class }.to raise_error NameError, /SomethingThatDoesntExist/
213
+ end
214
+ end
215
+ end
216
+
217
+ it 'is aliased to .source_class' do
218
+ expect(ProductDecorator.source_class).to be Product
219
+ end
220
+ end
221
+
222
+ describe '.object_class?' do
223
+ it 'returns truthy when .object_class is set' do
224
+ Decorator.stub(:object_class).and_return(Model)
225
+
226
+ expect(Decorator.object_class?).to be_truthy
227
+ end
228
+
229
+ it 'returns false when .object_class is not inferrable' do
230
+ Decorator.stub(:object_class).and_raise(UninferrableSourceError.new(Decorator))
231
+
232
+ expect(Decorator.object_class?).to be_falsey
233
+ end
234
+
235
+ it 'is aliased to .source_class?' do
236
+ Decorator.stub(:object_class).and_return(Model)
237
+
238
+ expect(Decorator.source_class?).to be_truthy
239
+ end
240
+ end
241
+
242
+ describe '.decorates_association' do
243
+ protect_class Decorator
244
+
245
+ describe 'options validation' do
246
+ before { DecoratedAssociation.stub(:new).and_return(-> {}) }
247
+
248
+ it 'does not raise error on valid options' do
249
+ valid_options = { with: Class, scope: :sorted, context: {} }
250
+ expect { Decorator.decorates_association(:children, valid_options) }.not_to raise_error
251
+ end
252
+
253
+ it 'raises error on invalid options' do
254
+ expect { Decorator.decorates_association(:children, foo: 'bar') }.to raise_error ArgumentError, /Unknown key/
255
+ end
256
+ end
257
+
258
+ describe 'defines an association method' do
259
+ it 'creates a DecoratedAssociation' do
260
+ options = { with: Class.new, scope: :foo, context: {} }
261
+ Decorator.decorates_association :children, options
262
+ decorator = Decorator.new(Model.new)
263
+
264
+ DecoratedAssociation.should_receive(:new).with(decorator, :children, options).and_return(-> {})
265
+ decorator.children
266
+ end
267
+
268
+ it 'memoizes the DecoratedAssociation' do
269
+ Decorator.decorates_association :children
270
+ decorator = Decorator.new(Model.new)
271
+
272
+ DecoratedAssociation.should_receive(:new).once.and_return(-> {})
273
+ decorator.children
274
+ decorator.children
275
+ end
276
+
277
+ it 'calls the DecoratedAssociation' do
278
+ Decorator.decorates_association :children
279
+ decorator = Decorator.new(Model.new)
280
+ decorated_association = -> {}
281
+ DecoratedAssociation.stub(:new).and_return(decorated_association)
282
+
283
+ decorated_association.should_receive(:call).and_return(:decorated)
284
+ expect(decorator.children).to be :decorated
285
+ end
286
+ end
287
+ end
288
+
289
+ describe '.decorates_associations' do
290
+ protect_class Decorator
291
+
292
+ it 'decorates each of the associations' do
293
+ Decorator.should_receive(:decorates_association).with(:friends, {})
294
+ Decorator.should_receive(:decorates_association).with(:enemies, {})
295
+ Decorator.decorates_associations :friends, :enemies
296
+ end
297
+
298
+ it 'dispatches options' do
299
+ options = { with: Class.new, scope: :foo, context: {} }
300
+
301
+ Decorator.should_receive(:decorates_association).with(:friends, options)
302
+ Decorator.should_receive(:decorates_association).with(:enemies, options)
303
+ Decorator.decorates_associations :friends, :enemies, options
304
+ end
305
+ end
306
+
307
+ describe '#applied_decorators' do
308
+ it 'returns a list of decorators applied to a model' do
309
+ decorator = ProductDecorator.new(OtherDecorator.new(Decorator.new(Model.new)))
310
+
311
+ expect(decorator.applied_decorators).to eq [Decorator, OtherDecorator, ProductDecorator]
312
+ end
313
+ end
314
+
315
+ describe '#decorated_with?' do
316
+ it 'checks if a decorator has been applied to a model' do
317
+ decorator = ProductDecorator.new(Decorator.new(Model.new))
318
+
319
+ expect(decorator).to be_decorated_with Decorator
320
+ expect(decorator).to be_decorated_with ProductDecorator
321
+ expect(decorator).not_to be_decorated_with OtherDecorator
322
+ end
323
+ end
324
+
325
+ describe '#decorated?' do
326
+ it 'returns true' do
327
+ decorator = Decorator.new(Model.new)
328
+
329
+ expect(decorator).to be_decorated
330
+ end
331
+ end
332
+
333
+ describe '#object' do
334
+ it 'returns the wrapped object' do
335
+ object = Model.new
336
+ decorator = Decorator.new(object)
337
+
338
+ expect(decorator.object).to be object
339
+ expect(decorator.model).to be object
340
+ expect(decorator.to_source).to be object
341
+ end
342
+
343
+ it 'is aliased to #model' do
344
+ object = Model.new
345
+ decorator = Decorator.new(object)
346
+
347
+ expect(decorator.model).to be object
348
+ end
349
+
350
+ it 'is aliased to #source' do
351
+ object = Model.new
352
+ decorator = Decorator.new(object)
353
+
354
+ expect(decorator.source).to be object
355
+ end
356
+
357
+ it 'is aliased to #to_source' do
358
+ object = Model.new
359
+ decorator = Decorator.new(object)
360
+
361
+ expect(decorator.to_source).to be object
362
+ end
363
+ end
364
+
365
+ describe 'aliasing object to object class name' do
366
+ context 'when object_class is inferrable from the decorator name' do
367
+ it 'aliases object to the object class name' do
368
+ object = double
369
+ decorator = ProductDecorator.new(object)
370
+
371
+ expect(decorator.product).to be object
372
+ end
373
+ end
374
+
375
+ context 'when object_class is set by decorates' do
376
+ it 'aliases object to the object class name' do
377
+ decorator_class = Class.new(Decorator) { decorates Product }
378
+ object = double
379
+ decorator = decorator_class.new(object)
380
+
381
+ expect(decorator.product).to be object
382
+ end
383
+ end
384
+
385
+ context "when object_class's name is several words long" do
386
+ it 'underscores the method name' do
387
+ stub_const 'LongWindedModel', Class.new
388
+ decorator_class = Class.new(Decorator) { decorates LongWindedModel }
389
+ object = double
390
+ decorator = decorator_class.new(object)
391
+
392
+ expect(decorator.long_winded_model).to be object
393
+ end
394
+ end
395
+
396
+ context 'when object_class is not set' do
397
+ it 'does not alias object' do
398
+ decorator_class = Class.new(Decorator)
399
+
400
+ expect(decorator_class.instance_methods).to eq Decorator.instance_methods
401
+ end
402
+ end
403
+ end
404
+
405
+ describe '#to_model' do
406
+ it 'returns the decorator' do
407
+ decorator = Decorator.new(Model.new)
408
+
409
+ expect(decorator.to_model).to be decorator
410
+ end
411
+ end
412
+
413
+ describe '#to_param' do
414
+ it 'delegates to the object' do
415
+ decorator = Decorator.new(double(to_param: :delegated))
416
+
417
+ expect(decorator.to_param).to be :delegated
418
+ end
419
+ end
420
+
421
+ describe '#present?' do
422
+ it 'delegates to the object' do
423
+ decorator = Decorator.new(double(present?: :delegated))
424
+
425
+ expect(decorator.present?).to be :delegated
426
+ end
427
+ end
428
+
429
+ describe '#blank?' do
430
+ it 'delegates to the object' do
431
+ decorator = Decorator.new(double(blank?: :delegated))
432
+
433
+ expect(decorator.blank?).to be :delegated
434
+ end
435
+ end
436
+
437
+ describe '#to_partial_path' do
438
+ it 'delegates to the object' do
439
+ decorator = Decorator.new(double(to_partial_path: :delegated))
440
+
441
+ expect(decorator.to_partial_path).to be :delegated
442
+ end
443
+ end
444
+
445
+ describe '#to_s' do
446
+ it 'delegates to the object' do
447
+ decorator = Decorator.new(double(to_s: :delegated))
448
+
449
+ expect(decorator.to_s).to be :delegated
450
+ end
451
+ end
452
+
453
+ describe '#inspect' do
454
+ it 'returns a detailed description of the decorator' do
455
+ decorator = ProductDecorator.new(double)
456
+
457
+ expect(decorator.inspect).to match /#<ProductDecorator:0x\h+ .+>/
458
+ end
459
+
460
+ it 'includes the object' do
461
+ decorator = Decorator.new(double(inspect: '#<the object>'))
462
+
463
+ expect(decorator.inspect).to include '@object=#<the object>'
464
+ end
465
+
466
+ it 'includes the context' do
467
+ decorator = Decorator.new(double, context: { foo: 'bar' })
468
+
469
+ expect(decorator.inspect).to include '@context={:foo=>"bar"}'
470
+ end
471
+
472
+ it 'includes other instance variables' do
473
+ decorator = Decorator.new(double)
474
+ decorator.instance_variable_set :@foo, 'bar'
475
+
476
+ expect(decorator.inspect).to include '@foo="bar"'
477
+ end
478
+ end
479
+
480
+ describe '#attributes' do
481
+ it "returns only the object's attributes that are implemented by the decorator" do
482
+ decorator = Decorator.new(double(attributes: { foo: 'bar', baz: 'qux' }))
483
+ decorator.stub(:foo)
484
+
485
+ expect(decorator.attributes).to eq({ foo: 'bar' })
486
+ end
487
+ end
488
+
489
+ describe '.model_name' do
490
+ it 'delegates to the source class' do
491
+ Decorator.stub object_class: double(model_name: :delegated)
492
+
493
+ expect(Decorator.model_name).to be :delegated
494
+ end
495
+ end
496
+
497
+ describe '#==' do
498
+ it 'works for a object that does not include Decoratable' do
499
+ object = Object.new
500
+ decorator = Decorator.new(object)
501
+
502
+ expect(decorator).to eq Decorator.new(object)
503
+ end
504
+
505
+ it 'works for a multiply-decorated object that does not include Decoratable' do
506
+ object = Object.new
507
+ decorator = Decorator.new(object)
508
+
509
+ expect(decorator).to eq ProductDecorator.new(Decorator.new(object))
510
+ end
511
+
512
+ it 'is true when object #== is true' do
513
+ object = Model.new
514
+ decorator = Decorator.new(object)
515
+ other = double(object: Model.new)
516
+
517
+ object.should_receive(:==).with(other).and_return(true)
518
+ expect(decorator == other).to be_truthy
519
+ end
520
+
521
+ it 'is false when object #== is false' do
522
+ object = Model.new
523
+ decorator = Decorator.new(object)
524
+ other = double(object: Model.new)
525
+
526
+ object.should_receive(:==).with(other).and_return(false)
527
+ expect(decorator == other).to be_falsey
528
+ end
529
+ end
530
+
531
+ describe '#===' do
532
+ it 'is true when #== is true' do
533
+ decorator = Decorator.new(Model.new)
534
+ allow(decorator).to receive(:==) { true }
535
+
536
+ expect(decorator === :anything).to be_truthy
537
+ end
538
+
539
+ it 'is false when #== is false' do
540
+ decorator = Decorator.new(Model.new)
541
+ decorator.stub(:==).with(:anything).and_return(false)
542
+
543
+ expect(decorator === :anything).to be_falsey
544
+ end
545
+ end
546
+
547
+ describe '#eql?' do
548
+ it 'is true when #eql? is true' do
549
+ first = Decorator.new('foo')
550
+ second = Decorator.new('foo')
551
+
552
+ expect(first.eql? second).to be
553
+ end
554
+
555
+ it 'is false when #eql? is false' do
556
+ first = Decorator.new('foo')
557
+ second = Decorator.new('bar')
558
+
559
+ expect(first.eql? second).to_not be
560
+ end
561
+ end
562
+
563
+ describe '#hash' do
564
+ it 'is consistent for equal objects' do
565
+ object = Model.new
566
+ first = Decorator.new(object)
567
+ second = Decorator.new(object)
568
+
569
+ expect(first.hash == second.hash).to be
570
+ end
571
+ end
572
+
573
+ describe '.delegate' do
574
+ protect_class Decorator
575
+
576
+ it 'defaults the :to option to :object' do
577
+ Object.should_receive(:delegate).with(:foo, :bar, to: :object)
578
+ Decorator.delegate :foo, :bar
579
+ end
580
+
581
+ it 'does not overwrite the :to option if supplied' do
582
+ Object.should_receive(:delegate).with(:foo, :bar, to: :baz)
583
+ Decorator.delegate :foo, :bar, to: :baz
584
+ end
585
+ end
586
+
587
+ context 'with .delegate_all' do
588
+ protect_class Decorator
589
+
590
+ before { Decorator.delegate_all }
591
+
592
+ describe '#method_missing' do
593
+ it 'delegates missing methods that exist on the object' do
594
+ decorator = Decorator.new(double(hello_world: :delegated))
595
+
596
+ expect(decorator.hello_world).to be :delegated
597
+ end
598
+
599
+ it 'adds delegated methods to the decorator when they are used' do
600
+ decorator = Decorator.new(double(hello_world: :delegated))
601
+
602
+ expect(decorator.methods).not_to include :hello_world
603
+ decorator.hello_world
604
+ expect(decorator.methods).to include :hello_world
605
+ end
606
+
607
+ it 'passes blocks to delegated methods' do
608
+ object = Model.new
609
+ object.stub(:hello_world) { |*_args, &block| block.call }
610
+ decorator = Decorator.new(object)
611
+
612
+ expect(decorator.hello_world { :yielded }).to be :yielded
613
+ end
614
+
615
+ it 'does not confuse Kernel#Array' do
616
+ decorator = Decorator.new(Model.new)
617
+
618
+ expect(Array(decorator)).to be_an Array
619
+ end
620
+
621
+ it 'delegates already-delegated methods' do
622
+ object = Class.new { delegate :bar, to: :foo }.new
623
+ object.stub foo: double(bar: :delegated)
624
+ decorator = Decorator.new(object)
625
+
626
+ expect(decorator.bar).to be :delegated
627
+ end
628
+
629
+ it 'does not delegate private methods' do
630
+ object = Class.new do
631
+ private
632
+
633
+ def hello_world; end
634
+ end.new
635
+ decorator = Decorator.new(object)
636
+
637
+ expect { decorator.hello_world }.to raise_error NoMethodError
638
+ end
639
+
640
+ it 'does not delegate methods that do not exist on the object' do
641
+ decorator = Decorator.new(Model.new)
642
+
643
+ expect(decorator.methods).not_to include :hello_world
644
+ expect { decorator.hello_world }.to raise_error NoMethodError
645
+ expect(decorator.methods).not_to include :hello_world
646
+ end
647
+ end
648
+
649
+ context '.method_missing' do
650
+ context 'without a source class' do
651
+ it 'raises a NoMethodError on missing methods' do
652
+ expect { Decorator.hello_world }.to raise_error NoMethodError
653
+ end
654
+ end
655
+
656
+ context 'with a source class' do
657
+ it 'delegates methods that exist on the source class' do
658
+ object_class = Class.new
659
+ object_class.stub hello_world: :delegated
660
+ Decorator.stub object_class: object_class
661
+
662
+ expect(Decorator.hello_world).to be :delegated
663
+ end
664
+
665
+ it 'does not delegate methods that do not exist on the source class' do
666
+ Decorator.stub object_class: Class.new
667
+
668
+ expect { Decorator.hello_world }.to raise_error NoMethodError
669
+ end
670
+ end
671
+ end
672
+
673
+ describe '#respond_to?' do
674
+ it 'returns true for its own methods' do
675
+ Decorator.class_eval { def hello_world; end }
676
+ decorator = Decorator.new(Model.new)
677
+
678
+ expect(decorator).to respond_to :hello_world
679
+ end
680
+
681
+ it "returns true for the object's methods" do
682
+ decorator = Decorator.new(double(hello_world: :delegated))
683
+
684
+ expect(decorator).to respond_to :hello_world
685
+ end
686
+
687
+ context 'with include_private' do
688
+ it 'returns true for its own private methods' do
689
+ Decorator.class_eval do
690
+ private
691
+
692
+ def hello_world; end
693
+ end
694
+ decorator = Decorator.new(Model.new)
695
+
696
+ expect(decorator.respond_to?(:hello_world, true)).to be_truthy
697
+ end
698
+
699
+ it "returns false for the object's private methods" do
700
+ object = Class.new do
701
+ private
702
+
703
+ def hello_world; end
704
+ end.new
705
+ decorator = Decorator.new(object)
706
+
707
+ expect(decorator.respond_to?(:hello_world, true)).to be_falsey
708
+ end
709
+ end
710
+ end
711
+
712
+ describe '.respond_to?' do
713
+ context 'without a source class' do
714
+ it 'returns true for its own class methods' do
715
+ Decorator.class_eval { def self.hello_world; end }
716
+
717
+ expect(Decorator).to respond_to :hello_world
718
+ end
719
+
720
+ it 'returns false for other class methods' do
721
+ expect(Decorator).not_to respond_to :goodnight_moon
722
+ end
723
+ end
724
+
725
+ context 'with a source class' do
726
+ it 'returns true for its own class methods' do
727
+ Decorator.class_eval { def self.hello_world; end }
728
+ Decorator.stub object_class: Class.new
729
+
730
+ expect(Decorator).to respond_to :hello_world
731
+ end
732
+
733
+ it "returns true for the source's class methods" do
734
+ Decorator.stub object_class: double(hello_world: :delegated)
735
+
736
+ expect(Decorator).to respond_to :hello_world
737
+ end
738
+ end
739
+ end
740
+
741
+ describe '#respond_to_missing?' do
742
+ it 'allows #method to be called on delegated methods' do
743
+ object = Class.new { def hello_world; end }.new
744
+ decorator = Decorator.new(object)
745
+
746
+ expect(decorator.method(:hello_world)).not_to be_nil
747
+ end
748
+ end
749
+
750
+ describe '.respond_to_missing?' do
751
+ it 'allows .method to be called on delegated class methods' do
752
+ Decorator.stub object_class: double(hello_world: :delegated)
753
+
754
+ expect(Decorator.method(:hello_world)).not_to be_nil
755
+ end
756
+ end
757
+ end
758
+
759
+ describe 'class spoofing' do
760
+ it 'pretends to be a kind of the source class' do
761
+ decorator = Decorator.new(Model.new)
762
+
763
+ expect(decorator.is_a?(Model)).to be_truthy
764
+ expect(decorator.is_a?(Model)).to be_truthy
765
+ end
766
+
767
+ it 'is still a kind of its own class' do
768
+ decorator = Decorator.new(Model.new)
769
+
770
+ expect(decorator.is_a?(Decorator)).to be_truthy
771
+ expect(decorator.is_a?(Decorator)).to be_truthy
772
+ end
773
+
774
+ it 'pretends to be an instance of the source class' do
775
+ decorator = Decorator.new(Model.new)
776
+
777
+ expect(decorator.instance_of?(Model)).to be_truthy
778
+ end
779
+
780
+ it 'is still an instance of its own class' do
781
+ decorator = Decorator.new(Model.new)
782
+
783
+ expect(decorator.instance_of?(Decorator)).to be_truthy
784
+ end
785
+ end
786
+
787
+ describe '.decorates_finders' do
788
+ protect_class Decorator
789
+
790
+ it 'extends the Finders module' do
791
+ expect(Decorator).not_to be_a_kind_of Finders
792
+ Decorator.decorates_finders
793
+ expect(Decorator).to be_a_kind_of Finders
794
+ end
795
+ end
796
+
797
+ describe 'Enumerable hash and equality functionality' do
798
+ describe '#uniq' do
799
+ it 'removes duplicate objects with same decorator' do
800
+ object = Model.new
801
+ array = [Decorator.new(object), Decorator.new(object)]
802
+
803
+ expect(array.uniq.count).to eq(1)
804
+ end
805
+
806
+ it 'separates different objects with identical decorators' do
807
+ array = [Decorator.new('foo'), Decorator.new('bar')]
808
+
809
+ expect(array.uniq.count).to eq(2)
810
+ end
811
+
812
+ it 'separates identical objects with different decorators' do
813
+ object = Model.new
814
+ array = [Decorator.new(object), OtherDecorator.new(object)]
815
+
816
+ expect(array.uniq.count).to eq(2)
817
+ end
818
+
819
+ it 'distinguishes between an objects and its decorated version' do
820
+ object = Model.new
821
+ array = [Decorator.new(object), object]
822
+
823
+ expect(array.uniq.count).to eq(2)
824
+ end
825
+ end
826
+ end
827
+ end
828
+ end