draper 0.18.0 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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