draper 0.17.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 (154) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +5 -0
  3. data/CHANGELOG.markdown +14 -0
  4. data/Gemfile +4 -0
  5. data/Rakefile +50 -31
  6. data/Readme.markdown +63 -92
  7. data/draper.gemspec +4 -2
  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 +208 -0
  12. data/lib/draper/finders.rb +44 -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 +42 -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 +497 -0
  33. data/spec/draper/finders_spec.rb +156 -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 +25 -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 +23 -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 +24 -5
  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 +19 -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 +13 -2
  101. data/spec/support/models/some_thing.rb +5 -0
  102. data/spec/support/models/uninferrable_decorator_model.rb +3 -0
  103. metadata +207 -98
  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
@@ -1,863 +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 collection of sequel models" do
380
- # Sequel models implement #each
381
- let(:source) { [SequelProduct.new, SequelProduct.new] }
382
-
383
- it "returns a collection of wrapped objects" do
384
- subject.each{ |decorated| decorated.should be_instance_of(Draper::Base) }
385
- end
386
- end
387
-
388
- context "when given a single source object" do
389
- let(:source) { Product.new }
390
-
391
- it { should be_instance_of(Draper::Base) }
392
-
393
- context "when the input is already decorated" do
394
- it "does not perform double-decoration" do
395
- decorated = ProductDecorator.decorate(source)
396
- ProductDecorator.decorate(decorated).object_id.should == decorated.object_id
397
- end
398
-
399
- it "overwrites options with provided options" do
400
- first_run = ProductDecorator.decorate(source, :context => {:role => :user})
401
- second_run = ProductDecorator.decorate(first_run, :context => {:role => :admin})
402
- second_run.context[:role].should == :admin
403
- end
404
-
405
- it "leaves existing options if none are supplied" do
406
- first_run = ProductDecorator.decorate(source, :context => {:role => :user})
407
- second_run = ProductDecorator.decorate(first_run)
408
- second_run.context[:role].should == :user
409
- end
410
- end
411
- end
412
- end
413
-
414
- context "with a context" do
415
- let(:context) {{ :some => 'data' }}
416
-
417
- subject { Draper::Base.decorate(source, :context => context) }
418
-
419
- context "when given a collection of source objects" do
420
- let(:source) { [Product.new, Product.new] }
421
-
422
- it "returns a collection of wrapped objects with the context" do
423
- subject.each{ |decorated| decorated.context.should eq(context) }
424
- end
425
- end
426
-
427
- context "when given a single source object" do
428
- let(:source) { Product.new }
429
-
430
- its(:context) { should eq(context) }
431
- end
432
- end
433
-
434
- context "with options" do
435
- let(:options) {{ :more => "settings" }}
436
-
437
- subject { Draper::Base.decorate(source, options ) }
438
-
439
- its(:options) { should eq(options) }
440
- end
441
-
442
- context "does not infer collections by default" do
443
- subject { Draper::Base.decorate(source).to_ary }
444
-
445
- let(:source) { [Product.new, Widget.new] }
446
-
447
- it "returns a collection of wrapped objects all with the same decorator" do
448
- subject.first.class.name.should eql 'Draper::Base'
449
- subject.last.class.name.should eql 'Draper::Base'
450
- end
451
- end
452
-
453
- context "does not infer single items by default" do
454
- subject { Draper::Base.decorate(source) }
455
-
456
- let(:source) { Product.new }
457
-
458
- it "returns a decorator of the type explicity used in the call" do
459
- subject.class.should eql Draper::Base
460
- end
461
- end
462
-
463
- context "returns a collection containing only the explicit decorator used in the call" do
464
- subject { Draper::Base.decorate(source, :infer => true).to_ary }
465
-
466
- let(:source) { [Product.new, Widget.new] }
467
-
468
- it "returns a mixed collection of wrapped objects" do
469
- subject.first.class.should eql ProductDecorator
470
- subject.last.class.should eql WidgetDecorator
471
- end
472
- end
473
-
474
- context "when given a single object" do
475
- subject { Draper::Base.decorate(source, :infer => true) }
476
-
477
- let(:source) { Product.new }
478
-
479
- it "can also infer its decorator" do
480
- subject.class.should eql ProductDecorator
481
- end
482
- end
483
- end
484
-
485
- context('.==') do
486
- it "compare the decorated models" do
487
- other = Draper::Base.new(source)
488
- subject.should == other
489
- end
490
- end
491
-
492
- context ".respond_to?" do
493
- # respond_to? is called by some proxies (id, to_param, errors).
494
- # This is, why I stub it this way.
495
- it "delegate respond_to? to the decorated model" do
496
- other = Draper::Base.new(source)
497
- source.stub(:respond_to?).and_return(false)
498
- source.stub(:respond_to?).with(:whatever, true).once.and_return("mocked")
499
- subject.respond_to?(:whatever, true).should == "mocked"
500
- end
501
- end
502
-
503
- context 'position accessors' do
504
- [:first, :last].each do |method|
505
- context "##{method}" do
506
- it "return a decorated instance" do
507
- ProductDecorator.send(method).should be_instance_of ProductDecorator
508
- end
509
-
510
- it "return the #{method} instance of the wrapped class" do
511
- ProductDecorator.send(method).model.should == Product.send(method)
512
- end
513
-
514
- it "accept an optional context" do
515
- ProductDecorator.send(method, :context => :admin).context.should == :admin
516
- end
517
- end
518
- end
519
- end
520
-
521
- describe "collection decoration" do
522
-
523
- # Implementation of #decorate that returns an array
524
- # of decorated objects is insufficient to deal with
525
- # situations where the original collection has been
526
- # expanded with the use of modules (as often the case
527
- # with paginator gems) or is just more complex then
528
- # an array.
529
- module Paginator; def page_number; "magic_value"; end; end
530
- Array.send(:include, Paginator)
531
- let(:paged_array) { [Product.new, Product.new] }
532
- let(:empty_collection) { [] }
533
- subject { ProductDecorator.decorate(paged_array) }
534
-
535
- it "proxy all calls to decorated collection" do
536
- paged_array.page_number.should == "magic_value"
537
- subject.page_number.should == "magic_value"
538
- end
539
-
540
- it "support Rails partial lookup for a collection" do
541
- # to support Rails render @collection the returned collection
542
- # (or its proxy) should implement #to_ary.
543
- subject.respond_to?(:to_ary).should be true
544
- subject.to_ary.first.should == ProductDecorator.decorate(paged_array.first)
545
- end
546
-
547
- it "delegate respond_to? to the wrapped collection" do
548
- decorator = ProductDecorator.decorate(paged_array)
549
- paged_array.should_receive(:respond_to?).with(:whatever, true)
550
- decorator.respond_to?(:whatever, true)
551
- end
552
-
553
- it "return blank for a decorated empty collection" do
554
- # This tests that respond_to? is defined for the DecoratedEnumerableProxy
555
- # since activesupport calls respond_to?(:empty) in #blank
556
- decorator = ProductDecorator.decorate(empty_collection)
557
- decorator.should be_blank
558
- end
559
-
560
- it "return whether the member is in the array for a decorated wrapped collection" do
561
- # This tests that include? is defined for the DecoratedEnumerableProxy
562
- member = paged_array.first
563
- subject.respond_to?(:include?)
564
- subject.include?(member).should == true
565
- subject.include?(subject.first).should == true
566
- subject.include?(Product.new).should == false
567
- end
568
-
569
- it "equal each other when decorating the same collection" do
570
- subject_one = ProductDecorator.decorate(paged_array)
571
- subject_two = ProductDecorator.decorate(paged_array)
572
- subject_one.should == subject_two
573
- end
574
-
575
- it "not equal each other when decorating different collections" do
576
- subject_one = ProductDecorator.decorate(paged_array)
577
- new_paged_array = paged_array + [Product.new]
578
- subject_two = ProductDecorator.decorate(new_paged_array)
579
- subject_one.should_not == subject_two
580
- end
581
-
582
- it "allow decorated access by index" do
583
- subject = ProductDecorator.decorate(paged_array)
584
- subject[0].should be_instance_of ProductDecorator
585
- end
586
-
587
- context "pretends to be of kind of wrapped collection class" do
588
- subject { ProductDecorator.decorate(paged_array) }
589
-
590
- it "#kind_of? DecoratedEnumerableProxy" do
591
- subject.should be_kind_of Draper::DecoratedEnumerableProxy
592
- end
593
-
594
- it "#is_a? DecoratedEnumerableProxy" do
595
- subject.is_a?(Draper::DecoratedEnumerableProxy).should be_true
596
- end
597
-
598
- it "#kind_of? Array" do
599
- subject.should be_kind_of Array
600
- end
601
-
602
- it "#is_a? Array" do
603
- subject.is_a?(Array).should be_true
604
- end
605
- end
606
-
607
- context '#all' do
608
- it "return a decorated collection" do
609
- ProductDecorator.all.first.should be_instance_of ProductDecorator
610
- end
611
-
612
- it "accept a context" do
613
- collection = ProductDecorator.all(:context => :admin)
614
- collection.first.context.should == :admin
615
- end
616
- end
617
-
618
- context(".source / .to_source") do
619
- it "return the wrapped object" do
620
- subject.to_source == source
621
- subject.source == source
622
- end
623
- end
624
- end
625
-
626
- describe "a sample usage with denies" do
627
- let(:subject_with_denies){ DecoratorWithDenies.new(source) }
628
-
629
- it "proxy methods not listed in denies" do
630
- subject_with_denies.should respond_to(:hello_world)
631
- end
632
-
633
- it "not echo methods specified with denies" do
634
- subject_with_denies.should_not respond_to(:goodnight_moon)
635
- end
636
-
637
- it "not clobber other decorators' methods" do
638
- subject.should respond_to(:hello_world)
639
- end
640
-
641
- it "not allow method_missing to circumvent a deny" do
642
- expect{subject_with_denies.title}.to raise_error(NoMethodError)
643
- end
644
- end
645
-
646
- describe "a sample usage with allows" do
647
- let(:subject_with_allows){ DecoratorWithAllows.new(source) }
648
-
649
- let(:subject_with_multiple_allows){ DecoratorWithMultipleAllows.new(source) }
650
-
651
- it "echo the allowed method" do
652
- subject_with_allows.should respond_to(:goodnight_moon)
653
- end
654
-
655
- it "echo _only_ the allowed method" do
656
- subject_with_allows.should_not respond_to(:hello_world)
657
- end
658
-
659
- it "echo the combined allowed methods" do
660
- subject_with_multiple_allows.should respond_to(:goodnight_moon)
661
- subject_with_multiple_allows.should respond_to(:hello_world)
662
- end
663
-
664
- it "echo _only_ the combined allowed methods" do
665
- subject_with_multiple_allows.should_not respond_to(:title)
666
- end
667
- end
668
-
669
- describe "invalid usages of allows and denies" do
670
- let(:blank_allows){
671
- class DecoratorWithInvalidAllows < Draper::Base
672
- allows
673
- end
674
- }
675
-
676
- let(:blank_denies){
677
- class DecoratorWithInvalidDenies < Draper::Base
678
- denies
679
- end
680
- }
681
-
682
- let(:using_allows_then_denies){
683
- class DecoratorWithAllowsAndDenies < Draper::Base
684
- allows :hello_world
685
- denies :goodnight_moon
686
- end
687
- }
688
-
689
- let(:using_denies_then_allows){
690
- class DecoratorWithDeniesAndAllows < Draper::Base
691
- denies :goodnight_moon
692
- allows :hello_world
693
- end
694
- }
695
-
696
- it "raise an exception for a blank allows" do
697
- expect {blank_allows}.to raise_error(ArgumentError)
698
- end
699
-
700
- it "raise an exception for a blank denies" do
701
- expect {blank_denies}.to raise_error(ArgumentError)
702
- end
703
-
704
- it "raise an exception for calling allows then denies" do
705
- expect {using_allows_then_denies}.to raise_error(ArgumentError)
706
- end
707
-
708
- it "raise an exception for calling denies then allows" do
709
- expect {using_denies_then_allows}.to raise_error(ArgumentError)
710
- end
711
- end
712
-
713
- describe "a sample usage with denies_all" do
714
- let(:subject_with_denies_all){ DecoratorWithDeniesAll.new(source) }
715
-
716
- [:goodnight_moon, :hello_world, :title].each do |method|
717
- it "does echo #{method} method" do
718
- subject_with_denies_all.should_not respond_to(method)
719
- end
720
- end
721
-
722
- let(:using_denies_all_then_denies_all) {
723
- class DecoratorWithDeniesAllAndDeniesAll < Draper::Base
724
- denies_all
725
- denies_all
726
- end
727
- }
728
-
729
- it "allows multple calls to .denies_all" do
730
- expect { using_denies_all_then_denies_all }.to_not raise_error(ArgumentError)
731
- end
732
- end
733
-
734
- describe "invalid usages of denies_all" do
735
- let(:using_allows_then_denies_all) {
736
- class DecoratorWithAllowsAndDeniesAll < Draper::Base
737
- allows :hello_world
738
- denies_all
739
- end
740
- }
741
- let(:using_denies_then_denies_all) {
742
- class DecoratorWithDeniesAndDeniesAll < Draper::Base
743
- denies :goodnight_moon
744
- denies_all
745
- end
746
- }
747
- let(:using_denies_all_then_allows) {
748
- class DecoratorWithDeniesAllAndAllows < Draper::Base
749
- denies_all
750
- allows :hello_world
751
- end
752
- }
753
- let(:using_denies_all_then_denies) {
754
- class DecoratorWithDeniesAllAndDenies < Draper::Base
755
- denies_all
756
- denies :goodnight_moon
757
- end
758
- }
759
- it "raises an exception when calling allows then denies_all" do
760
- expect {using_allows_then_denies_all}.to raise_error(ArgumentError)
761
- end
762
-
763
- it "raises an exception when calling denies then denies_all" do
764
- expect {using_denies_then_denies_all}.to raise_error(ArgumentError)
765
- end
766
-
767
- it "raises an exception when calling denies_all then allows" do
768
- expect {using_denies_all_then_allows}.to raise_error(ArgumentError)
769
- end
770
-
771
- it "raises an exception when calling denies_all then denies" do
772
- expect {using_denies_all_then_denies}.to raise_error(ArgumentError)
773
- end
774
- end
775
-
776
- context "in a Rails application" do
777
- let(:decorator){ DecoratorWithApplicationHelper.decorate(Object.new) }
778
-
779
- it "have access to ApplicationHelper helpers" do
780
- decorator.uses_hello_world == "Hello, World!"
781
- end
782
-
783
- it "is able to use the content_tag helper" do
784
- decorator.sample_content.to_s.should == "<span>Hello, World!</span>"
785
- end
786
-
787
- it "is able to use the link_to helper" do
788
- decorator.sample_link.should == "<a href=\"/World\">Hello</a>"
789
- end
790
-
791
- it "is able to use the pluralize helper" do
792
- decorator.sample_truncate.should == "Once..."
793
- end
794
-
795
- it "is able to use l rather than helpers.l" do
796
- now = Time.now
797
- decorator.helpers.instance_variable_get(:@helpers).should_receive(:localize).with(now)
798
- decorator.l now
799
- end
800
-
801
- it "is able to access html_escape, a private method" do
802
- decorator.sample_html_escaped_text.should == '&lt;script&gt;danger&lt;/script&gt;'
803
- end
804
- end
805
-
806
- context "#method_missing" do
807
- context "with an isolated decorator class" do
808
- let(:decorator_class) { Class.new(Decorator) }
809
- subject{ decorator_class.new(source) }
810
-
811
- context "when #hello_world is called again" do
812
- it "proxies method directly after first hit" do
813
- subject.methods.should_not include(:hello_world)
814
- subject.hello_world
815
- subject.methods.should include(:hello_world)
816
- end
817
- end
818
-
819
- context "when #hello_world is called for the first time" do
820
- it "hits method missing" do
821
- subject.should_receive(:method_missing)
822
- subject.hello_world
823
- end
824
- end
825
- end
826
-
827
- context "when the delegated method calls a non-existant method" do
828
- it 'should not try to delegate to non-existant methods to not confuse Kernel#Array' do
829
- Array(subject).should be_kind_of(Array)
830
- end
831
-
832
- it "raises the correct NoMethodError" do
833
- begin
834
- subject.some_action
835
- rescue NoMethodError => e
836
- e.name.should_not == :some_action
837
- else
838
- fail("No exception raised")
839
- end
840
- end
841
- end
842
- end
843
-
844
- describe "#kind_of?" do
845
- context "pretends to be of kind of model class" do
846
- it "#kind_of? decorator class" do
847
- subject.should be_kind_of subject.class
848
- end
849
-
850
- it "#is_a? decorator class" do
851
- subject.is_a?(subject.class).should be_true
852
- end
853
-
854
- it "#kind_of? source class" do
855
- subject.should be_kind_of source.class
856
- end
857
-
858
- it "#is_a? source class" do
859
- subject.is_a?(source.class).should be_true
860
- end
861
- end
862
- end
863
- end