draper 0.18.0 → 1.0.0

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 (140) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +11 -0
  3. data/.yardopts +1 -1
  4. data/CHANGELOG.md +161 -0
  5. data/CONTRIBUTING.md +11 -0
  6. data/Gemfile +20 -0
  7. data/LICENSE +7 -0
  8. data/README.md +324 -0
  9. data/Rakefile +52 -31
  10. data/draper.gemspec +11 -9
  11. data/lib/draper/automatic_delegation.rb +50 -0
  12. data/lib/draper/collection_decorator.rb +107 -0
  13. data/lib/draper/decoratable.rb +83 -0
  14. data/lib/draper/decorated_association.rb +74 -0
  15. data/lib/draper/decorator.rb +238 -0
  16. data/lib/draper/delegation.rb +13 -0
  17. data/lib/draper/finders.rb +37 -0
  18. data/lib/draper/helper_proxy.rb +17 -0
  19. data/lib/draper/lazy_helpers.rb +10 -6
  20. data/lib/draper/railtie.rb +27 -21
  21. data/lib/draper/tasks/test.rake +22 -0
  22. data/lib/draper/test/devise_helper.rb +34 -0
  23. data/lib/draper/test/minitest_integration.rb +2 -3
  24. data/lib/draper/test/rspec_integration.rb +3 -13
  25. data/lib/draper/test_case.rb +33 -0
  26. data/lib/draper/version.rb +1 -1
  27. data/lib/draper/view_context.rb +12 -17
  28. data/lib/draper/view_helpers.rb +37 -0
  29. data/lib/draper.rb +47 -8
  30. data/lib/generators/decorator/decorator_generator.rb +10 -2
  31. data/lib/generators/decorator/templates/decorator.rb +12 -27
  32. data/lib/generators/mini_test/decorator_generator.rb +20 -0
  33. data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
  34. data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
  35. data/lib/generators/test_unit/templates/decorator_test.rb +1 -1
  36. data/spec/draper/collection_decorator_spec.rb +286 -0
  37. data/spec/draper/decoratable_spec.rb +192 -0
  38. data/spec/draper/decorated_association_spec.rb +142 -0
  39. data/spec/draper/decorator_spec.rb +624 -0
  40. data/spec/draper/finders_spec.rb +132 -0
  41. data/spec/draper/helper_proxy_spec.rb +12 -0
  42. data/spec/draper/view_helpers_spec.rb +41 -0
  43. data/spec/dummy/.rspec +2 -0
  44. data/spec/dummy/README.rdoc +261 -0
  45. data/spec/dummy/Rakefile +7 -0
  46. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  47. data/spec/dummy/app/controllers/localized_urls.rb +5 -0
  48. data/spec/dummy/app/controllers/posts_controller.rb +11 -0
  49. data/spec/dummy/app/decorators/post_decorator.rb +32 -0
  50. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  51. data/spec/dummy/app/mailers/application_mailer.rb +3 -0
  52. data/spec/dummy/app/mailers/post_mailer.rb +9 -0
  53. data/spec/dummy/app/models/post.rb +3 -0
  54. data/spec/dummy/app/views/layouts/application.html.erb +11 -0
  55. data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +1 -0
  56. data/spec/dummy/app/views/posts/_post.html.erb +19 -0
  57. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  58. data/spec/dummy/config/application.rb +64 -0
  59. data/spec/dummy/config/boot.rb +10 -0
  60. data/spec/dummy/config/database.yml +25 -0
  61. data/spec/dummy/config/environment.rb +5 -0
  62. data/spec/dummy/config/environments/development.rb +34 -0
  63. data/spec/dummy/config/environments/production.rb +55 -0
  64. data/spec/dummy/config/environments/test.rb +32 -0
  65. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  66. data/spec/dummy/config/initializers/inflections.rb +15 -0
  67. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  68. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  69. data/spec/dummy/config/initializers/session_store.rb +8 -0
  70. data/spec/dummy/config/locales/en.yml +5 -0
  71. data/spec/dummy/config/routes.rb +7 -0
  72. data/spec/dummy/config.ru +4 -0
  73. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
  74. data/spec/dummy/db/schema.rb +21 -0
  75. data/spec/dummy/db/seeds.rb +2 -0
  76. data/spec/dummy/lib/tasks/test.rake +10 -0
  77. data/spec/dummy/log/.gitkeep +0 -0
  78. data/spec/dummy/mini_test/mini_test_integration_test.rb +46 -0
  79. data/spec/dummy/public/404.html +26 -0
  80. data/spec/dummy/public/422.html +26 -0
  81. data/spec/dummy/public/500.html +25 -0
  82. data/spec/dummy/public/favicon.ico +0 -0
  83. data/spec/dummy/script/rails +6 -0
  84. data/spec/dummy/spec/decorators/post_decorator_spec.rb +26 -0
  85. data/spec/dummy/spec/decorators/rspec_integration_spec.rb +19 -0
  86. data/spec/dummy/spec/mailers/post_mailer_spec.rb +29 -0
  87. data/spec/dummy/spec/models/post_spec.rb +14 -0
  88. data/spec/dummy/spec/spec_helper.rb +8 -0
  89. data/spec/generators/decorator/decorator_generator_spec.rb +47 -7
  90. data/spec/integration/integration_spec.rb +33 -0
  91. data/{performance → spec/performance}/decorators.rb +2 -4
  92. data/spec/spec_helper.rb +20 -45
  93. data/spec/support/action_controller.rb +12 -0
  94. data/spec/support/active_model.rb +7 -0
  95. data/spec/support/active_record.rb +9 -0
  96. data/spec/support/{samples → decorators}/decorator_with_application_helper.rb +1 -1
  97. data/spec/support/decorators/namespaced_product_decorator.rb +5 -0
  98. data/spec/support/decorators/non_active_model_product_decorator.rb +2 -0
  99. data/spec/support/decorators/product_decorator.rb +23 -0
  100. data/spec/support/{samples → decorators}/products_decorator.rb +1 -1
  101. data/spec/support/decorators/some_thing_decorator.rb +2 -0
  102. data/spec/support/{samples → decorators}/specific_product_decorator.rb +0 -2
  103. data/spec/support/dummy_app.rb +85 -0
  104. data/spec/support/matchers/have_text.rb +50 -0
  105. data/spec/support/{samples → models}/namespaced_product.rb +1 -3
  106. data/spec/support/{samples → models}/non_active_model_product.rb +1 -0
  107. data/spec/support/{samples → models}/product.rb +17 -2
  108. data/spec/support/models/some_thing.rb +5 -0
  109. data/spec/support/models/uninferrable_decorator_model.rb +3 -0
  110. metadata +241 -87
  111. data/CHANGELOG.markdown +0 -57
  112. data/Readme.markdown +0 -387
  113. data/lib/draper/active_model_support.rb +0 -27
  114. data/lib/draper/base.rb +0 -312
  115. data/lib/draper/decorated_enumerable_proxy.rb +0 -90
  116. data/lib/draper/model_support.rb +0 -25
  117. data/lib/draper/rspec_integration.rb +0 -2
  118. data/lib/draper/system.rb +0 -18
  119. data/spec/draper/base_spec.rb +0 -873
  120. data/spec/draper/decorated_enumerable_proxy_spec.rb +0 -45
  121. data/spec/draper/model_support_spec.rb +0 -48
  122. data/spec/minitest-rails/spec_type_spec.rb +0 -63
  123. data/spec/support/samples/active_record.rb +0 -17
  124. data/spec/support/samples/decorator.rb +0 -5
  125. data/spec/support/samples/decorator_with_allows.rb +0 -3
  126. data/spec/support/samples/decorator_with_denies.rb +0 -3
  127. data/spec/support/samples/decorator_with_denies_all.rb +0 -3
  128. data/spec/support/samples/decorator_with_multiple_allows.rb +0 -4
  129. data/spec/support/samples/decorator_with_special_methods.rb +0 -13
  130. data/spec/support/samples/enumerable_proxy.rb +0 -3
  131. data/spec/support/samples/namespaced_product_decorator.rb +0 -7
  132. data/spec/support/samples/product_decorator.rb +0 -7
  133. data/spec/support/samples/sequel_product.rb +0 -13
  134. data/spec/support/samples/some_thing.rb +0 -2
  135. data/spec/support/samples/some_thing_decorator.rb +0 -3
  136. /data/{performance → spec/performance}/active_record.rb +0 -0
  137. /data/{performance/bechmark.rb → spec/performance/benchmark.rb} +0 -0
  138. /data/{performance → spec/performance}/models.rb +0 -0
  139. /data/spec/support/{samples → decorators}/widget_decorator.rb +0 -0
  140. /data/spec/support/{samples → models}/widget.rb +0 -0
@@ -0,0 +1,286 @@
1
+ require 'spec_helper'
2
+
3
+ describe Draper::CollectionDecorator do
4
+ before { ApplicationController.new.view_context }
5
+ subject { Draper::CollectionDecorator.new(source, with: ProductDecorator) }
6
+ let(:source){ [Product.new, Product.new] }
7
+ let(:non_active_model_source){ NonActiveModelProduct.new }
8
+
9
+ it "decorates a collection's items" do
10
+ subject.each do |item|
11
+ item.should be_decorated_with ProductDecorator
12
+ end
13
+ end
14
+
15
+ it "sets the decorated items' source models" do
16
+ subject.map{|item| item.source}.should == source
17
+ end
18
+
19
+ context "with context" do
20
+ subject { Draper::CollectionDecorator.new(source, with: ProductDecorator, context: {some: 'context'}) }
21
+
22
+ its(:context) { should == {some: 'context'} }
23
+
24
+ it "passes context to the individual decorators" do
25
+ subject.each do |item|
26
+ item.context.should == {some: 'context'}
27
+ end
28
+ end
29
+
30
+ it "does not tie the individual decorators' contexts together" do
31
+ subject.each do |item|
32
+ item.context.should == {some: 'context'}
33
+ item.context = {alt: 'context'}
34
+ item.context.should == {alt: 'context'}
35
+ end
36
+ end
37
+
38
+ describe "#context=" do
39
+ it "updates the collection decorator's context" do
40
+ subject.context = {other: 'context'}
41
+ subject.context.should == {other: 'context'}
42
+ end
43
+
44
+ context "when the collection is already decorated" do
45
+ it "updates the items' context" do
46
+ subject.decorated_collection
47
+ subject.context = {other: 'context'}
48
+ subject.each do |item|
49
+ item.context.should == {other: 'context'}
50
+ end
51
+ end
52
+ end
53
+
54
+ context "when the collection has not yet been decorated" do
55
+ it "does not trigger decoration" do
56
+ subject.should_not_receive(:decorated_collection)
57
+ subject.context = {other: 'context'}
58
+ end
59
+
60
+ it "sets context after decoration is triggered" do
61
+ subject.context = {other: 'context'}
62
+ subject.each do |item|
63
+ item.context.should == {other: 'context'}
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ describe "#initialize" do
71
+ describe "options validation" do
72
+ let(:valid_options) { {with: ProductDecorator, context: {}} }
73
+
74
+ it "does not raise error on valid options" do
75
+ expect { Draper::CollectionDecorator.new(source, valid_options) }.to_not raise_error
76
+ end
77
+
78
+ it "raises error on invalid options" do
79
+ expect { Draper::CollectionDecorator.new(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, /Unknown key/)
80
+ end
81
+ end
82
+ end
83
+
84
+ describe "item decoration" do
85
+ subject { subject_class.new(source, options) }
86
+ let(:decorator_classes) { subject.decorated_collection.map(&:class) }
87
+ let(:source) { [Product.new, Widget.new] }
88
+
89
+ context "when the :with option was given" do
90
+ let(:options) { {with: SpecificProductDecorator} }
91
+
92
+ context "and the decorator can't be inferred from the class" do
93
+ let(:subject_class) { Draper::CollectionDecorator }
94
+
95
+ it "uses the :with option" do
96
+ decorator_classes.should == [SpecificProductDecorator, SpecificProductDecorator]
97
+ end
98
+ end
99
+
100
+ context "and the decorator is inferrable from the class" do
101
+ let(:subject_class) { ProductsDecorator }
102
+
103
+ it "uses the :with option" do
104
+ decorator_classes.should == [SpecificProductDecorator, SpecificProductDecorator]
105
+ end
106
+ end
107
+ end
108
+
109
+ context "when the :with option was not given" do
110
+ let(:options) { {} }
111
+
112
+ context "and the decorator can't be inferred from the class" do
113
+ let(:subject_class) { Draper::CollectionDecorator }
114
+
115
+ it "infers the decorator from each item" do
116
+ decorator_classes.should == [ProductDecorator, WidgetDecorator]
117
+ end
118
+ end
119
+
120
+ context "and the decorator is inferrable from the class" do
121
+ let(:subject_class) { ProductsDecorator}
122
+
123
+ it "infers the decorator" do
124
+ decorator_classes.should == [ProductDecorator, ProductDecorator]
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ describe ".delegate" do
131
+ subject { Class.new(Draper::CollectionDecorator) }
132
+
133
+ it "defaults the :to option to :source" do
134
+ Draper::CollectionDecorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :source)
135
+ subject.delegate :foo, :bar
136
+ end
137
+
138
+ it "does not overwrite the :to option if supplied" do
139
+ Draper::CollectionDecorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :baz)
140
+ subject.delegate :foo, :bar, to: :baz
141
+ end
142
+ end
143
+
144
+ describe "#find" do
145
+ context "with a block" do
146
+ it "decorates Enumerable#find" do
147
+ subject.decorated_collection.should_receive(:find)
148
+ subject.find {|p| p.title == "title" }
149
+ end
150
+ end
151
+
152
+ context "without a block" do
153
+ it "decorates Model.find" do
154
+ source.should_not_receive(:find)
155
+ Product.should_receive(:find).with(1).and_return(:product)
156
+ subject.find(1).should == ProductDecorator.new(:product)
157
+ end
158
+ end
159
+ end
160
+
161
+ describe "#helpers" do
162
+ it "returns a HelperProxy" do
163
+ subject.helpers.should be_a Draper::HelperProxy
164
+ end
165
+
166
+ it "is aliased to #h" do
167
+ subject.h.should be subject.helpers
168
+ end
169
+
170
+ it "initializes the wrapper only once" do
171
+ helper_proxy = subject.helpers
172
+ helper_proxy.stub(:test_method) { "test_method" }
173
+ subject.helpers.test_method.should == "test_method"
174
+ subject.helpers.test_method.should == "test_method"
175
+ end
176
+ end
177
+
178
+ describe "#localize" do
179
+ before { subject.helpers.should_receive(:localize).with(:an_object, {some: 'parameter'}) }
180
+
181
+ it "delegates to helpers" do
182
+ subject.localize(:an_object, some: 'parameter')
183
+ end
184
+
185
+ it "is aliased to #l" do
186
+ subject.l(:an_object, some: 'parameter')
187
+ end
188
+ end
189
+
190
+ describe ".helpers" do
191
+ it "returns a HelperProxy" do
192
+ subject.class.helpers.should be_a Draper::HelperProxy
193
+ end
194
+
195
+ it "is aliased to .h" do
196
+ subject.class.h.should be subject.class.helpers
197
+ end
198
+ end
199
+
200
+ describe "#to_ary" do
201
+ # required for `render @collection` in Rails
202
+ it "delegates to the decorated collection" do
203
+ subject.decorated_collection.stub to_ary: :an_array
204
+ subject.to_ary.should == :an_array
205
+ end
206
+ end
207
+
208
+ it "delegates array methods to the decorated collection" do
209
+ subject.decorated_collection.should_receive(:[]).with(42).and_return(:the_answer)
210
+ subject[42].should == :the_answer
211
+ end
212
+
213
+ describe "#==" do
214
+ context "when comparing to a collection decorator with the same source" do
215
+ it "returns true" do
216
+ a = Draper::CollectionDecorator.new(source, with: ProductDecorator)
217
+ b = ProductsDecorator.new(source)
218
+ a.should == b
219
+ end
220
+ end
221
+
222
+ context "when comparing to a collection decorator with a different source" do
223
+ it "returns false" do
224
+ a = Draper::CollectionDecorator.new(source, with: ProductDecorator)
225
+ b = ProductsDecorator.new([Product.new])
226
+ a.should_not == b
227
+ end
228
+ end
229
+
230
+ context "when comparing to a collection of the same items" do
231
+ it "returns true" do
232
+ a = Draper::CollectionDecorator.new(source, with: ProductDecorator)
233
+ b = source.dup
234
+ a.should == b
235
+ end
236
+ end
237
+
238
+ context "when comparing to a collection of different items" do
239
+ it "returns true" do
240
+ a = Draper::CollectionDecorator.new(source, with: ProductDecorator)
241
+ b = [Product.new]
242
+ a.should_not == b
243
+ end
244
+ end
245
+
246
+ context "when the decorated collection has been modified" do
247
+ it "is no longer equal to the source" do
248
+ a = Draper::CollectionDecorator.new(source, with: ProductDecorator)
249
+ b = source.dup
250
+
251
+ a << Product.new.decorate
252
+ a.should_not == b
253
+ end
254
+ end
255
+ end
256
+
257
+ describe "#to_s" do
258
+ subject { Draper::CollectionDecorator.new(source, options) }
259
+ let(:source) { ["a", "b", "c"] }
260
+
261
+ context "when :with option was given" do
262
+ let(:options) { {with: ProductDecorator} }
263
+
264
+ it "returns a string representation of the CollectionDecorator" do
265
+ subject.to_s.should == '#<Draper::CollectionDecorator of ProductDecorator for ["a", "b", "c"]>'
266
+ end
267
+ end
268
+
269
+ context "when :with option was not given" do
270
+ let(:options) { {} }
271
+
272
+ it "returns a string representation of the CollectionDecorator" do
273
+ subject.to_s.should == '#<Draper::CollectionDecorator of inferred decorators for ["a", "b", "c"]>'
274
+ end
275
+ end
276
+
277
+ context "for a custom subclass" do
278
+ subject { ProductsDecorator.new(source) }
279
+
280
+ it "uses the custom class name" do
281
+ subject.to_s.should =~ /ProductsDecorator/
282
+ end
283
+ end
284
+ end
285
+
286
+ end
@@ -0,0 +1,192 @@
1
+ require 'spec_helper'
2
+
3
+ describe Draper::Decoratable do
4
+ subject { Product.new }
5
+
6
+ describe "#decorate" do
7
+ it "returns a decorator for self" do
8
+ subject.decorate.should be_a ProductDecorator
9
+ subject.decorate.source.should be subject
10
+ end
11
+
12
+ it "accepts context" do
13
+ decorator = subject.decorate(context: {some: 'context'})
14
+ decorator.context.should == {some: 'context'}
15
+ end
16
+
17
+ it "is not memoized" do
18
+ subject.decorate.should_not be subject.decorate
19
+ end
20
+ end
21
+
22
+ describe "#applied_decorators" do
23
+ it "returns an empty list" do
24
+ subject.applied_decorators.should be_empty
25
+ end
26
+ end
27
+
28
+ describe "#decorated_with?" do
29
+ it "returns false" do
30
+ subject.should_not be_decorated_with ProductDecorator
31
+ end
32
+ end
33
+
34
+ describe "#decorated?" do
35
+ it "returns false" do
36
+ subject.should_not be_decorated
37
+ end
38
+ end
39
+
40
+ describe "#decorator_class" do
41
+ it "delegates to .decorator_class" do
42
+ Product.stub(:decorator_class).and_return(WidgetDecorator)
43
+ product = Product.new
44
+ product.decorator_class.should be WidgetDecorator
45
+ end
46
+ end
47
+
48
+ describe "#==" do
49
+ context "with itself" do
50
+ it "returns true" do
51
+ (subject == subject).should be_true
52
+ end
53
+ end
54
+
55
+ context "with another instance" do
56
+ it "returns false" do
57
+ (subject == Product.new).should be_false
58
+ end
59
+ end
60
+
61
+ context "with a decorated version of itself" do
62
+ it "returns true" do
63
+ decorator = double(source: subject)
64
+ (subject == decorator).should be_true
65
+ end
66
+ end
67
+
68
+ context "with a decorated other instance" do
69
+ it "returns false" do
70
+ decorator = double(source: Product.new)
71
+ (subject == decorator).should be_false
72
+ end
73
+ end
74
+ end
75
+
76
+ describe "#===" do
77
+ context "with itself" do
78
+ it "returns true" do
79
+ (subject === subject).should be_true
80
+ end
81
+ end
82
+
83
+ context "with another instance" do
84
+ it "returns false" do
85
+ (subject === Product.new).should be_false
86
+ end
87
+ end
88
+
89
+ context "with a decorated version of itself" do
90
+ it "returns true" do
91
+ decorator = double(source: subject)
92
+ (subject === decorator).should be_true
93
+ end
94
+ end
95
+
96
+ context "with a decorated other instance" do
97
+ it "returns false" do
98
+ decorator = double(source: Product.new)
99
+ (subject === decorator).should be_false
100
+ end
101
+ end
102
+ end
103
+
104
+ describe ".====" do
105
+ context "with an instance" do
106
+ it "returns true" do
107
+ (Product === Product.new).should be_true
108
+ end
109
+ end
110
+
111
+ context "with a derived instance" do
112
+ it "returns true" do
113
+ (Product === Widget.new).should be_true
114
+ end
115
+ end
116
+
117
+ context "with an unrelated instance" do
118
+ it "returns false" do
119
+ (Product === Object.new).should be_false
120
+ end
121
+ end
122
+
123
+ context "with a decorated instance" do
124
+ it "returns true" do
125
+ decorator = double(source: Product.new)
126
+ (Product === decorator).should be_true
127
+ end
128
+ end
129
+
130
+ context "with a decorated derived instance" do
131
+ it "returns true" do
132
+ decorator = double(source: Widget.new)
133
+ (Product === decorator).should be_true
134
+ end
135
+ end
136
+
137
+ context "with a decorated unrelated instance" do
138
+ it "returns false" do
139
+ decorator = double(source: Object.new)
140
+ (Product === decorator).should be_false
141
+ end
142
+ end
143
+ end
144
+
145
+ describe ".decorate" do
146
+ it "returns a collection decorator" do
147
+ Product.stub(:scoped).and_return([Product.new])
148
+ Product.stub(:decorator_class).and_return(WidgetDecorator)
149
+ decorator = Product.decorate
150
+
151
+ decorator.should be_a Draper::CollectionDecorator
152
+ decorator.decorator_class.should be WidgetDecorator
153
+ decorator.should == Product.scoped
154
+ end
155
+
156
+ it "accepts context" do
157
+ decorator = Product.decorate(context: {some: 'context'})
158
+ decorator.context.should == {some: 'context'}
159
+ end
160
+
161
+ it "is not memoized" do
162
+ Product.decorate.should_not be Product.decorate
163
+ end
164
+ end
165
+
166
+ describe ".decorator_class" do
167
+ context "for non-ActiveModel classes" do
168
+ it "infers the decorator from the class" do
169
+ NonActiveModelProduct.decorator_class.should be NonActiveModelProductDecorator
170
+ end
171
+ end
172
+
173
+ context "for ActiveModel classes" do
174
+ it "infers the decorator from the model name" do
175
+ Product.stub(:model_name).and_return("Widget")
176
+ Product.decorator_class.should be WidgetDecorator
177
+ end
178
+ end
179
+
180
+ context "for namespaced ActiveModel classes" do
181
+ it "infers the decorator from the model name" do
182
+ Namespace::Product.decorator_class.should be Namespace::ProductDecorator
183
+ end
184
+ end
185
+
186
+ context "when the decorator can't be inferred" do
187
+ it "throws an UninferrableDecoratorError" do
188
+ expect{UninferrableDecoratorModel.decorator_class}.to raise_error Draper::UninferrableDecoratorError
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,142 @@
1
+ require 'spec_helper'
2
+
3
+ describe Draper::DecoratedAssociation do
4
+ let(:decorated_association) { Draper::DecoratedAssociation.new(owner, :association, options) }
5
+ let(:source) { Product.new }
6
+ let(:owner) { source.decorate }
7
+ let(:options) { {} }
8
+
9
+ describe "#initialize" do
10
+ describe "options validation" do
11
+ let(:valid_options) { {with: ProductDecorator, scope: :foo, context: {}} }
12
+
13
+ it "does not raise error on valid options" do
14
+ expect { Draper::DecoratedAssociation.new(owner, :association, valid_options) }.to_not raise_error
15
+ end
16
+
17
+ it "raises error on invalid options" do
18
+ expect { Draper::DecoratedAssociation.new(owner, :association, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, /Unknown key/)
19
+ end
20
+ end
21
+ end
22
+
23
+ describe "#call" do
24
+ let(:context) { {foo: "bar"} }
25
+ let(:expected_options) { {context: context} }
26
+
27
+ before do
28
+ source.stub association: associated
29
+ decorated_association.stub context: context
30
+ end
31
+
32
+ context "for a singular association" do
33
+ let(:associated) { Product.new }
34
+ let(:decorator) { SpecificProductDecorator }
35
+
36
+ context "when :with option was given" do
37
+ let(:options) { {with: decorator} }
38
+
39
+ it "uses the specified decorator" do
40
+ decorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated)
41
+ decorated_association.call.should be :decorated
42
+ end
43
+ end
44
+
45
+ context "when :with option was not given" do
46
+ it "infers the decorator" do
47
+ associated.stub(:decorator_class).and_return(decorator)
48
+ decorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated)
49
+ decorated_association.call.should be :decorated
50
+ end
51
+ end
52
+ end
53
+
54
+ context "for a collection association" do
55
+ let(:associated) { [Product.new, Widget.new] }
56
+ let(:collection_decorator) { ProductsDecorator }
57
+
58
+ context "when :with option is a collection decorator" do
59
+ let(:options) { {with: collection_decorator} }
60
+
61
+ it "uses the specified decorator" do
62
+ collection_decorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated_collection)
63
+ decorated_association.call.should be :decorated_collection
64
+ end
65
+ end
66
+
67
+ context "when :with option is a singular decorator" do
68
+ let(:options) { {with: decorator} }
69
+ let(:decorator) { SpecificProductDecorator }
70
+
71
+ it "uses a CollectionDecorator of the specified decorator" do
72
+ decorator.should_receive(:decorate_collection).with(associated, expected_options).and_return(:decorated_collection)
73
+ decorated_association.call.should be :decorated_collection
74
+ end
75
+ end
76
+
77
+ context "when :with option was not given" do
78
+ context "when the collection is decoratable" do
79
+ it "infers the decorator" do
80
+ associated.stub(:decorator_class).and_return(collection_decorator)
81
+ collection_decorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated_collection)
82
+ decorated_association.call.should be :decorated_collection
83
+ end
84
+ end
85
+
86
+ context "when the collection is not decoratable" do
87
+ it "uses a CollectionDecorator of inferred decorators" do
88
+ Draper::CollectionDecorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated_collection)
89
+ decorated_association.call.should be :decorated_collection
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ context "with a scope" do
96
+ let(:associated) { [] }
97
+ let(:options) { {scope: :foo} }
98
+
99
+ it "applies the scope before decoration" do
100
+ scoped = [Product.new]
101
+ associated.should_receive(:foo).and_return(scoped)
102
+ decorated_association.call.should == scoped
103
+ end
104
+ end
105
+ end
106
+
107
+ describe "#context" do
108
+ before { owner.stub context: :owner_context }
109
+
110
+ context "when :context option was given" do
111
+ let(:options) { {context: context} }
112
+
113
+ context "and is callable" do
114
+ let(:context) { ->(*){ :dynamic_context } }
115
+
116
+ it "calls it with the owner's context" do
117
+ context.should_receive(:call).with(:owner_context)
118
+ decorated_association.context
119
+ end
120
+
121
+ it "returns the lambda's return value" do
122
+ decorated_association.context.should be :dynamic_context
123
+ end
124
+ end
125
+
126
+ context "and is not callable" do
127
+ let(:context) { :static_context }
128
+
129
+ it "returns the specified value" do
130
+ decorated_association.context.should be :static_context
131
+ end
132
+ end
133
+ end
134
+
135
+ context "when :context option was not given" do
136
+ it "returns the owner's context" do
137
+ decorated_association.context.should be :owner_context
138
+ end
139
+ end
140
+ end
141
+
142
+ end