draper 0.18.0 → 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 (123) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +5 -0
  3. data/CHANGELOG.markdown +8 -0
  4. data/Gemfile +4 -0
  5. data/Rakefile +50 -31
  6. data/Readme.markdown +42 -48
  7. data/draper.gemspec +2 -1
  8. data/lib/draper.rb +42 -7
  9. data/lib/draper/collection_decorator.rb +88 -0
  10. data/lib/draper/decoratable.rb +44 -0
  11. data/lib/draper/decorated_association.rb +55 -0
  12. data/lib/draper/decorator.rb +208 -0
  13. data/lib/draper/finders.rb +44 -0
  14. data/lib/draper/helper_proxy.rb +16 -0
  15. data/lib/draper/railtie.rb +17 -21
  16. data/lib/draper/security.rb +48 -0
  17. data/lib/draper/tasks/tu.rake +5 -0
  18. data/lib/draper/test/minitest_integration.rb +1 -1
  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 +6 -13
  22. data/lib/draper/view_helpers.rb +36 -0
  23. data/lib/generators/decorator/decorator_generator.rb +1 -1
  24. data/lib/generators/decorator/templates/decorator.rb +0 -1
  25. data/spec/draper/collection_decorator_spec.rb +240 -0
  26. data/spec/draper/decoratable_spec.rb +164 -0
  27. data/spec/draper/decorated_association_spec.rb +130 -0
  28. data/spec/draper/decorator_spec.rb +497 -0
  29. data/spec/draper/finders_spec.rb +156 -0
  30. data/spec/draper/helper_proxy_spec.rb +12 -0
  31. data/spec/draper/security_spec.rb +158 -0
  32. data/spec/draper/view_helpers_spec.rb +41 -0
  33. data/spec/dummy/.rspec +2 -0
  34. data/spec/dummy/README.rdoc +261 -0
  35. data/spec/dummy/Rakefile +7 -0
  36. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  37. data/spec/dummy/app/controllers/localized_urls.rb +5 -0
  38. data/spec/dummy/app/controllers/posts_controller.rb +17 -0
  39. data/spec/dummy/app/decorators/post_decorator.rb +25 -0
  40. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  41. data/spec/dummy/app/mailers/application_mailer.rb +3 -0
  42. data/spec/dummy/app/mailers/post_mailer.rb +9 -0
  43. data/spec/dummy/app/models/post.rb +3 -0
  44. data/spec/dummy/app/views/layouts/application.html.erb +11 -0
  45. data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +1 -0
  46. data/spec/dummy/app/views/posts/_post.html.erb +19 -0
  47. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  48. data/spec/dummy/config.ru +4 -0
  49. data/spec/dummy/config/application.rb +64 -0
  50. data/spec/dummy/config/boot.rb +10 -0
  51. data/spec/dummy/config/database.yml +25 -0
  52. data/spec/dummy/config/environment.rb +5 -0
  53. data/spec/dummy/config/environments/development.rb +34 -0
  54. data/spec/dummy/config/environments/production.rb +55 -0
  55. data/spec/dummy/config/environments/test.rb +32 -0
  56. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  57. data/spec/dummy/config/initializers/inflections.rb +15 -0
  58. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  59. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  60. data/spec/dummy/config/initializers/session_store.rb +8 -0
  61. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  62. data/spec/dummy/config/locales/en.yml +5 -0
  63. data/spec/dummy/config/routes.rb +7 -0
  64. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
  65. data/spec/dummy/db/schema.rb +21 -0
  66. data/spec/dummy/db/seeds.rb +2 -0
  67. data/spec/dummy/lib/tasks/spec.rake +5 -0
  68. data/spec/dummy/public/404.html +26 -0
  69. data/spec/dummy/public/422.html +26 -0
  70. data/spec/dummy/public/500.html +25 -0
  71. data/spec/dummy/public/favicon.ico +0 -0
  72. data/spec/dummy/script/rails +6 -0
  73. data/spec/dummy/spec/decorators/post_decorator_spec.rb +23 -0
  74. data/spec/dummy/spec/mailers/post_mailer_spec.rb +29 -0
  75. data/spec/dummy/spec/spec_helper.rb +9 -0
  76. data/spec/generators/decorator/decorator_generator_spec.rb +3 -4
  77. data/spec/integration/integration_spec.rb +33 -0
  78. data/{performance → spec/performance}/active_record.rb +0 -0
  79. data/{performance/bechmark.rb → spec/performance/benchmark.rb} +0 -0
  80. data/{performance → spec/performance}/decorators.rb +2 -4
  81. data/{performance → spec/performance}/models.rb +0 -0
  82. data/spec/spec_helper.rb +20 -41
  83. data/spec/support/action_controller.rb +12 -0
  84. data/spec/support/active_model.rb +7 -0
  85. data/spec/support/{samples/active_record.rb → active_record.rb} +5 -0
  86. data/spec/support/{samples → decorators}/decorator_with_application_helper.rb +1 -1
  87. data/spec/support/decorators/namespaced_product_decorator.rb +5 -0
  88. data/spec/support/decorators/non_active_model_product_decorator.rb +2 -0
  89. data/spec/support/decorators/product_decorator.rb +19 -0
  90. data/spec/support/{samples → decorators}/products_decorator.rb +1 -1
  91. data/spec/support/decorators/some_thing_decorator.rb +2 -0
  92. data/spec/support/{samples → decorators}/specific_product_decorator.rb +0 -2
  93. data/spec/support/{samples → decorators}/widget_decorator.rb +0 -0
  94. data/spec/support/dummy_app.rb +84 -0
  95. data/spec/support/matchers/have_text.rb +50 -0
  96. data/spec/support/{samples → models}/namespaced_product.rb +1 -3
  97. data/spec/support/{samples → models}/non_active_model_product.rb +1 -0
  98. data/spec/support/{samples → models}/product.rb +13 -2
  99. data/spec/support/models/some_thing.rb +5 -0
  100. data/spec/support/models/uninferrable_decorator_model.rb +3 -0
  101. data/spec/support/{samples → models}/widget.rb +0 -0
  102. metadata +185 -68
  103. data/lib/draper/active_model_support.rb +0 -27
  104. data/lib/draper/base.rb +0 -312
  105. data/lib/draper/decorated_enumerable_proxy.rb +0 -90
  106. data/lib/draper/model_support.rb +0 -25
  107. data/lib/draper/rspec_integration.rb +0 -2
  108. data/lib/draper/system.rb +0 -18
  109. data/spec/draper/base_spec.rb +0 -873
  110. data/spec/draper/decorated_enumerable_proxy_spec.rb +0 -45
  111. data/spec/draper/model_support_spec.rb +0 -48
  112. data/spec/support/samples/decorator.rb +0 -5
  113. data/spec/support/samples/decorator_with_allows.rb +0 -3
  114. data/spec/support/samples/decorator_with_denies.rb +0 -3
  115. data/spec/support/samples/decorator_with_denies_all.rb +0 -3
  116. data/spec/support/samples/decorator_with_multiple_allows.rb +0 -4
  117. data/spec/support/samples/decorator_with_special_methods.rb +0 -13
  118. data/spec/support/samples/enumerable_proxy.rb +0 -3
  119. data/spec/support/samples/namespaced_product_decorator.rb +0 -7
  120. data/spec/support/samples/product_decorator.rb +0 -7
  121. data/spec/support/samples/sequel_product.rb +0 -13
  122. data/spec/support/samples/some_thing.rb +0 -2
  123. data/spec/support/samples/some_thing_decorator.rb +0 -3
@@ -1,2 +0,0 @@
1
- warn 'DEPRECATION WARNING -- use `require "draper/test/rspec_integration"` instead of `require "draper/rspec_integration"`'
2
- require 'draper/test/rspec_integration'
data/lib/draper/system.rb DELETED
@@ -1,18 +0,0 @@
1
- module Draper
2
- class System
3
- def self.setup_action_controller(component)
4
- component.class_eval do
5
- include Draper::ViewContext
6
- extend Draper::HelperSupport
7
- before_filter lambda {|controller|
8
- Draper::ViewContext.current = nil
9
- Draper::ViewContext.current_controller = controller
10
- }
11
- end
12
- end
13
-
14
- def self.setup_action_mailer(component)
15
- include Draper::ViewContext
16
- end
17
- end
18
- end
@@ -1,873 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Draper::Base do
4
- before(:each){ ApplicationController.new.view_context }
5
- subject{ Decorator.new(source) }
6
- let(:source){ Product.new }
7
- let(:non_active_model_source){ NonActiveModelProduct.new }
8
-
9
- context("proxying class methods") do
10
- it "pass missing class method calls on to the wrapped class" do
11
- subject.class.sample_class_method.should == "sample class method"
12
- end
13
-
14
- it "respond_to a wrapped class method" do
15
- subject.class.should respond_to(:sample_class_method)
16
- end
17
-
18
- it "still respond_to its own class methods" do
19
- subject.class.should respond_to(:own_class_method)
20
- end
21
- end
22
-
23
- context(".helpers") do
24
- it "have a valid view_context" do
25
- subject.helpers.should be
26
- end
27
-
28
- it "is aliased to .h" do
29
- subject.h.should == subject.helpers
30
- end
31
- end
32
-
33
- context("#helpers") do
34
- it "have a valid view_context" do
35
- Decorator.helpers.should be
36
- end
37
-
38
- it "is aliased to #h" do
39
- Decorator.h.should == Decorator.helpers
40
- end
41
- end
42
-
43
- context(".decorates") do
44
- it "sets the model class for the decorator" do
45
- ProductDecorator.new(source).model_class.should == Product
46
- end
47
-
48
- it "returns decorator if it's decorated model already" do
49
- product_decorator = ProductDecorator.new(source)
50
- ProductDecorator.new(product_decorator).model.should be_instance_of Product
51
- end
52
-
53
- it "handle plural-like words properly'" do
54
- class Business; end
55
- expect do
56
- class BusinessDecorator < Draper::Base
57
- decorates:business
58
- end
59
- BusinessDecorator.model_class.should == Business
60
- end.to_not raise_error
61
- end
62
-
63
- context("accepts ActiveRecord like :class_name option too") do
64
- it "accepts constants for :class" do
65
- expect do
66
- class CustomDecorator < Draper::Base
67
- decorates :product, :class => Product
68
- end
69
- CustomDecorator.model_class.should == Product
70
- end.to_not raise_error
71
- end
72
-
73
- it "accepts constants for :class_name" do
74
- expect do
75
- class CustomDecorator < Draper::Base
76
- decorates :product, :class_name => Product
77
- end
78
- CustomDecorator.model_class.should == Product
79
- end.to_not raise_error
80
- end
81
-
82
- it "accepts strings for :class" do
83
- expect do
84
- class CustomDecorator < Draper::Base
85
- decorates :product, :class => 'Product'
86
- end
87
- CustomDecorator.model_class.should == Product
88
- end.to_not raise_error
89
- end
90
-
91
- it "accepts strings for :class_name" do
92
- expect do
93
- class CustomDecorator < Draper::Base
94
- decorates :product, :class_name => 'Product'
95
- end
96
- CustomDecorator.model_class.should == Product
97
- end.to_not raise_error
98
- end
99
- end
100
-
101
- it "creates a named accessor for the wrapped model" do
102
- pd = ProductDecorator.new(source)
103
- pd.send(:product).should == source
104
- end
105
-
106
- context("namespaced model supporting") do
107
- let(:source){ Namespace::Product.new }
108
-
109
- it "sets the model class for the decorator" do
110
- decorator = Namespace::ProductDecorator.new(source)
111
- decorator.model_class.should == Namespace::Product
112
- end
113
-
114
- it "creates a named accessor for the wrapped model" do
115
- pd = Namespace::ProductDecorator.new(source)
116
- pd.send(:product).should == source
117
- end
118
- end
119
- end
120
-
121
- context(".decorates_association") do
122
- context "for ActiveModel collection associations" do
123
- before(:each){ subject.class_eval{ decorates_association :similar_products } }
124
- it "causes the association's method to return a collection of wrapped objects" do
125
- subject.similar_products.each{ |decorated| decorated.should be_instance_of(ProductDecorator) }
126
- end
127
- end
128
-
129
- context "for Plain Old Ruby Object collection associations" do
130
- before(:each){ subject.class_eval{ decorates_association :poro_similar_products } }
131
- it "causes the association's method to return a collection of wrapped objects" do
132
- subject.poro_similar_products.each{ |decorated| decorated.should be_instance_of(ProductDecorator) }
133
- end
134
- end
135
-
136
- context "for an ActiveModel singular association" do
137
- before(:each){ subject.class_eval{ decorates_association :previous_version } }
138
- it "causes the association's method to return a single wrapped object if the association is singular" do
139
- subject.previous_version.should be_instance_of(ProductDecorator)
140
- end
141
- end
142
-
143
- context "for a Plain Old Ruby Object singular association" do
144
- before(:each){ subject.class_eval{ decorates_association :poro_previous_version } }
145
- it "causes the association's method to return a single wrapped object" do
146
- subject.poro_previous_version.should be_instance_of(ProductDecorator)
147
- end
148
- end
149
-
150
- context "with a specific decorator specified" do
151
- before(:each){ subject.class_eval{ decorates_association :previous_version, :with => SpecificProductDecorator } }
152
- it "causes the association to be decorated with the specified association" do
153
- subject.previous_version.should be_instance_of(SpecificProductDecorator)
154
- end
155
- end
156
-
157
- context "with a scope specified" do
158
- before(:each){ subject.class_eval{ decorates_association :thing, :scope => :foo } }
159
- it "applies the scope before decoration" do
160
- SomeThing.any_instance.should_receive(:foo).and_return(:bar)
161
- subject.thing.model.should == :bar
162
- end
163
- end
164
-
165
- context "for a polymorphic association" do
166
- before(:each){ subject.class_eval{ decorates_association :thing, :polymorphic => true } }
167
- it "causes the association to be decorated with the right decorator" do
168
- subject.thing.should be_instance_of(SomeThingDecorator)
169
- end
170
- end
171
-
172
- context "when the association is nil" do
173
- before(:each) do
174
- subject.class_eval{ decorates_association :previous_version }
175
- source.stub(:previous_version){ nil }
176
- end
177
- it "causes the association's method to return nil" do
178
- subject.previous_version.should be_nil
179
- end
180
- end
181
-
182
- context "#find" do
183
- before(:each){ subject.class_eval{ decorates_association :similar_products } }
184
- context "with a block" do
185
- it "delegates to #each" do
186
- subject.similar_products.source.should_receive :find
187
- subject.similar_products.find {|p| p.title == "title" }
188
- end
189
- end
190
-
191
- context "without a block" do
192
- it "calls a finder method" do
193
- subject.similar_products.source.should_not_receive :find
194
- subject.similar_products.find 1
195
- end
196
- end
197
- end
198
- end
199
-
200
- context('.decorates_associations') do
201
- subject { Decorator }
202
- it "decorates each of the associations" do
203
- subject.should_receive(:decorates_association).with(:similar_products, {})
204
- subject.should_receive(:decorates_association).with(:previous_version, {})
205
-
206
- subject.decorates_associations :similar_products, :previous_version
207
- end
208
-
209
- it "dispatches options" do
210
- subject.should_receive(:decorates_association).with(:similar_products, :with => ProductDecorator)
211
- subject.should_receive(:decorates_association).with(:previous_version, :with => ProductDecorator)
212
-
213
- subject.decorates_associations :similar_products, :previous_version, :with => ProductDecorator
214
- end
215
- end
216
-
217
- context(".wrapped_object") do
218
- it "return the wrapped object" do
219
- subject.wrapped_object.should == source
220
- end
221
- end
222
-
223
- context(".source / .to_source") do
224
- it "return the wrapped object" do
225
- subject.to_source == source
226
- subject.source == source
227
- end
228
- end
229
-
230
- describe "method selection" do
231
- it "echos the methods of the wrapped class except default exclusions" do
232
- source.methods.each do |method|
233
- unless Draper::Base::DEFAULT_DENIED.include?(method)
234
- subject.should respond_to(method.to_sym)
235
- end
236
- end
237
- end
238
-
239
- it "not override a defined method with a source method" do
240
- DecoratorWithApplicationHelper.new(source).length.should == "overridden"
241
- end
242
-
243
- it "not copy the .class, .inspect, or other existing methods" do
244
- source.class.should_not == subject.class
245
- source.inspect.should_not == subject.inspect
246
- source.to_s.should_not == subject.to_s
247
- end
248
-
249
- context "when an ActiveModel descendant" do
250
- it "always proxy to_param if it is not defined on the decorator itself" do
251
- source.stub(:to_param).and_return(1)
252
- Draper::Base.new(source).to_param.should == 1
253
- end
254
-
255
- it "always proxy id if it is not defined on the decorator itself" do
256
- source.stub(:id).and_return(123456789)
257
- Draper::Base.new(source).id.should == 123456789
258
- end
259
-
260
- it "always proxy errors if it is not defined on the decorator itself" do
261
- Draper::Base.new(source).errors.should be_an_instance_of ActiveModel::Errors
262
- end
263
-
264
- it "never proxy to_param if it is defined on the decorator itself" do
265
- source.stub(:to_param).and_return(1)
266
- DecoratorWithSpecialMethods.new(source).to_param.should == "foo"
267
- end
268
-
269
- it "never proxy id if it is defined on the decorator itself" do
270
- source.stub(:id).and_return(123456789)
271
- DecoratorWithSpecialMethods.new(source).id.should == 1337
272
- end
273
-
274
- it "never proxy errors if it is defined on the decorator itself" do
275
- DecoratorWithSpecialMethods.new(source).errors.should be_an_instance_of Array
276
- end
277
- end
278
- end
279
-
280
- context 'the decorated model' do
281
- it 'receives the mixin' do
282
- source.class.ancestors.include?(Draper::ModelSupport)
283
- end
284
-
285
- it 'includes ActiveModel support' do
286
- source.class.ancestors.include?(Draper::ActiveModelSupport)
287
- end
288
- end
289
-
290
- it "wrap source methods so they still accept blocks" do
291
- subject.block{"marker"}.should == "marker"
292
- end
293
-
294
- context ".find" do
295
- it "lookup the associated model when passed an integer" do
296
- pd = ProductDecorator.find(1)
297
- pd.should be_instance_of(ProductDecorator)
298
- pd.model.should be_instance_of(Product)
299
- end
300
-
301
- it "lookup the associated model when passed a string" do
302
- pd = ProductDecorator.find("1")
303
- pd.should be_instance_of(ProductDecorator)
304
- pd.model.should be_instance_of(Product)
305
- end
306
-
307
- it "accept and store a context" do
308
- pd = ProductDecorator.find(1, :context => :admin)
309
- pd.context.should == :admin
310
- end
311
- end
312
-
313
- context ".find_by_(x)" do
314
- it "runs the similarly named finder" do
315
- Product.should_receive(:find_by_name)
316
- ProductDecorator.find_by_name("apples")
317
- end
318
-
319
- it "returns a decorated result" do
320
- ProductDecorator.find_by_name("apples").should be_kind_of(ProductDecorator)
321
- end
322
-
323
- it "runs complex finders" do
324
- Product.should_receive(:find_by_name_and_size)
325
- ProductDecorator.find_by_name_and_size("apples", "large")
326
- end
327
-
328
- it "runs find_all_by_(x) finders" do
329
- Product.should_receive(:find_all_by_name_and_size)
330
- ProductDecorator.find_all_by_name_and_size("apples", "large")
331
- end
332
-
333
- it "runs find_last_by_(x) finders" do
334
- Product.should_receive(:find_last_by_name_and_size)
335
- ProductDecorator.find_last_by_name_and_size("apples", "large")
336
- end
337
-
338
- it "runs find_or_initialize_by_(x) finders" do
339
- Product.should_receive(:find_or_initialize_by_name_and_size)
340
- ProductDecorator.find_or_initialize_by_name_and_size("apples", "large")
341
- end
342
-
343
- it "runs find_or_create_by_(x) finders" do
344
- Product.should_receive(:find_or_create_by_name_and_size)
345
- ProductDecorator.find_or_create_by_name_and_size("apples", "large")
346
- end
347
-
348
- it "accepts an options hash" do
349
- Product.should_receive(:find_by_name_and_size).with("apples", "large", {:role => :admin})
350
- ProductDecorator.find_by_name_and_size("apples", "large", {:role => :admin})
351
- end
352
-
353
- it "uses the options hash in the decorator instantiation" do
354
- Product.should_receive(:find_by_name_and_size).with("apples", "large", {:role => :admin})
355
- pd = ProductDecorator.find_by_name_and_size("apples", "large", {:role => :admin})
356
- pd.context[:role].should == :admin
357
- end
358
- end
359
-
360
- context ".decorate" do
361
- context "without any context" do
362
- subject { Draper::Base.decorate(source) }
363
-
364
- context "when given a collection of source objects" do
365
- let(:source) { [Product.new, Product.new] }
366
-
367
- its(:size) { should == source.size }
368
-
369
- it "returns a collection of wrapped objects" do
370
- subject.each{ |decorated| decorated.should be_instance_of(Draper::Base) }
371
- end
372
-
373
- it 'should accepted and store a context for a collection' do
374
- subject.context = :admin
375
- subject.each { |decorated| decorated.context.should == :admin }
376
- end
377
- end
378
-
379
- context "when given a struct" do
380
- # Struct objects implement #each
381
- let(:source) { Struct.new(:title).new("Godzilla") }
382
-
383
- it "returns a wrapped object" do
384
- subject.should be_instance_of(Draper::Base)
385
- end
386
- end
387
-
388
- context "when given a collection of sequel models" do
389
- # Sequel models implement #each
390
- let(:source) { [SequelProduct.new, SequelProduct.new] }
391
-
392
- it "returns a collection of wrapped objects" do
393
- subject.each{ |decorated| decorated.should be_instance_of(Draper::Base) }
394
- end
395
- end
396
-
397
- context "when given a single source object" do
398
- let(:source) { Product.new }
399
-
400
- it { should be_instance_of(Draper::Base) }
401
-
402
- context "when the input is already decorated" do
403
- it "does not perform double-decoration" do
404
- decorated = ProductDecorator.decorate(source)
405
- ProductDecorator.decorate(decorated).object_id.should == decorated.object_id
406
- end
407
-
408
- it "overwrites options with provided options" do
409
- first_run = ProductDecorator.decorate(source, :context => {:role => :user})
410
- second_run = ProductDecorator.decorate(first_run, :context => {:role => :admin})
411
- second_run.context[:role].should == :admin
412
- end
413
-
414
- it "leaves existing options if none are supplied" do
415
- first_run = ProductDecorator.decorate(source, :context => {:role => :user})
416
- second_run = ProductDecorator.decorate(first_run)
417
- second_run.context[:role].should == :user
418
- end
419
- end
420
- end
421
- end
422
-
423
- context "with a context" do
424
- let(:context) {{ :some => 'data' }}
425
-
426
- subject { Draper::Base.decorate(source, :context => context) }
427
-
428
- context "when given a collection of source objects" do
429
- let(:source) { [Product.new, Product.new] }
430
-
431
- it "returns a collection of wrapped objects with the context" do
432
- subject.each{ |decorated| decorated.context.should eq(context) }
433
- end
434
- end
435
-
436
- context "when given a single source object" do
437
- let(:source) { Product.new }
438
-
439
- its(:context) { should eq(context) }
440
- end
441
- end
442
-
443
- context "with options" do
444
- let(:options) {{ :more => "settings" }}
445
-
446
- subject { Draper::Base.decorate(source, options ) }
447
-
448
- its(:options) { should eq(options) }
449
- end
450
-
451
- context "does not infer collections by default" do
452
- subject { Draper::Base.decorate(source).to_ary }
453
-
454
- let(:source) { [Product.new, Widget.new] }
455
-
456
- it "returns a collection of wrapped objects all with the same decorator" do
457
- subject.first.class.name.should eql 'Draper::Base'
458
- subject.last.class.name.should eql 'Draper::Base'
459
- end
460
- end
461
-
462
- context "does not infer single items by default" do
463
- subject { Draper::Base.decorate(source) }
464
-
465
- let(:source) { Product.new }
466
-
467
- it "returns a decorator of the type explicity used in the call" do
468
- subject.class.should eql Draper::Base
469
- end
470
- end
471
-
472
- context "returns a collection containing only the explicit decorator used in the call" do
473
- subject { Draper::Base.decorate(source, :infer => true).to_ary }
474
-
475
- let(:source) { [Product.new, Widget.new] }
476
-
477
- it "returns a mixed collection of wrapped objects" do
478
- subject.first.class.should eql ProductDecorator
479
- subject.last.class.should eql WidgetDecorator
480
- end
481
- end
482
-
483
- context "when given a single object" do
484
- subject { Draper::Base.decorate(source, :infer => true) }
485
-
486
- let(:source) { Product.new }
487
-
488
- it "can also infer its decorator" do
489
- subject.class.should eql ProductDecorator
490
- end
491
- end
492
- end
493
-
494
- context('.==') do
495
- it "compare the decorated models" do
496
- other = Draper::Base.new(source)
497
- subject.should == other
498
- end
499
- end
500
-
501
- context ".respond_to?" do
502
- # respond_to? is called by some proxies (id, to_param, errors).
503
- # This is, why I stub it this way.
504
- it "delegate respond_to? to the decorated model" do
505
- other = Draper::Base.new(source)
506
- source.stub(:respond_to?).and_return(false)
507
- source.stub(:respond_to?).with(:whatever, true).once.and_return("mocked")
508
- subject.respond_to?(:whatever, true).should == "mocked"
509
- end
510
- end
511
-
512
- context 'position accessors' do
513
- [:first, :last].each do |method|
514
- context "##{method}" do
515
- it "return a decorated instance" do
516
- ProductDecorator.send(method).should be_instance_of ProductDecorator
517
- end
518
-
519
- it "return the #{method} instance of the wrapped class" do
520
- ProductDecorator.send(method).model.should == Product.send(method)
521
- end
522
-
523
- it "accept an optional context" do
524
- ProductDecorator.send(method, :context => :admin).context.should == :admin
525
- end
526
- end
527
- end
528
- end
529
-
530
- describe "collection decoration" do
531
-
532
- # Implementation of #decorate that returns an array
533
- # of decorated objects is insufficient to deal with
534
- # situations where the original collection has been
535
- # expanded with the use of modules (as often the case
536
- # with paginator gems) or is just more complex then
537
- # an array.
538
- module Paginator; def page_number; "magic_value"; end; end
539
- Array.send(:include, Paginator)
540
- let(:paged_array) { [Product.new, Product.new] }
541
- let(:empty_collection) { [] }
542
- subject { ProductDecorator.decorate(paged_array) }
543
-
544
- it "proxy all calls to decorated collection" do
545
- paged_array.page_number.should == "magic_value"
546
- subject.page_number.should == "magic_value"
547
- end
548
-
549
- it "support Rails partial lookup for a collection" do
550
- # to support Rails render @collection the returned collection
551
- # (or its proxy) should implement #to_ary.
552
- subject.respond_to?(:to_ary).should be true
553
- subject.to_ary.first.should == ProductDecorator.decorate(paged_array.first)
554
- end
555
-
556
- it "delegate respond_to? to the wrapped collection" do
557
- decorator = ProductDecorator.decorate(paged_array)
558
- paged_array.should_receive(:respond_to?).with(:whatever, true)
559
- decorator.respond_to?(:whatever, true)
560
- end
561
-
562
- it "return blank for a decorated empty collection" do
563
- # This tests that respond_to? is defined for the DecoratedEnumerableProxy
564
- # since activesupport calls respond_to?(:empty) in #blank
565
- decorator = ProductDecorator.decorate(empty_collection)
566
- decorator.should be_blank
567
- end
568
-
569
- it "return whether the member is in the array for a decorated wrapped collection" do
570
- # This tests that include? is defined for the DecoratedEnumerableProxy
571
- member = paged_array.first
572
- subject.respond_to?(:include?)
573
- subject.include?(member).should == true
574
- subject.include?(subject.first).should == true
575
- subject.include?(Product.new).should == false
576
- end
577
-
578
- it "equal each other when decorating the same collection" do
579
- subject_one = ProductDecorator.decorate(paged_array)
580
- subject_two = ProductDecorator.decorate(paged_array)
581
- subject_one.should == subject_two
582
- end
583
-
584
- it "not equal each other when decorating different collections" do
585
- subject_one = ProductDecorator.decorate(paged_array)
586
- new_paged_array = paged_array + [Product.new]
587
- subject_two = ProductDecorator.decorate(new_paged_array)
588
- subject_one.should_not == subject_two
589
- end
590
-
591
- it "allow decorated access by index" do
592
- subject = ProductDecorator.decorate(paged_array)
593
- subject[0].should be_instance_of ProductDecorator
594
- end
595
-
596
- context "pretends to be of kind of wrapped collection class" do
597
- subject { ProductDecorator.decorate(paged_array) }
598
-
599
- it "#kind_of? DecoratedEnumerableProxy" do
600
- subject.should be_kind_of Draper::DecoratedEnumerableProxy
601
- end
602
-
603
- it "#is_a? DecoratedEnumerableProxy" do
604
- subject.is_a?(Draper::DecoratedEnumerableProxy).should be_true
605
- end
606
-
607
- it "#kind_of? Array" do
608
- subject.should be_kind_of Array
609
- end
610
-
611
- it "#is_a? Array" do
612
- subject.is_a?(Array).should be_true
613
- end
614
- end
615
-
616
- context '#all' do
617
- it "return a decorated collection" do
618
- ProductDecorator.all.first.should be_instance_of ProductDecorator
619
- end
620
-
621
- it "accept a context" do
622
- collection = ProductDecorator.all(:context => :admin)
623
- collection.first.context.should == :admin
624
- end
625
- end
626
-
627
- context(".source / .to_source") do
628
- it "return the wrapped object" do
629
- subject.to_source == source
630
- subject.source == source
631
- end
632
- end
633
- end
634
-
635
- describe "a sample usage with denies" do
636
- let(:subject_with_denies){ DecoratorWithDenies.new(source) }
637
-
638
- it "proxy methods not listed in denies" do
639
- subject_with_denies.should respond_to(:hello_world)
640
- end
641
-
642
- it "not echo methods specified with denies" do
643
- subject_with_denies.should_not respond_to(:goodnight_moon)
644
- end
645
-
646
- it "not clobber other decorators' methods" do
647
- subject.should respond_to(:hello_world)
648
- end
649
-
650
- it "not allow method_missing to circumvent a deny" do
651
- expect{subject_with_denies.title}.to raise_error(NoMethodError)
652
- end
653
- end
654
-
655
- describe "a sample usage with allows" do
656
- let(:subject_with_allows){ DecoratorWithAllows.new(source) }
657
-
658
- let(:subject_with_multiple_allows){ DecoratorWithMultipleAllows.new(source) }
659
-
660
- it "echo the allowed method" do
661
- subject_with_allows.should respond_to(:goodnight_moon)
662
- end
663
-
664
- it "echo _only_ the allowed method" do
665
- subject_with_allows.should_not respond_to(:hello_world)
666
- end
667
-
668
- it "echo the combined allowed methods" do
669
- subject_with_multiple_allows.should respond_to(:goodnight_moon)
670
- subject_with_multiple_allows.should respond_to(:hello_world)
671
- end
672
-
673
- it "echo _only_ the combined allowed methods" do
674
- subject_with_multiple_allows.should_not respond_to(:title)
675
- end
676
- end
677
-
678
- describe "invalid usages of allows and denies" do
679
- let(:blank_allows){
680
- class DecoratorWithInvalidAllows < Draper::Base
681
- allows
682
- end
683
- }
684
-
685
- let(:blank_denies){
686
- class DecoratorWithInvalidDenies < Draper::Base
687
- denies
688
- end
689
- }
690
-
691
- let(:using_allows_then_denies){
692
- class DecoratorWithAllowsAndDenies < Draper::Base
693
- allows :hello_world
694
- denies :goodnight_moon
695
- end
696
- }
697
-
698
- let(:using_denies_then_allows){
699
- class DecoratorWithDeniesAndAllows < Draper::Base
700
- denies :goodnight_moon
701
- allows :hello_world
702
- end
703
- }
704
-
705
- it "raise an exception for a blank allows" do
706
- expect {blank_allows}.to raise_error(ArgumentError)
707
- end
708
-
709
- it "raise an exception for a blank denies" do
710
- expect {blank_denies}.to raise_error(ArgumentError)
711
- end
712
-
713
- it "raise an exception for calling allows then denies" do
714
- expect {using_allows_then_denies}.to raise_error(ArgumentError)
715
- end
716
-
717
- it "raise an exception for calling denies then allows" do
718
- expect {using_denies_then_allows}.to raise_error(ArgumentError)
719
- end
720
- end
721
-
722
- describe "a sample usage with denies_all" do
723
- let(:subject_with_denies_all){ DecoratorWithDeniesAll.new(source) }
724
-
725
- [:goodnight_moon, :hello_world, :title].each do |method|
726
- it "does echo #{method} method" do
727
- subject_with_denies_all.should_not respond_to(method)
728
- end
729
- end
730
-
731
- let(:using_denies_all_then_denies_all) {
732
- class DecoratorWithDeniesAllAndDeniesAll < Draper::Base
733
- denies_all
734
- denies_all
735
- end
736
- }
737
-
738
- it "allows multple calls to .denies_all" do
739
- expect { using_denies_all_then_denies_all }.to_not raise_error(ArgumentError)
740
- end
741
- end
742
-
743
- describe "invalid usages of denies_all" do
744
- let(:using_allows_then_denies_all) {
745
- class DecoratorWithAllowsAndDeniesAll < Draper::Base
746
- allows :hello_world
747
- denies_all
748
- end
749
- }
750
- let(:using_denies_then_denies_all) {
751
- class DecoratorWithDeniesAndDeniesAll < Draper::Base
752
- denies :goodnight_moon
753
- denies_all
754
- end
755
- }
756
- let(:using_denies_all_then_allows) {
757
- class DecoratorWithDeniesAllAndAllows < Draper::Base
758
- denies_all
759
- allows :hello_world
760
- end
761
- }
762
- let(:using_denies_all_then_denies) {
763
- class DecoratorWithDeniesAllAndDenies < Draper::Base
764
- denies_all
765
- denies :goodnight_moon
766
- end
767
- }
768
- it "raises an exception when calling allows then denies_all" do
769
- expect {using_allows_then_denies_all}.to raise_error(ArgumentError)
770
- end
771
-
772
- it "raises an exception when calling denies then denies_all" do
773
- expect {using_denies_then_denies_all}.to raise_error(ArgumentError)
774
- end
775
-
776
- it "raises an exception when calling denies_all then allows" do
777
- expect {using_denies_all_then_allows}.to raise_error(ArgumentError)
778
- end
779
-
780
- it "raises an exception when calling denies_all then denies" do
781
- expect {using_denies_all_then_denies}.to raise_error(ArgumentError)
782
- end
783
- end
784
-
785
- context "in a Rails application" do
786
- let(:decorator){ DecoratorWithApplicationHelper.decorate(Object.new) }
787
-
788
- it "have access to ApplicationHelper helpers" do
789
- decorator.uses_hello_world == "Hello, World!"
790
- end
791
-
792
- it "is able to use the content_tag helper" do
793
- decorator.sample_content.to_s.should == "<span>Hello, World!</span>"
794
- end
795
-
796
- it "is able to use the link_to helper" do
797
- decorator.sample_link.should == "<a href=\"/World\">Hello</a>"
798
- end
799
-
800
- it "is able to use the pluralize helper" do
801
- decorator.sample_truncate.should == "Once..."
802
- end
803
-
804
- it "is able to use l rather than helpers.l" do
805
- now = Time.now
806
- helper_proxy = decorator.helpers.instance_variable_get(:@helpers)
807
- helper_proxy.should_receive(:localize).with(now, :format => :long)
808
- decorator.l now, :format => :long
809
- end
810
-
811
- it "is able to access html_escape, a private method" do
812
- decorator.sample_html_escaped_text.should == '&lt;script&gt;danger&lt;/script&gt;'
813
- end
814
- end
815
-
816
- context "#method_missing" do
817
- context "with an isolated decorator class" do
818
- let(:decorator_class) { Class.new(Decorator) }
819
- subject{ decorator_class.new(source) }
820
-
821
- context "when #hello_world is called again" do
822
- it "proxies method directly after first hit" do
823
- subject.methods.should_not include(:hello_world)
824
- subject.hello_world
825
- subject.methods.should include(:hello_world)
826
- end
827
- end
828
-
829
- context "when #hello_world is called for the first time" do
830
- it "hits method missing" do
831
- subject.should_receive(:method_missing)
832
- subject.hello_world
833
- end
834
- end
835
- end
836
-
837
- context "when the delegated method calls a non-existant method" do
838
- it 'should not try to delegate to non-existant methods to not confuse Kernel#Array' do
839
- Array(subject).should be_kind_of(Array)
840
- end
841
-
842
- it "raises the correct NoMethodError" do
843
- begin
844
- subject.some_action
845
- rescue NoMethodError => e
846
- e.name.should_not == :some_action
847
- else
848
- fail("No exception raised")
849
- end
850
- end
851
- end
852
- end
853
-
854
- describe "#kind_of?" do
855
- context "pretends to be of kind of model class" do
856
- it "#kind_of? decorator class" do
857
- subject.should be_kind_of subject.class
858
- end
859
-
860
- it "#is_a? decorator class" do
861
- subject.is_a?(subject.class).should be_true
862
- end
863
-
864
- it "#kind_of? source class" do
865
- subject.should be_kind_of source.class
866
- end
867
-
868
- it "#is_a? source class" do
869
- subject.is_a?(source.class).should be_true
870
- end
871
- end
872
- end
873
- end