draper 1.0.0 → 1.1.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.
- data/CHANGELOG.md +3 -0
- data/CONTRIBUTING.md +5 -1
- data/Gemfile +23 -9
- data/README.md +144 -52
- data/Rakefile +1 -0
- data/draper.gemspec +1 -1
- data/lib/draper.rb +9 -6
- data/lib/draper/decoratable.rb +3 -7
- data/lib/draper/decoratable/equality.rb +14 -0
- data/lib/draper/decorator.rb +4 -7
- data/lib/draper/helper_proxy.rb +22 -3
- data/lib/draper/test/devise_helper.rb +18 -22
- data/lib/draper/test/rspec_integration.rb +4 -0
- data/lib/draper/test_case.rb +20 -0
- data/lib/draper/version.rb +1 -1
- data/lib/draper/view_context.rb +75 -13
- data/lib/draper/view_context/build_strategy.rb +48 -0
- data/lib/draper/view_helpers.rb +2 -2
- data/spec/draper/collection_decorator_spec.rb +169 -196
- data/spec/draper/decoratable/equality_spec.rb +10 -0
- data/spec/draper/decoratable_spec.rb +107 -132
- data/spec/draper/decorated_association_spec.rb +99 -96
- data/spec/draper/decorator_spec.rb +408 -434
- data/spec/draper/finders_spec.rb +160 -126
- data/spec/draper/helper_proxy_spec.rb +38 -8
- data/spec/draper/view_context/build_strategy_spec.rb +116 -0
- data/spec/draper/view_context_spec.rb +154 -0
- data/spec/draper/view_helpers_spec.rb +4 -37
- data/spec/dummy/app/controllers/posts_controller.rb +7 -0
- data/spec/dummy/app/decorators/post_decorator.rb +26 -2
- data/spec/dummy/app/helpers/application_helper.rb +3 -0
- data/spec/dummy/app/mailers/post_mailer.rb +10 -0
- data/spec/dummy/app/models/admin.rb +5 -0
- data/spec/dummy/app/models/mongoid_post.rb +5 -0
- data/spec/dummy/app/models/user.rb +5 -0
- data/spec/dummy/app/views/posts/_post.html.erb +15 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/config/application.rb +9 -3
- data/spec/dummy/config/boot.rb +2 -7
- data/spec/dummy/config/environments/development.rb +2 -3
- data/spec/dummy/config/environments/production.rb +2 -0
- data/spec/dummy/config/environments/test.rb +3 -4
- data/spec/dummy/config/initializers/secret_token.rb +1 -0
- data/spec/dummy/config/mongoid.yml +80 -0
- data/spec/dummy/config/routes.rb +2 -0
- data/spec/dummy/fast_spec/post_decorator_spec.rb +38 -0
- data/spec/dummy/lib/tasks/test.rake +11 -5
- data/spec/dummy/spec/decorators/devise_spec.rb +64 -0
- data/spec/dummy/spec/decorators/helpers_spec.rb +21 -0
- data/spec/dummy/spec/decorators/post_decorator_spec.rb +26 -6
- data/spec/dummy/spec/decorators/spec_type_spec.rb +7 -0
- data/spec/dummy/spec/decorators/view_context_spec.rb +22 -0
- data/spec/dummy/spec/mailers/post_mailer_spec.rb +10 -6
- data/spec/dummy/spec/models/mongoid_post_spec.rb +10 -0
- data/spec/dummy/spec/models/post_spec.rb +5 -5
- data/spec/dummy/spec/spec_helper.rb +1 -0
- data/spec/dummy/test/decorators/minitest/devise_test.rb +64 -0
- data/spec/dummy/test/decorators/minitest/helpers_test.rb +21 -0
- data/spec/dummy/{mini_test/mini_test_integration_test.rb → test/decorators/minitest/spec_type_test.rb} +9 -3
- data/spec/dummy/test/decorators/minitest/view_context_test.rb +24 -0
- data/spec/dummy/test/decorators/test_unit/devise_test.rb +64 -0
- data/spec/dummy/test/decorators/test_unit/helpers_test.rb +21 -0
- data/spec/dummy/test/decorators/test_unit/view_context_test.rb +24 -0
- data/spec/dummy/test/minitest_helper.rb +4 -0
- data/spec/dummy/test/test_helper.rb +3 -0
- data/spec/generators/decorator/decorator_generator_spec.rb +1 -0
- data/spec/integration/integration_spec.rb +31 -6
- data/spec/spec_helper.rb +32 -25
- data/spec/support/shared_examples/decoratable_equality.rb +40 -0
- data/spec/support/shared_examples/view_helpers.rb +39 -0
- metadata +56 -44
- data/spec/dummy/README.rdoc +0 -261
- data/spec/dummy/spec/decorators/rspec_integration_spec.rb +0 -19
- data/spec/support/action_controller.rb +0 -12
- data/spec/support/active_model.rb +0 -7
- data/spec/support/active_record.rb +0 -9
- data/spec/support/decorators/decorator_with_application_helper.rb +0 -25
- data/spec/support/decorators/namespaced_product_decorator.rb +0 -5
- data/spec/support/decorators/non_active_model_product_decorator.rb +0 -2
- data/spec/support/decorators/product_decorator.rb +0 -23
- data/spec/support/decorators/products_decorator.rb +0 -10
- data/spec/support/decorators/some_thing_decorator.rb +0 -2
- data/spec/support/decorators/specific_product_decorator.rb +0 -2
- data/spec/support/decorators/widget_decorator.rb +0 -2
- data/spec/support/models/namespaced_product.rb +0 -49
- data/spec/support/models/non_active_model_product.rb +0 -3
- data/spec/support/models/product.rb +0 -95
- data/spec/support/models/some_thing.rb +0 -5
- data/spec/support/models/uninferrable_decorator_model.rb +0 -3
- data/spec/support/models/widget.rb +0 -2
@@ -1,624 +1,598 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'support/shared_examples/view_helpers'
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
let(:decorator_class) { Draper::Decorator }
|
7
|
-
let(:source) { Product.new }
|
4
|
+
module Draper
|
5
|
+
describe Decorator do
|
6
|
+
it_behaves_like "view helpers", Decorator.new(Model.new)
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
describe "#initialize" do
|
9
|
+
describe "options validation" do
|
10
|
+
it "does not raise error on valid options" do
|
11
|
+
valid_options = {context: {}}
|
12
|
+
expect{Decorator.new(Model.new, valid_options)}.not_to raise_error
|
13
|
+
end
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
+
it "raises error on invalid options" do
|
16
|
+
expect{Decorator.new(Model.new, foo: "bar")}.to raise_error ArgumentError, /Unknown key/
|
17
|
+
end
|
15
18
|
end
|
16
19
|
|
17
|
-
it "
|
18
|
-
|
19
|
-
|
20
|
-
end
|
20
|
+
it "sets the source" do
|
21
|
+
source = Model.new
|
22
|
+
decorator = Decorator.new(source)
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
end
|
24
|
+
expect(decorator.source).to be source
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
27
|
+
it "stores context" do
|
28
|
+
context = {some: "context"}
|
29
|
+
decorator = Decorator.new(Model.new, context: context)
|
30
30
|
|
31
|
-
|
32
|
-
it "does not redecorate" do
|
33
|
-
decorator = ProductDecorator.new(source)
|
34
|
-
ProductDecorator.new(decorator).source.should be source
|
31
|
+
expect(decorator.context).to be context
|
35
32
|
end
|
36
33
|
|
37
|
-
context "when
|
38
|
-
it "
|
39
|
-
|
40
|
-
|
34
|
+
context "when decorating an instance of itself" do
|
35
|
+
it "applies to the source instead" do
|
36
|
+
source = Model.new
|
37
|
+
decorated = Decorator.new(source)
|
38
|
+
redecorated = Decorator.new(decorated)
|
39
|
+
|
40
|
+
expect(redecorated.source).to be source
|
41
41
|
end
|
42
|
-
end
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
context "with context" do
|
44
|
+
it "overwrites existing context" do
|
45
|
+
decorated = Decorator.new(Model.new, context: {some: "context"})
|
46
|
+
new_context = {other: "context"}
|
47
|
+
redecorated = Decorator.new(decorated, context: new_context)
|
48
|
+
|
49
|
+
expect(redecorated.context).to be new_context
|
50
|
+
end
|
48
51
|
end
|
49
|
-
end
|
50
|
-
end
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
context "without context" do
|
54
|
+
it "preserves existing context" do
|
55
|
+
old_context = {some: "context"}
|
56
|
+
decorated = Decorator.new(Model.new, context: old_context)
|
57
|
+
redecorated = Decorator.new(decorated)
|
58
|
+
|
59
|
+
expect(redecorated.context).to be old_context
|
60
|
+
end
|
61
|
+
end
|
56
62
|
end
|
57
63
|
|
58
|
-
|
59
|
-
|
64
|
+
it "decorates other decorators" do
|
65
|
+
decorated = OtherDecorator.new(Model.new)
|
66
|
+
redecorated = Decorator.new(decorated)
|
67
|
+
|
68
|
+
expect(redecorated.source).to be decorated
|
69
|
+
end
|
60
70
|
|
71
|
+
context "when it has been applied previously" do
|
61
72
|
it "warns" do
|
73
|
+
decorated = OtherDecorator.new(Decorator.new(Model.new))
|
74
|
+
|
62
75
|
warning_message = nil
|
63
76
|
Object.any_instance.stub(:warn) {|message| warning_message = message }
|
64
77
|
|
65
|
-
expect{
|
66
|
-
warning_message.
|
67
|
-
warning_message.
|
78
|
+
expect{Decorator.new(decorated)}.to change{warning_message}
|
79
|
+
expect(warning_message).to start_with "Reapplying Draper::Decorator"
|
80
|
+
expect(warning_message).to include caller(1).first
|
68
81
|
end
|
69
82
|
|
70
|
-
it "
|
83
|
+
it "decorates anyway" do
|
84
|
+
decorated = OtherDecorator.new(Decorator.new(Model.new))
|
71
85
|
Object.any_instance.stub(:warn)
|
72
|
-
|
86
|
+
redecorated = Decorator.decorate(decorated)
|
87
|
+
|
88
|
+
expect(redecorated.source).to be decorated
|
73
89
|
end
|
74
90
|
end
|
75
91
|
end
|
76
|
-
end
|
77
|
-
|
78
|
-
describe "#context=" do
|
79
|
-
it "modifies the context" do
|
80
|
-
decorator = decorator_class.new(source, context: {some: 'context'})
|
81
|
-
decorator.context = {some: 'other_context'}
|
82
|
-
decorator.context.should == {some: 'other_context'}
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
describe ".decorate_collection" do
|
87
|
-
let(:source) { [Product.new, Widget.new] }
|
88
|
-
|
89
|
-
describe "options validation" do
|
90
|
-
let(:valid_options) { {with: :infer, context: {}} }
|
91
|
-
before(:each) { Draper::CollectionDecorator.stub(:new) }
|
92
92
|
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
describe "#context=" do
|
94
|
+
it "modifies the context" do
|
95
|
+
decorator = Decorator.new(Model.new, context: {some: "context"})
|
96
|
+
new_context = {other: "context"}
|
96
97
|
|
97
|
-
|
98
|
-
expect
|
98
|
+
decorator.context = new_context
|
99
|
+
expect(decorator.context).to be new_context
|
99
100
|
end
|
100
101
|
end
|
101
102
|
|
102
|
-
|
103
|
-
|
103
|
+
describe ".decorate_collection" do
|
104
|
+
describe "options validation" do
|
105
|
+
before { CollectionDecorator.stub(:new) }
|
104
106
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
107
|
+
it "does not raise error on valid options" do
|
108
|
+
valid_options = {with: OtherDecorator, context: {}}
|
109
|
+
expect{Decorator.decorate_collection([], valid_options)}.not_to raise_error
|
110
|
+
end
|
109
111
|
|
110
|
-
|
111
|
-
|
112
|
+
it "raises error on invalid options" do
|
113
|
+
expect{Decorator.decorate_collection([], foo: "bar")}.to raise_error ArgumentError, /Unknown key/
|
114
|
+
end
|
112
115
|
end
|
113
|
-
end
|
114
|
-
|
115
|
-
context "when a custom collection decorator exists" do
|
116
|
-
subject { ProductDecorator.decorate_collection(source) }
|
117
116
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
end
|
117
|
+
context "without a custom collection decorator" do
|
118
|
+
it "creates a CollectionDecorator using itself for each item" do
|
119
|
+
source = [Model.new]
|
122
120
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
end
|
121
|
+
CollectionDecorator.should_receive(:new).with(source, with: Decorator)
|
122
|
+
Decorator.decorate_collection(source)
|
123
|
+
end
|
127
124
|
|
128
|
-
|
129
|
-
|
125
|
+
it "passes options to the collection decorator" do
|
126
|
+
options = {with: OtherDecorator, context: {some: "context"}}
|
130
127
|
|
131
|
-
|
132
|
-
|
128
|
+
CollectionDecorator.should_receive(:new).with([], options)
|
129
|
+
Decorator.decorate_collection([], options)
|
130
|
+
end
|
133
131
|
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
describe "#helpers" do
|
138
|
-
it "returns a HelperProxy" do
|
139
|
-
subject.helpers.should be_a Draper::HelperProxy
|
140
|
-
end
|
141
132
|
|
142
|
-
|
143
|
-
|
144
|
-
|
133
|
+
context "with a custom collection decorator" do
|
134
|
+
it "creates a custom collection decorator using itself for each item" do
|
135
|
+
source = [Model.new]
|
145
136
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
subject.helpers.test_method.should == "test_method"
|
150
|
-
subject.helpers.test_method.should == "test_method"
|
151
|
-
end
|
152
|
-
end
|
137
|
+
ProductsDecorator.should_receive(:new).with(source, with: ProductDecorator)
|
138
|
+
ProductDecorator.decorate_collection(source)
|
139
|
+
end
|
153
140
|
|
154
|
-
|
155
|
-
|
141
|
+
it "passes options to the collection decorator" do
|
142
|
+
options = {with: OtherDecorator, context: {some: "context"}}
|
156
143
|
|
157
|
-
|
158
|
-
|
144
|
+
ProductsDecorator.should_receive(:new).with([], options)
|
145
|
+
ProductDecorator.decorate_collection([], options)
|
146
|
+
end
|
147
|
+
end
|
159
148
|
end
|
160
149
|
|
161
|
-
|
162
|
-
|
163
|
-
end
|
164
|
-
end
|
150
|
+
describe ".decorates" do
|
151
|
+
protect_class Decorator
|
165
152
|
|
166
|
-
|
167
|
-
|
168
|
-
subject.class.helpers.should be_a Draper::HelperProxy
|
169
|
-
end
|
153
|
+
it "sets .source_class with a symbol" do
|
154
|
+
Decorator.decorates :product
|
170
155
|
|
171
|
-
|
172
|
-
|
173
|
-
end
|
174
|
-
end
|
156
|
+
expect(Decorator.source_class).to be Product
|
157
|
+
end
|
175
158
|
|
176
|
-
|
177
|
-
|
159
|
+
it "sets .source_class with a string" do
|
160
|
+
Decorator.decorates "product"
|
178
161
|
|
179
|
-
|
180
|
-
it "sets .source_class" do
|
181
|
-
subject.decorates :product
|
182
|
-
subject.source_class.should be Product
|
162
|
+
expect(Decorator.source_class).to be Product
|
183
163
|
end
|
184
|
-
end
|
185
164
|
|
186
|
-
|
187
|
-
|
188
|
-
subject.decorates "product"
|
189
|
-
subject.source_class.should be Product
|
190
|
-
end
|
191
|
-
end
|
165
|
+
it "sets .source_class with a class" do
|
166
|
+
Decorator.decorates Product
|
192
167
|
|
193
|
-
|
194
|
-
it "sets .source_class" do
|
195
|
-
subject.decorates Product
|
196
|
-
subject.source_class.should be Product
|
168
|
+
expect(Decorator.source_class).to be Product
|
197
169
|
end
|
198
170
|
end
|
199
|
-
end
|
200
|
-
|
201
|
-
describe ".source_class" do
|
202
|
-
context "when not set by .decorates" do
|
203
|
-
context "for an anonymous decorator" do
|
204
|
-
subject { Class.new(Draper::Decorator) }
|
205
171
|
|
206
|
-
|
207
|
-
|
172
|
+
describe ".source_class" do
|
173
|
+
context "when not set by .decorates" do
|
174
|
+
it "raises an error for a so-named 'Decorator'" do
|
175
|
+
expect{Decorator.source_class}.to raise_error UninferrableSourceError
|
208
176
|
end
|
209
|
-
end
|
210
177
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
it "raises an UninferrableSourceError" do
|
215
|
-
expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
|
178
|
+
it "raises an error for anonymous decorators" do
|
179
|
+
expect{Class.new(Decorator).source_class}.to raise_error UninferrableSourceError
|
216
180
|
end
|
217
|
-
end
|
218
|
-
|
219
|
-
context "for a decorator called Decorator" do
|
220
|
-
subject { Draper::Decorator }
|
221
181
|
|
222
|
-
it "raises an
|
223
|
-
expect{
|
182
|
+
it "raises an error for a decorator without a model" do
|
183
|
+
expect{OtherDecorator.source_class}.to raise_error UninferrableSourceError
|
224
184
|
end
|
225
|
-
end
|
226
|
-
|
227
|
-
context "for a decorator with a name not ending in Decorator" do
|
228
|
-
subject { DecoratorWithApplicationHelper }
|
229
185
|
|
230
|
-
it "raises an
|
231
|
-
expect{
|
186
|
+
it "raises an error for other naming conventions" do
|
187
|
+
expect{ProductPresenter.source_class}.to raise_error UninferrableSourceError
|
232
188
|
end
|
233
|
-
end
|
234
189
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
it "infers the source" do
|
239
|
-
subject.source_class.should be Product
|
190
|
+
it "infers the source for '<Model>Decorator'" do
|
191
|
+
expect(ProductDecorator.source_class).to be Product
|
240
192
|
end
|
241
|
-
end
|
242
|
-
|
243
|
-
context "for a namespaced inferrable source" do
|
244
|
-
subject { Namespace::ProductDecorator }
|
245
193
|
|
246
|
-
it "infers
|
247
|
-
|
194
|
+
it "infers namespaced sources" do
|
195
|
+
expect(Namespaced::ProductDecorator.source_class).to be Namespaced::Product
|
248
196
|
end
|
249
197
|
end
|
250
198
|
end
|
251
|
-
end
|
252
199
|
|
253
|
-
|
254
|
-
|
200
|
+
describe ".source_class?" do
|
201
|
+
it "returns truthy when .source_class is set" do
|
202
|
+
Decorator.stub(:source_class).and_return(Model)
|
255
203
|
|
256
|
-
|
257
|
-
|
258
|
-
subject.source_class?.should be_true
|
259
|
-
end
|
204
|
+
expect(Decorator.source_class?).to be_true
|
205
|
+
end
|
260
206
|
|
261
|
-
|
262
|
-
|
263
|
-
subject.source_class?.should be_false
|
264
|
-
end
|
265
|
-
end
|
207
|
+
it "returns false when .source_class is not inferrable" do
|
208
|
+
Decorator.stub(:source_class).and_raise(UninferrableSourceError.new(Decorator))
|
266
209
|
|
267
|
-
|
268
|
-
|
269
|
-
|
210
|
+
expect(Decorator.source_class?).to be_false
|
211
|
+
end
|
212
|
+
end
|
270
213
|
|
271
|
-
describe "
|
272
|
-
|
214
|
+
describe ".decorates_association" do
|
215
|
+
protect_class Decorator
|
273
216
|
|
274
217
|
describe "options validation" do
|
275
|
-
|
276
|
-
before(:each) { Draper::DecoratedAssociation.stub(:new).and_return(decorated_association) }
|
218
|
+
before { DecoratedAssociation.stub(:new).and_return(->{}) }
|
277
219
|
|
278
220
|
it "does not raise error on valid options" do
|
279
|
-
|
221
|
+
valid_options = {with: Class, scope: :sorted, context: {}}
|
222
|
+
expect{Decorator.decorates_association(:children, valid_options)}.not_to raise_error
|
280
223
|
end
|
281
224
|
|
282
225
|
it "raises error on invalid options" do
|
283
|
-
expect
|
226
|
+
expect{Decorator.decorates_association(:children, foo: "bar")}.to raise_error ArgumentError, /Unknown key/
|
284
227
|
end
|
285
228
|
end
|
286
229
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
it "receives the Decorator" do
|
293
|
-
Draper::DecoratedAssociation.should_receive(:new).with(kind_of(decorator_class), :similar_products, {with: ProductDecorator}).and_return(decorated_association)
|
294
|
-
subject.similar_products
|
295
|
-
end
|
296
|
-
|
297
|
-
it "memoizes the DecoratedAssociation" do
|
298
|
-
Draper::DecoratedAssociation.should_receive(:new).once.and_return(decorated_association)
|
299
|
-
subject.similar_products
|
300
|
-
subject.similar_products
|
301
|
-
end
|
230
|
+
describe "defines an association method" do
|
231
|
+
it "creates a DecoratedAssociation" do
|
232
|
+
options = {with: Class.new, scope: :foo, context: {}}
|
233
|
+
Decorator.decorates_association :children, options
|
234
|
+
decorator = Decorator.new(Model.new)
|
302
235
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
subject.similar_products.should be :decorated
|
307
|
-
end
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
describe ".decorates_associations" do
|
312
|
-
subject { decorator_class }
|
236
|
+
DecoratedAssociation.should_receive(:new).with(decorator, :children, options).and_return(->{})
|
237
|
+
decorator.children
|
238
|
+
end
|
313
239
|
|
314
|
-
|
315
|
-
|
316
|
-
|
240
|
+
it "memoizes the DecoratedAssociation" do
|
241
|
+
Decorator.decorates_association :children
|
242
|
+
decorator = Decorator.new(Model.new)
|
317
243
|
|
318
|
-
|
319
|
-
|
244
|
+
DecoratedAssociation.should_receive(:new).once.and_return(->{})
|
245
|
+
decorator.children
|
246
|
+
decorator.children
|
247
|
+
end
|
320
248
|
|
321
|
-
|
322
|
-
|
323
|
-
|
249
|
+
it "calls the DecoratedAssociation" do
|
250
|
+
Decorator.decorates_association :children
|
251
|
+
decorator = Decorator.new(Model.new)
|
252
|
+
decorated_association = ->{}
|
253
|
+
DecoratedAssociation.stub(:new).and_return(decorated_association)
|
324
254
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
describe "#applied_decorators" do
|
330
|
-
it "returns a list of decorators applied to a model" do
|
331
|
-
decorator = ProductDecorator.new(SpecificProductDecorator.new(Product.new))
|
332
|
-
decorator.applied_decorators.should == [SpecificProductDecorator, ProductDecorator]
|
255
|
+
decorated_association.should_receive(:call).and_return(:decorated)
|
256
|
+
expect(decorator.children).to be :decorated
|
257
|
+
end
|
258
|
+
end
|
333
259
|
end
|
334
|
-
end
|
335
260
|
|
336
|
-
|
337
|
-
|
338
|
-
decorator = ProductDecorator.new(SpecificProductDecorator.new(Product.new))
|
339
|
-
decorator.should be_decorated_with ProductDecorator
|
340
|
-
decorator.should be_decorated_with SpecificProductDecorator
|
341
|
-
decorator.should_not be_decorated_with WidgetDecorator
|
342
|
-
end
|
343
|
-
end
|
261
|
+
describe ".decorates_associations" do
|
262
|
+
protect_class Decorator
|
344
263
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
264
|
+
it "decorates each of the associations" do
|
265
|
+
Decorator.should_receive(:decorates_association).with(:friends, {})
|
266
|
+
Decorator.should_receive(:decorates_association).with(:enemies, {})
|
267
|
+
Decorator.decorates_associations :friends, :enemies
|
268
|
+
end
|
350
269
|
|
351
|
-
|
352
|
-
|
353
|
-
subject.source.should be source
|
354
|
-
end
|
270
|
+
it "dispatches options" do
|
271
|
+
options = {with: Class.new, scope: :foo, context: {}}
|
355
272
|
|
356
|
-
|
357
|
-
|
273
|
+
Decorator.should_receive(:decorates_association).with(:friends, options)
|
274
|
+
Decorator.should_receive(:decorates_association).with(:enemies, options)
|
275
|
+
Decorator.decorates_associations :friends, :enemies, options
|
276
|
+
end
|
358
277
|
end
|
359
278
|
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
end
|
279
|
+
describe "#applied_decorators" do
|
280
|
+
it "returns a list of decorators applied to a model" do
|
281
|
+
decorator = ProductDecorator.new(OtherDecorator.new(Decorator.new(Model.new)))
|
364
282
|
|
365
|
-
|
366
|
-
|
367
|
-
subject.to_model.should be subject
|
283
|
+
expect(decorator.applied_decorators).to eq [Decorator, OtherDecorator, ProductDecorator]
|
284
|
+
end
|
368
285
|
end
|
369
|
-
end
|
370
286
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
subject.to_param.should == 42
|
375
|
-
end
|
376
|
-
end
|
287
|
+
describe "#decorated_with?" do
|
288
|
+
it "checks if a decorator has been applied to a model" do
|
289
|
+
decorator = ProductDecorator.new(Decorator.new(Model.new))
|
377
290
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
(subject == subject).should be_true
|
291
|
+
expect(decorator).to be_decorated_with Decorator
|
292
|
+
expect(decorator).to be_decorated_with ProductDecorator
|
293
|
+
expect(decorator).not_to be_decorated_with OtherDecorator
|
382
294
|
end
|
383
295
|
end
|
384
296
|
|
385
|
-
|
297
|
+
describe "#decorated?" do
|
386
298
|
it "returns true" do
|
387
|
-
|
299
|
+
decorator = Decorator.new(Model.new)
|
300
|
+
|
301
|
+
expect(decorator).to be_decorated
|
388
302
|
end
|
389
303
|
end
|
390
304
|
|
391
|
-
|
392
|
-
it "returns
|
393
|
-
|
305
|
+
describe "#source" do
|
306
|
+
it "returns the wrapped object" do
|
307
|
+
source = Model.new
|
308
|
+
decorator = Decorator.new(source)
|
309
|
+
|
310
|
+
expect(decorator.source).to be source
|
311
|
+
expect(decorator.model).to be source
|
312
|
+
expect(decorator.to_source).to be source
|
394
313
|
end
|
395
314
|
end
|
396
315
|
|
397
|
-
|
398
|
-
it "returns
|
399
|
-
|
316
|
+
describe "#to_model" do
|
317
|
+
it "returns the decorator" do
|
318
|
+
decorator = Decorator.new(Model.new)
|
319
|
+
|
320
|
+
expect(decorator.to_model).to be decorator
|
400
321
|
end
|
401
322
|
end
|
402
323
|
|
403
|
-
|
404
|
-
it "
|
405
|
-
|
324
|
+
describe "#to_param" do
|
325
|
+
it "delegates to the source" do
|
326
|
+
decorator = Decorator.new(double(to_param: :delegated))
|
327
|
+
|
328
|
+
expect(decorator.to_param).to be :delegated
|
406
329
|
end
|
407
330
|
end
|
408
|
-
end
|
409
331
|
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
332
|
+
describe "#present?" do
|
333
|
+
it "delegates to the source" do
|
334
|
+
decorator = Decorator.new(double(present?: :delegated))
|
335
|
+
|
336
|
+
expect(decorator.present?).to be :delegated
|
414
337
|
end
|
415
338
|
end
|
416
339
|
|
417
|
-
|
418
|
-
it "
|
419
|
-
|
340
|
+
describe "#to_partial_path" do
|
341
|
+
it "delegates to the source" do
|
342
|
+
decorator = Decorator.new(double(to_partial_path: :delegated))
|
343
|
+
|
344
|
+
expect(decorator.to_partial_path).to be :delegated
|
420
345
|
end
|
421
346
|
end
|
422
347
|
|
423
|
-
|
424
|
-
it "
|
425
|
-
|
348
|
+
describe ".model_name" do
|
349
|
+
it "delegates to the source class" do
|
350
|
+
Decorator.stub source_class: double(model_name: :delegated)
|
351
|
+
|
352
|
+
expect(Decorator.model_name).to be :delegated
|
426
353
|
end
|
427
354
|
end
|
428
355
|
|
429
|
-
|
430
|
-
it "
|
431
|
-
|
356
|
+
describe "#==" do
|
357
|
+
it "ensures the source has a decoration-aware #==" do
|
358
|
+
source = Object.new
|
359
|
+
decorator = Decorator.new(source)
|
360
|
+
|
361
|
+
expect(source).not_to be_a_kind_of Draper::Decoratable::Equality
|
362
|
+
decorator == :something
|
363
|
+
expect(source).to be_a_kind_of Draper::Decoratable::Equality
|
432
364
|
end
|
433
|
-
end
|
434
365
|
|
435
|
-
|
436
|
-
|
437
|
-
|
366
|
+
it "is true when source #== is true" do
|
367
|
+
source = Model.new
|
368
|
+
decorator = Decorator.new(source)
|
369
|
+
other = double(source: Model.new)
|
370
|
+
|
371
|
+
source.should_receive(:==).with(other).and_return(true)
|
372
|
+
expect(decorator == other).to be_true
|
438
373
|
end
|
439
|
-
end
|
440
|
-
end
|
441
374
|
|
442
|
-
|
443
|
-
|
375
|
+
it "is false when source #== is false" do
|
376
|
+
source = Model.new
|
377
|
+
decorator = Decorator.new(source)
|
378
|
+
other = double(source: Model.new)
|
444
379
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
end
|
380
|
+
source.should_receive(:==).with(other).and_return(false)
|
381
|
+
expect(decorator == other).to be_false
|
382
|
+
end
|
449
383
|
|
450
|
-
it "does not overwrite the :to option if supplied" do
|
451
|
-
Draper::Decorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :baz)
|
452
|
-
subject.delegate :foo, :bar, to: :baz
|
453
384
|
end
|
454
|
-
end
|
455
385
|
|
456
|
-
|
457
|
-
|
458
|
-
|
386
|
+
describe "#===" do
|
387
|
+
it "is true when #== is true" do
|
388
|
+
decorator = Decorator.new(Model.new)
|
389
|
+
decorator.stub(:==).with(:anything).and_return(true)
|
459
390
|
|
460
|
-
|
461
|
-
it "does not delegate methods that are defined on the decorator" do
|
462
|
-
subject.overridable.should be :overridden
|
391
|
+
expect(decorator === :anything).to be_true
|
463
392
|
end
|
464
393
|
|
465
|
-
it "
|
466
|
-
|
467
|
-
|
394
|
+
it "is false when #== is false" do
|
395
|
+
decorator = Decorator.new(Model.new)
|
396
|
+
decorator.stub(:==).with(:anything).and_return(false)
|
468
397
|
|
469
|
-
|
470
|
-
source.stub(:hello_world).and_return(:delegated)
|
471
|
-
subject.hello_world.should be :delegated
|
398
|
+
expect(decorator === :anything).to be_false
|
472
399
|
end
|
400
|
+
end
|
473
401
|
|
474
|
-
|
475
|
-
|
476
|
-
subject.hello_world
|
477
|
-
subject.methods.should include :hello_world
|
478
|
-
end
|
402
|
+
describe ".delegate" do
|
403
|
+
protect_class Decorator
|
479
404
|
|
480
|
-
it "
|
481
|
-
|
405
|
+
it "defaults the :to option to :source" do
|
406
|
+
Object.should_receive(:delegate).with(:foo, :bar, to: :source)
|
407
|
+
Decorator.delegate :foo, :bar
|
482
408
|
end
|
483
409
|
|
484
|
-
it "does not
|
485
|
-
|
410
|
+
it "does not overwrite the :to option if supplied" do
|
411
|
+
Object.should_receive(:delegate).with(:foo, :bar, to: :baz)
|
412
|
+
Decorator.delegate :foo, :bar, to: :baz
|
486
413
|
end
|
414
|
+
end
|
487
415
|
|
488
|
-
|
489
|
-
|
490
|
-
end
|
416
|
+
context "with .delegate_all" do
|
417
|
+
protect_class Decorator
|
491
418
|
|
492
|
-
|
493
|
-
expect{subject.private_title}.to raise_error NoMethodError
|
494
|
-
end
|
495
|
-
end
|
419
|
+
before { Decorator.delegate_all }
|
496
420
|
|
497
|
-
|
498
|
-
|
421
|
+
describe "#method_missing" do
|
422
|
+
it "delegates missing methods that exist on the source" do
|
423
|
+
decorator = Decorator.new(double(hello_world: :delegated))
|
499
424
|
|
500
|
-
|
501
|
-
it "raises a NoMethodError on missing methods" do
|
502
|
-
expect{subject.hello_world}.to raise_error NoMethodError
|
425
|
+
expect(decorator.hello_world).to be :delegated
|
503
426
|
end
|
504
|
-
end
|
505
427
|
|
506
|
-
|
507
|
-
|
508
|
-
before { subject.decorates source_class }
|
428
|
+
it "adds delegated methods to the decorator when they are used" do
|
429
|
+
decorator = Decorator.new(double(hello_world: :delegated))
|
509
430
|
|
510
|
-
|
511
|
-
|
431
|
+
expect(decorator.methods).not_to include :hello_world
|
432
|
+
decorator.hello_world
|
433
|
+
expect(decorator.methods).to include :hello_world
|
512
434
|
end
|
513
435
|
|
514
|
-
it "
|
515
|
-
|
516
|
-
|
436
|
+
it "passes blocks to delegated methods" do
|
437
|
+
source = Model.new
|
438
|
+
source.stub(:hello_world).and_return{|*args, &block| block.call}
|
439
|
+
decorator = Decorator.new(source)
|
440
|
+
|
441
|
+
expect(decorator.hello_world{:yielded}).to be :yielded
|
517
442
|
end
|
518
|
-
end
|
519
|
-
end
|
520
443
|
|
521
|
-
|
522
|
-
|
523
|
-
subject.should respond_to :awesome_title
|
524
|
-
end
|
444
|
+
it "does not confuse Kernel#Array" do
|
445
|
+
decorator = Decorator.new(Model.new)
|
525
446
|
|
526
|
-
|
527
|
-
|
528
|
-
|
447
|
+
expect(Array(decorator)).to be_an Array
|
448
|
+
end
|
449
|
+
|
450
|
+
it "delegates already-delegated methods" do
|
451
|
+
source = Class.new{ delegate :bar, to: :foo }.new
|
452
|
+
source.stub foo: double(bar: :delegated)
|
453
|
+
decorator = Decorator.new(source)
|
529
454
|
|
530
|
-
|
531
|
-
it "returns true for its own private methods" do
|
532
|
-
subject.respond_to?(:awesome_private_title, true).should be_true
|
455
|
+
expect(decorator.bar).to be :delegated
|
533
456
|
end
|
534
457
|
|
535
|
-
it "
|
536
|
-
|
458
|
+
it "does not delegate private methods" do
|
459
|
+
source = Class.new{ private; def hello_world; end }.new
|
460
|
+
decorator = Decorator.new(source)
|
461
|
+
|
462
|
+
expect{decorator.hello_world}.to raise_error NoMethodError
|
537
463
|
end
|
538
|
-
end
|
539
|
-
end
|
540
464
|
|
541
|
-
|
542
|
-
|
465
|
+
it "does not delegate methods that do not exist on the source" do
|
466
|
+
decorator = Decorator.new(Model.new)
|
467
|
+
|
468
|
+
expect(decorator.methods).not_to include :hello_world
|
469
|
+
expect{decorator.hello_world}.to raise_error NoMethodError
|
470
|
+
expect(decorator.methods).not_to include :hello_world
|
471
|
+
end
|
472
|
+
end
|
543
473
|
|
544
|
-
context "
|
545
|
-
|
546
|
-
|
474
|
+
context ".method_missing" do
|
475
|
+
context "without a source class" do
|
476
|
+
it "raises a NoMethodError on missing methods" do
|
477
|
+
expect{Decorator.hello_world}.to raise_error NoMethodError
|
478
|
+
end
|
547
479
|
end
|
548
480
|
|
549
|
-
|
550
|
-
|
481
|
+
context "with a source class" do
|
482
|
+
it "delegates methods that exist on the source class" do
|
483
|
+
source_class = Class.new
|
484
|
+
source_class.stub hello_world: :delegated
|
485
|
+
Decorator.stub source_class: source_class
|
486
|
+
|
487
|
+
expect(Decorator.hello_world).to be :delegated
|
488
|
+
end
|
489
|
+
|
490
|
+
it "does not delegate methods that do not exist on the source class" do
|
491
|
+
Decorator.stub source_class: Class.new
|
492
|
+
|
493
|
+
expect{Decorator.hello_world}.to raise_error NoMethodError
|
494
|
+
end
|
551
495
|
end
|
552
496
|
end
|
553
497
|
|
554
|
-
|
555
|
-
|
498
|
+
describe "#respond_to?" do
|
499
|
+
it "returns true for its own methods" do
|
500
|
+
Decorator.class_eval{def hello_world; end}
|
501
|
+
decorator = Decorator.new(Model.new)
|
502
|
+
|
503
|
+
expect(decorator).to respond_to :hello_world
|
504
|
+
end
|
505
|
+
|
506
|
+
it "returns true for the source's methods" do
|
507
|
+
decorator = Decorator.new(double(hello_world: :delegated))
|
556
508
|
|
557
|
-
|
558
|
-
subject.should respond_to :my_class_method
|
509
|
+
expect(decorator).to respond_to :hello_world
|
559
510
|
end
|
560
511
|
|
561
|
-
|
562
|
-
|
512
|
+
context "with include_private" do
|
513
|
+
it "returns true for its own private methods" do
|
514
|
+
Decorator.class_eval{private; def hello_world; end}
|
515
|
+
decorator = Decorator.new(Model.new)
|
516
|
+
|
517
|
+
expect(decorator.respond_to?(:hello_world, true)).to be_true
|
518
|
+
end
|
519
|
+
|
520
|
+
it "returns false for the source's private methods" do
|
521
|
+
source = Class.new{private; def hello_world; end}.new
|
522
|
+
decorator = Decorator.new(source)
|
523
|
+
|
524
|
+
expect(decorator.respond_to?(:hello_world, true)).to be_false
|
525
|
+
end
|
563
526
|
end
|
564
527
|
end
|
565
|
-
end
|
566
|
-
end
|
567
528
|
|
568
|
-
|
569
|
-
|
529
|
+
describe ".respond_to?" do
|
530
|
+
context "without a source class" do
|
531
|
+
it "returns true for its own class methods" do
|
532
|
+
Decorator.class_eval{def self.hello_world; end}
|
570
533
|
|
571
|
-
|
572
|
-
|
573
|
-
end
|
534
|
+
expect(Decorator).to respond_to :hello_world
|
535
|
+
end
|
574
536
|
|
575
|
-
|
576
|
-
|
577
|
-
|
537
|
+
it "returns false for other class methods" do
|
538
|
+
expect(Decorator).not_to respond_to :goodnight_moon
|
539
|
+
end
|
540
|
+
end
|
578
541
|
|
579
|
-
|
580
|
-
|
581
|
-
|
542
|
+
context "with a source class" do
|
543
|
+
it "returns true for its own class methods" do
|
544
|
+
Decorator.class_eval{def self.hello_world; end}
|
545
|
+
Decorator.stub source_class: Class.new
|
582
546
|
|
583
|
-
|
584
|
-
|
585
|
-
|
547
|
+
expect(Decorator).to respond_to :hello_world
|
548
|
+
end
|
549
|
+
|
550
|
+
it "returns true for the source's class methods" do
|
551
|
+
Decorator.stub source_class: double(hello_world: :delegated)
|
586
552
|
|
587
|
-
|
588
|
-
|
553
|
+
expect(Decorator).to respond_to :hello_world
|
554
|
+
end
|
555
|
+
end
|
556
|
+
end
|
589
557
|
end
|
590
|
-
end
|
591
558
|
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
end
|
559
|
+
describe "class spoofing" do
|
560
|
+
it "pretends to be a kind of the source class" do
|
561
|
+
decorator = Decorator.new(Model.new)
|
596
562
|
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
end
|
563
|
+
expect(decorator.kind_of?(Model)).to be_true
|
564
|
+
expect(decorator.is_a?(Model)).to be_true
|
565
|
+
end
|
601
566
|
|
602
|
-
|
603
|
-
|
604
|
-
end
|
567
|
+
it "is still a kind of its own class" do
|
568
|
+
decorator = Decorator.new(Model.new)
|
605
569
|
|
606
|
-
|
607
|
-
|
608
|
-
|
570
|
+
expect(decorator.kind_of?(Decorator)).to be_true
|
571
|
+
expect(decorator.is_a?(Decorator)).to be_true
|
572
|
+
end
|
573
|
+
|
574
|
+
it "pretends to be an instance of the source class" do
|
575
|
+
decorator = Decorator.new(Model.new)
|
576
|
+
|
577
|
+
expect(decorator.instance_of?(Model)).to be_true
|
578
|
+
end
|
579
|
+
|
580
|
+
it "is still an instance of its own class" do
|
581
|
+
decorator = Decorator.new(Model.new)
|
609
582
|
|
610
|
-
|
611
|
-
|
612
|
-
ProductDecorator.should be_a_kind_of Draper::Finders
|
583
|
+
expect(decorator.instance_of?(Decorator)).to be_true
|
584
|
+
end
|
613
585
|
end
|
614
|
-
end
|
615
586
|
|
616
|
-
|
617
|
-
|
587
|
+
describe ".decorates_finders" do
|
588
|
+
protect_class Decorator
|
618
589
|
|
619
|
-
|
620
|
-
|
590
|
+
it "extends the Finders module" do
|
591
|
+
expect(Decorator).not_to be_a_kind_of Finders
|
592
|
+
Decorator.decorates_finders
|
593
|
+
expect(Decorator).to be_a_kind_of Finders
|
594
|
+
end
|
621
595
|
end
|
622
|
-
end
|
623
596
|
|
597
|
+
end
|
624
598
|
end
|