draper 0.17.0 → 1.0.0.beta2

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 (154) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +5 -0
  3. data/CHANGELOG.markdown +49 -0
  4. data/Gemfile +4 -0
  5. data/Rakefile +50 -31
  6. data/Readme.markdown +63 -92
  7. data/draper.gemspec +6 -4
  8. data/lib/draper/collection_decorator.rb +88 -0
  9. data/lib/draper/decoratable.rb +44 -0
  10. data/lib/draper/decorated_association.rb +55 -0
  11. data/lib/draper/decorator.rb +260 -0
  12. data/lib/draper/finders.rb +34 -0
  13. data/lib/draper/helper_proxy.rb +16 -0
  14. data/lib/draper/railtie.rb +17 -21
  15. data/lib/draper/security.rb +48 -0
  16. data/lib/draper/tasks/tu.rake +5 -0
  17. data/lib/draper/test/minitest_integration.rb +5 -24
  18. data/lib/draper/test/rspec_integration.rb +0 -6
  19. data/lib/draper/test/test_unit_integration.rb +9 -0
  20. data/lib/draper/version.rb +1 -1
  21. data/lib/draper/view_context.rb +24 -6
  22. data/lib/draper/view_helpers.rb +36 -0
  23. data/lib/draper.rb +48 -7
  24. data/lib/generators/decorator/decorator_generator.rb +1 -1
  25. data/lib/generators/decorator/templates/decorator.rb +0 -1
  26. data/lib/generators/rspec/decorator_generator.rb +1 -1
  27. data/lib/generators/test_unit/decorator_generator.rb +1 -1
  28. data/lib/generators/test_unit/templates/decorator_test.rb +0 -3
  29. data/spec/draper/collection_decorator_spec.rb +240 -0
  30. data/spec/draper/decoratable_spec.rb +164 -0
  31. data/spec/draper/decorated_association_spec.rb +130 -0
  32. data/spec/draper/decorator_spec.rb +622 -0
  33. data/spec/draper/finders_spec.rb +145 -0
  34. data/spec/draper/helper_proxy_spec.rb +12 -0
  35. data/spec/draper/security_spec.rb +158 -0
  36. data/spec/draper/view_helpers_spec.rb +41 -0
  37. data/spec/dummy/.rspec +2 -0
  38. data/spec/dummy/README.rdoc +261 -0
  39. data/spec/dummy/Rakefile +7 -0
  40. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  41. data/spec/dummy/app/controllers/localized_urls.rb +5 -0
  42. data/spec/dummy/app/controllers/posts_controller.rb +17 -0
  43. data/spec/dummy/app/decorators/post_decorator.rb +29 -0
  44. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  45. data/spec/dummy/app/mailers/application_mailer.rb +3 -0
  46. data/spec/dummy/app/mailers/post_mailer.rb +9 -0
  47. data/spec/dummy/app/models/post.rb +3 -0
  48. data/spec/dummy/app/views/layouts/application.html.erb +11 -0
  49. data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +1 -0
  50. data/spec/dummy/app/views/posts/_post.html.erb +19 -0
  51. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  52. data/spec/dummy/config/application.rb +64 -0
  53. data/spec/dummy/config/boot.rb +10 -0
  54. data/spec/dummy/config/database.yml +25 -0
  55. data/spec/dummy/config/environment.rb +5 -0
  56. data/spec/dummy/config/environments/development.rb +34 -0
  57. data/spec/dummy/config/environments/production.rb +55 -0
  58. data/spec/dummy/config/environments/test.rb +32 -0
  59. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/spec/dummy/config/initializers/inflections.rb +15 -0
  61. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  62. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  63. data/spec/dummy/config/initializers/session_store.rb +8 -0
  64. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  65. data/spec/dummy/config/locales/en.yml +5 -0
  66. data/spec/dummy/config/routes.rb +7 -0
  67. data/spec/dummy/config.ru +4 -0
  68. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
  69. data/spec/dummy/db/schema.rb +21 -0
  70. data/spec/dummy/db/seeds.rb +2 -0
  71. data/spec/dummy/lib/tasks/spec.rake +5 -0
  72. data/spec/dummy/log/.gitkeep +0 -0
  73. data/spec/dummy/public/404.html +26 -0
  74. data/spec/dummy/public/422.html +26 -0
  75. data/spec/dummy/public/500.html +25 -0
  76. data/spec/dummy/public/favicon.ico +0 -0
  77. data/spec/dummy/script/rails +6 -0
  78. data/spec/dummy/spec/decorators/post_decorator_spec.rb +26 -0
  79. data/spec/dummy/spec/mailers/post_mailer_spec.rb +29 -0
  80. data/spec/dummy/spec/spec_helper.rb +9 -0
  81. data/spec/generators/decorator/decorator_generator_spec.rb +25 -6
  82. data/spec/integration/integration_spec.rb +33 -0
  83. data/spec/minitest-rails/spec_type_spec.rb +63 -0
  84. data/{performance → spec/performance}/decorators.rb +2 -4
  85. data/spec/spec_helper.rb +23 -26
  86. data/spec/support/action_controller.rb +12 -0
  87. data/spec/support/active_model.rb +7 -0
  88. data/spec/support/{samples/active_record.rb → active_record.rb} +5 -0
  89. data/spec/support/{samples → decorators}/decorator_with_application_helper.rb +1 -1
  90. data/spec/support/decorators/namespaced_product_decorator.rb +5 -0
  91. data/spec/support/decorators/non_active_model_product_decorator.rb +2 -0
  92. data/spec/support/decorators/product_decorator.rb +23 -0
  93. data/spec/support/decorators/products_decorator.rb +10 -0
  94. data/spec/support/decorators/some_thing_decorator.rb +2 -0
  95. data/spec/support/{samples → decorators}/specific_product_decorator.rb +0 -2
  96. data/spec/support/dummy_app.rb +84 -0
  97. data/spec/support/matchers/have_text.rb +50 -0
  98. data/spec/support/{samples → models}/namespaced_product.rb +1 -3
  99. data/spec/support/{samples → models}/non_active_model_product.rb +1 -0
  100. data/spec/support/{samples → models}/product.rb +17 -2
  101. data/spec/support/models/some_thing.rb +5 -0
  102. data/spec/support/models/uninferrable_decorator_model.rb +3 -0
  103. metadata +211 -102
  104. data/doc/ApplicationDecorator.html +0 -147
  105. data/doc/Draper/AllHelpers.html +0 -256
  106. data/doc/Draper/Base.html +0 -1222
  107. data/doc/Draper/DecoratorGenerator.html +0 -216
  108. data/doc/Draper/LazyHelpers.html +0 -174
  109. data/doc/Draper/ModelSupport.html +0 -164
  110. data/doc/Draper/System.html +0 -179
  111. data/doc/Draper.html +0 -123
  112. data/doc/_index.html +0 -172
  113. data/doc/class_list.html +0 -47
  114. data/doc/css/common.css +0 -1
  115. data/doc/css/full_list.css +0 -53
  116. data/doc/css/style.css +0 -320
  117. data/doc/file_list.html +0 -46
  118. data/doc/frames.html +0 -13
  119. data/doc/index.html +0 -172
  120. data/doc/js/app.js +0 -205
  121. data/doc/js/full_list.js +0 -150
  122. data/doc/js/jquery.js +0 -16
  123. data/doc/method_list.html +0 -174
  124. data/doc/top-level-namespace.html +0 -103
  125. data/lib/draper/active_model_support.rb +0 -27
  126. data/lib/draper/base.rb +0 -312
  127. data/lib/draper/decorated_enumerable_proxy.rb +0 -57
  128. data/lib/draper/model_support.rb +0 -17
  129. data/lib/draper/rspec_integration.rb +0 -2
  130. data/lib/draper/system.rb +0 -10
  131. data/lib/draper/test/view_context.rb +0 -12
  132. data/spec/draper/base_spec.rb +0 -863
  133. data/spec/draper/helper_support_spec.rb +0 -18
  134. data/spec/draper/model_support_spec.rb +0 -48
  135. data/spec/draper/view_context_spec.rb +0 -30
  136. data/spec/support/samples/active_model.rb +0 -9
  137. data/spec/support/samples/application_controller.rb +0 -42
  138. data/spec/support/samples/application_helper.rb +0 -8
  139. data/spec/support/samples/decorator.rb +0 -5
  140. data/spec/support/samples/decorator_with_allows.rb +0 -3
  141. data/spec/support/samples/decorator_with_denies.rb +0 -3
  142. data/spec/support/samples/decorator_with_denies_all.rb +0 -3
  143. data/spec/support/samples/decorator_with_multiple_allows.rb +0 -4
  144. data/spec/support/samples/decorator_with_special_methods.rb +0 -13
  145. data/spec/support/samples/namespaced_product_decorator.rb +0 -7
  146. data/spec/support/samples/product_decorator.rb +0 -7
  147. data/spec/support/samples/sequel_product.rb +0 -13
  148. data/spec/support/samples/some_thing.rb +0 -2
  149. data/spec/support/samples/some_thing_decorator.rb +0 -3
  150. /data/{performance → spec/performance}/active_record.rb +0 -0
  151. /data/{performance/bechmark.rb → spec/performance/benchmark.rb} +0 -0
  152. /data/{performance → spec/performance}/models.rb +0 -0
  153. /data/spec/support/{samples → decorators}/widget_decorator.rb +0 -0
  154. /data/spec/support/{samples → models}/widget.rb +0 -0
@@ -0,0 +1,622 @@
1
+ require 'spec_helper'
2
+
3
+ describe Draper::Decorator do
4
+ before { ApplicationController.new.view_context }
5
+ subject { decorator_class.new(source) }
6
+ let(:decorator_class) { Draper::Decorator }
7
+ let(:source) { Product.new }
8
+
9
+ describe "#initialize" do
10
+ it "sets the source" do
11
+ subject.source.should be source
12
+ end
13
+
14
+ it "stores options" do
15
+ decorator = decorator_class.new(source, some: "options")
16
+ decorator.options.should == {some: "options"}
17
+ end
18
+
19
+ context "when decorating an instance of itself" do
20
+ it "does not redecorate" do
21
+ decorator = ProductDecorator.new(source)
22
+ ProductDecorator.new(decorator).source.should be source
23
+ end
24
+
25
+ context "when options are supplied" do
26
+ it "overwrites existing options" do
27
+ decorator = ProductDecorator.new(source, role: :admin)
28
+ ProductDecorator.new(decorator, role: :user).options.should == {role: :user}
29
+ end
30
+ end
31
+
32
+ context "when no options are supplied" do
33
+ it "preserves existing options" do
34
+ decorator = ProductDecorator.new(source, role: :admin)
35
+ ProductDecorator.new(decorator).options.should == {role: :admin}
36
+ end
37
+ end
38
+ end
39
+
40
+ it "decorates other decorators" do
41
+ decorator = ProductDecorator.new(source)
42
+ SpecificProductDecorator.new(decorator).source.should be decorator
43
+ end
44
+
45
+ it "warns if target is already decorated with the same decorator class" do
46
+ warning_message = nil
47
+ Object.any_instance.stub(:warn) { |message| warning_message = message }
48
+
49
+ deep_decorator = SpecificProductDecorator.new(ProductDecorator.new(Product.new))
50
+ expect {
51
+ ProductDecorator.new(deep_decorator)
52
+ }.to change { warning_message }
53
+ warning_message.should =~ /ProductDecorator/
54
+ warning_message.should include caller(1).first
55
+ end
56
+ end
57
+
58
+ describe ".decorate_collection" do
59
+ subject { ProductDecorator.decorate_collection(source) }
60
+ let(:source) { [Product.new, Widget.new] }
61
+
62
+ it "returns a collection decorator" do
63
+ subject.should be_a Draper::CollectionDecorator
64
+ subject.source.should be source
65
+ end
66
+
67
+ it "uses itself as the item decorator by default" do
68
+ subject.each {|item| item.should be_a ProductDecorator}
69
+ end
70
+
71
+ context "when given :with => :infer" do
72
+ subject { ProductDecorator.decorate_collection(source, with: :infer) }
73
+
74
+ it "infers the item decorators" do
75
+ subject.first.should be_a ProductDecorator
76
+ subject.last.should be_a WidgetDecorator
77
+ end
78
+ end
79
+
80
+ context "with options" do
81
+ subject { ProductDecorator.decorate_collection(source, with: :infer, some: "options") }
82
+
83
+ it "passes the options to the collection decorator" do
84
+ subject.options.should == {some: "options"}
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#helpers" do
90
+ it "returns a HelperProxy" do
91
+ subject.helpers.should be_a Draper::HelperProxy
92
+ end
93
+
94
+ it "is aliased to #h" do
95
+ subject.h.should be subject.helpers
96
+ end
97
+
98
+ it "initializes the wrapper only once" do
99
+ helper_proxy = subject.helpers
100
+ helper_proxy.stub(:test_method) { "test_method" }
101
+ subject.helpers.test_method.should == "test_method"
102
+ subject.helpers.test_method.should == "test_method"
103
+ end
104
+ end
105
+
106
+ describe "#localize" do
107
+ before { subject.helpers.should_receive(:localize).with(:an_object, {some: "options"}) }
108
+
109
+ it "delegates to #helpers" do
110
+ subject.localize(:an_object, some: "options")
111
+ end
112
+
113
+ it "is aliased to #l" do
114
+ subject.l(:an_object, some: "options")
115
+ end
116
+ end
117
+
118
+ describe ".helpers" do
119
+ it "returns a HelperProxy" do
120
+ subject.class.helpers.should be_a Draper::HelperProxy
121
+ end
122
+
123
+ it "is aliased to .h" do
124
+ subject.class.h.should be subject.class.helpers
125
+ end
126
+ end
127
+
128
+ describe ".decorates" do
129
+ subject { Class.new(Draper::Decorator) }
130
+
131
+ context "with a symbol" do
132
+ it "sets .source_class" do
133
+ subject.decorates :product
134
+ subject.source_class.should be Product
135
+ end
136
+ end
137
+
138
+ context "with a string" do
139
+ it "sets .source_class" do
140
+ subject.decorates "product"
141
+ subject.source_class.should be Product
142
+ end
143
+ end
144
+
145
+ context "with a class" do
146
+ it "sets .source_class" do
147
+ subject.decorates Product
148
+ subject.source_class.should be Product
149
+ end
150
+ end
151
+ end
152
+
153
+ describe ".source_class" do
154
+ context "when not set by .decorates" do
155
+ context "for an anonymous decorator" do
156
+ subject { Class.new(Draper::Decorator) }
157
+
158
+ it "raises an UninferrableSourceError" do
159
+ expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
160
+ end
161
+ end
162
+
163
+ context "for a decorator without a corresponding source" do
164
+ subject { SpecificProductDecorator }
165
+
166
+ it "raises an UninferrableSourceError" do
167
+ expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
168
+ end
169
+ end
170
+
171
+ context "for a decorator called Decorator" do
172
+ subject { Draper::Decorator }
173
+
174
+ it "raises an UninferrableSourceError" do
175
+ expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
176
+ end
177
+ end
178
+
179
+ context "for a decorator with a name not ending in Decorator" do
180
+ subject { DecoratorWithApplicationHelper }
181
+
182
+ it "raises an UninferrableSourceError" do
183
+ expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
184
+ end
185
+ end
186
+
187
+ context "for an inferrable source" do
188
+ subject { ProductDecorator }
189
+
190
+ it "infers the source" do
191
+ subject.source_class.should be Product
192
+ end
193
+ end
194
+
195
+ context "for a namespaced inferrable source" do
196
+ subject { Namespace::ProductDecorator }
197
+
198
+ it "infers the namespaced source" do
199
+ subject.source_class.should be Namespace::Product
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ describe ".source_class?" do
206
+ subject { Class.new(Draper::Decorator) }
207
+
208
+ it "returns truthy when .source_class is set" do
209
+ subject.stub(:source_class).and_return(Product)
210
+ subject.source_class?.should be_true
211
+ end
212
+
213
+ it "returns false when .source_class is not inferrable" do
214
+ subject.stub(:source_class).and_raise(Draper::UninferrableSourceError.new(subject))
215
+ subject.source_class?.should be_false
216
+ end
217
+ end
218
+
219
+ describe ".decorates_association" do
220
+ let(:decorator_class) { Class.new(ProductDecorator) }
221
+ before { decorator_class.decorates_association :similar_products, with: ProductDecorator }
222
+
223
+ describe "overridden association method" do
224
+ let(:decorated_association) { ->{} }
225
+
226
+ it "creates a DecoratedAssociation" do
227
+ Draper::DecoratedAssociation.should_receive(:new).with(source, :similar_products, {with: ProductDecorator}).and_return(decorated_association)
228
+ subject.similar_products
229
+ end
230
+
231
+ it "memoizes the DecoratedAssociation" do
232
+ Draper::DecoratedAssociation.should_receive(:new).once.and_return(decorated_association)
233
+ subject.similar_products
234
+ subject.similar_products
235
+ end
236
+
237
+ it "calls the DecoratedAssociation" do
238
+ Draper::DecoratedAssociation.stub(:new).and_return(decorated_association)
239
+ decorated_association.should_receive(:call).and_return(:decorated)
240
+ subject.similar_products.should be :decorated
241
+ end
242
+ end
243
+ end
244
+
245
+ describe ".decorates_associations" do
246
+ subject { decorator_class }
247
+
248
+ it "decorates each of the associations" do
249
+ subject.should_receive(:decorates_association).with(:similar_products, {})
250
+ subject.should_receive(:decorates_association).with(:previous_version, {})
251
+
252
+ subject.decorates_associations :similar_products, :previous_version
253
+ end
254
+
255
+ it "dispatches options" do
256
+ subject.should_receive(:decorates_association).with(:similar_products, {with: ProductDecorator})
257
+ subject.should_receive(:decorates_association).with(:previous_version, {with: ProductDecorator})
258
+
259
+ subject.decorates_associations :similar_products, :previous_version, with: ProductDecorator
260
+ end
261
+ end
262
+
263
+ describe "#applied_decorators" do
264
+ it "returns a list of decorators applied to a model" do
265
+ decorator = ProductDecorator.new(SpecificProductDecorator.new(Product.new))
266
+ decorator.applied_decorators.should == [SpecificProductDecorator, ProductDecorator]
267
+ end
268
+ end
269
+
270
+ describe "#decorated_with?" do
271
+ it "checks if a decorator has been applied to a model" do
272
+ decorator = ProductDecorator.new(SpecificProductDecorator.new(Product.new))
273
+ decorator.should be_decorated_with ProductDecorator
274
+ decorator.should be_decorated_with SpecificProductDecorator
275
+ decorator.should_not be_decorated_with WidgetDecorator
276
+ end
277
+ end
278
+
279
+ describe "#decorated?" do
280
+ it "returns true" do
281
+ subject.should be_decorated
282
+ end
283
+ end
284
+
285
+ describe "#source" do
286
+ it "returns the wrapped object" do
287
+ subject.source.should be source
288
+ end
289
+
290
+ it "is aliased to #to_source" do
291
+ subject.to_source.should be source
292
+ end
293
+
294
+ it "is aliased to #model" do
295
+ subject.model.should be source
296
+ end
297
+ end
298
+
299
+ describe "#to_model" do
300
+ it "returns the decorator" do
301
+ subject.to_model.should be subject
302
+ end
303
+ end
304
+
305
+ describe "#to_param" do
306
+ it "proxies to the source" do
307
+ source.stub(:to_param).and_return(42)
308
+ subject.to_param.should == 42
309
+ end
310
+ end
311
+
312
+ describe "#==" do
313
+ context "with itself" do
314
+ it "returns true" do
315
+ (subject == subject).should be_true
316
+ end
317
+ end
318
+
319
+ context "with another decorator having the same source" do
320
+ it "returns true" do
321
+ (subject == ProductDecorator.new(source)).should be_true
322
+ end
323
+ end
324
+
325
+ context "with another decorator having a different source" do
326
+ it "returns false" do
327
+ (subject == ProductDecorator.new(Object.new)).should be_false
328
+ end
329
+ end
330
+
331
+ context "with the source object" do
332
+ it "returns true" do
333
+ (subject == source).should be_true
334
+ end
335
+ end
336
+
337
+ context "with another object" do
338
+ it "returns false" do
339
+ (subject == Object.new).should be_false
340
+ end
341
+ end
342
+ end
343
+
344
+ describe "#===" do
345
+ context "with itself" do
346
+ it "returns true" do
347
+ (subject === subject).should be_true
348
+ end
349
+ end
350
+
351
+ context "with another decorator having the same source" do
352
+ it "returns true" do
353
+ (subject === ProductDecorator.new(source)).should be_true
354
+ end
355
+ end
356
+
357
+ context "with another decorator having a different source" do
358
+ it "returns false" do
359
+ (subject === ProductDecorator.new(Object.new)).should be_false
360
+ end
361
+ end
362
+
363
+ context "with the source object" do
364
+ it "returns true" do
365
+ (subject === source).should be_true
366
+ end
367
+ end
368
+
369
+ context "with another object" do
370
+ it "returns false" do
371
+ (subject === Object.new).should be_false
372
+ end
373
+ end
374
+ end
375
+
376
+ describe "#respond_to?" do
377
+ let(:decorator_class) { Class.new(ProductDecorator) }
378
+
379
+ it "returns true for its own methods" do
380
+ subject.should respond_to :awesome_title
381
+ end
382
+
383
+ it "returns true for the source's methods" do
384
+ subject.should respond_to :title
385
+ end
386
+
387
+ context "with include_private" do
388
+ it "returns true for its own private methods" do
389
+ subject.respond_to?(:awesome_private_title, true).should be_true
390
+ end
391
+
392
+ it "returns false for the source's private methods" do
393
+ subject.respond_to?(:private_title, true).should be_false
394
+ end
395
+ end
396
+
397
+ context "with method security" do
398
+ it "respects allows" do
399
+ subject.class.allows :hello_world
400
+
401
+ subject.should respond_to :hello_world
402
+ subject.should_not respond_to :goodnight_moon
403
+ end
404
+
405
+ it "respects denies" do
406
+ subject.class.denies :goodnight_moon
407
+
408
+ subject.should respond_to :hello_world
409
+ subject.should_not respond_to :goodnight_moon
410
+ end
411
+
412
+ it "respects denies_all" do
413
+ subject.class.denies_all
414
+
415
+ subject.should_not respond_to :hello_world
416
+ subject.should_not respond_to :goodnight_moon
417
+ end
418
+ end
419
+ end
420
+
421
+ describe ".respond_to?" do
422
+ subject { Class.new(ProductDecorator) }
423
+
424
+ context "without a source class" do
425
+ it "returns true for its own class methods" do
426
+ subject.should respond_to :my_class_method
427
+ end
428
+
429
+ it "returns false for other class methods" do
430
+ subject.should_not respond_to :sample_class_method
431
+ end
432
+ end
433
+
434
+ context "with a source_class" do
435
+ before { subject.decorates :product }
436
+
437
+ it "returns true for its own class methods" do
438
+ subject.should respond_to :my_class_method
439
+ end
440
+
441
+ it "returns true for the source's class methods" do
442
+ subject.should respond_to :sample_class_method
443
+ end
444
+ end
445
+ end
446
+
447
+ describe "proxying" do
448
+ context "instance methods" do
449
+ let(:decorator_class) { Class.new(ProductDecorator) }
450
+
451
+ it "does not proxy methods that are defined on the decorator" do
452
+ subject.overridable.should be :overridden
453
+ end
454
+
455
+ it "does not proxy methods inherited from Object" do
456
+ subject.inspect.should_not be source.inspect
457
+ end
458
+
459
+ it "proxies missing methods that exist on the source" do
460
+ source.stub(:hello_world).and_return(:proxied)
461
+ subject.hello_world.should be :proxied
462
+ end
463
+
464
+ it "adds proxied methods to the decorator when they are used" do
465
+ subject.methods.should_not include :hello_world
466
+ subject.hello_world
467
+ subject.methods.should include :hello_world
468
+ end
469
+
470
+ it "passes blocks to proxied methods" do
471
+ subject.block{"marker"}.should == "marker"
472
+ end
473
+
474
+ it "does not confuse Kernel#Array" do
475
+ Array(subject).should be_a Array
476
+ end
477
+
478
+ it "proxies delegated methods" do
479
+ subject.delegated_method.should == "Yay, delegation"
480
+ end
481
+
482
+ it "does not proxy private methods" do
483
+ expect{subject.private_title}.to raise_error NoMethodError
484
+ end
485
+
486
+ context "with method security" do
487
+ it "respects allows" do
488
+ source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
489
+ subject.class.allows :hello_world
490
+
491
+ subject.hello_world.should be :proxied
492
+ expect{subject.goodnight_moon}.to raise_error NameError
493
+ end
494
+
495
+ it "respects denies" do
496
+ source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
497
+ subject.class.denies :goodnight_moon
498
+
499
+ subject.hello_world.should be :proxied
500
+ expect{subject.goodnight_moon}.to raise_error NameError
501
+ end
502
+
503
+ it "respects denies_all" do
504
+ source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
505
+ subject.class.denies_all
506
+
507
+ expect{subject.hello_world}.to raise_error NameError
508
+ expect{subject.goodnight_moon}.to raise_error NameError
509
+ end
510
+ end
511
+ end
512
+
513
+ context "class methods" do
514
+ subject { Class.new(ProductDecorator) }
515
+ let(:source_class) { Product }
516
+ before { subject.decorates source_class }
517
+
518
+ it "does not proxy methods that are defined on the decorator" do
519
+ subject.overridable.should be :overridden
520
+ end
521
+
522
+ it "proxies missing methods that exist on the source" do
523
+ source_class.stub(:hello_world).and_return(:proxied)
524
+ subject.hello_world.should be :proxied
525
+ end
526
+ end
527
+ end
528
+
529
+ describe "method security" do
530
+ subject(:decorator_class) { Draper::Decorator }
531
+ let(:security) { stub }
532
+ before { decorator_class.stub(:security).and_return(security) }
533
+
534
+ it "delegates .denies to Draper::Security" do
535
+ security.should_receive(:denies).with(:foo, :bar)
536
+ decorator_class.denies :foo, :bar
537
+ end
538
+
539
+ it "delegates .denies_all to Draper::Security" do
540
+ security.should_receive(:denies_all)
541
+ decorator_class.denies_all
542
+ end
543
+
544
+ it "delegates .allows to Draper::Security" do
545
+ security.should_receive(:allows).with(:foo, :bar)
546
+ decorator_class.allows :foo, :bar
547
+ end
548
+ end
549
+
550
+ context "in a Rails application" do
551
+ let(:decorator_class) { DecoratorWithApplicationHelper }
552
+
553
+ it "has access to ApplicationHelper helpers" do
554
+ subject.uses_hello_world.should == "Hello, World!"
555
+ end
556
+
557
+ it "is able to use the content_tag helper" do
558
+ subject.sample_content.to_s.should == "<span>Hello, World!</span>"
559
+ end
560
+
561
+ it "is able to use the link_to helper" do
562
+ subject.sample_link.should == %{<a href="/World">Hello</a>}
563
+ end
564
+
565
+ it "is able to use the truncate helper" do
566
+ subject.sample_truncate.should == "Once..."
567
+ end
568
+
569
+ it "is able to access html_escape, a private method" do
570
+ subject.sample_html_escaped_text.should == '&lt;script&gt;danger&lt;/script&gt;'
571
+ end
572
+ end
573
+
574
+ it "pretends to be the source class" do
575
+ subject.kind_of?(source.class).should be_true
576
+ subject.is_a?(source.class).should be_true
577
+ end
578
+
579
+ it "is still its own class" do
580
+ subject.kind_of?(subject.class).should be_true
581
+ subject.is_a?(subject.class).should be_true
582
+ end
583
+
584
+ describe ".decorates_finders" do
585
+ it "extends the Finders module" do
586
+ ProductDecorator.should be_a_kind_of Draper::Finders
587
+ end
588
+ end
589
+
590
+ describe "#serializable_hash" do
591
+ let(:decorator_class) { ProductDecorator }
592
+
593
+ it "serializes overridden attributes" do
594
+ subject.serializable_hash[:overridable].should be :overridden
595
+ end
596
+ end
597
+
598
+ describe ".method_missing" do
599
+ context "when called on an anonymous decorator" do
600
+ subject { ->{ Class.new(Draper::Decorator).fizzbuzz } }
601
+ it { should raise_error NoMethodError }
602
+ end
603
+
604
+ context "when called on an uninferrable decorator" do
605
+ subject { ->{ SpecificProductDecorator.fizzbuzz } }
606
+ it { should raise_error NoMethodError }
607
+ end
608
+
609
+ context "when called on an inferrable decorator" do
610
+ context "for a method known to the inferred class" do
611
+ subject { ->{ ProductDecorator.model_name } }
612
+ it { should_not raise_error }
613
+ end
614
+
615
+ context "for a method unknown to the inferred class" do
616
+ subject { ->{ ProductDecorator.fizzbuzz } }
617
+ it { should raise_error NoMethodError }
618
+ end
619
+ end
620
+ end
621
+
622
+ end