draper 0.18.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/.gitignore +3 -0
- data/.travis.yml +11 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +164 -0
- data/CONTRIBUTING.md +15 -0
- data/Gemfile +34 -0
- data/LICENSE +7 -0
- data/README.md +416 -0
- data/Rakefile +53 -31
- data/draper.gemspec +11 -9
- data/lib/draper/automatic_delegation.rb +50 -0
- data/lib/draper/collection_decorator.rb +107 -0
- data/lib/draper/decoratable/equality.rb +14 -0
- data/lib/draper/decoratable.rb +79 -0
- data/lib/draper/decorated_association.rb +74 -0
- data/lib/draper/decorator.rb +235 -0
- data/lib/draper/delegation.rb +13 -0
- data/lib/draper/finders.rb +37 -0
- data/lib/draper/helper_proxy.rb +36 -0
- data/lib/draper/lazy_helpers.rb +10 -6
- data/lib/draper/railtie.rb +27 -21
- data/lib/draper/tasks/test.rake +22 -0
- data/lib/draper/test/devise_helper.rb +30 -0
- data/lib/draper/test/minitest_integration.rb +2 -3
- data/lib/draper/test/rspec_integration.rb +7 -13
- data/lib/draper/test_case.rb +53 -0
- data/lib/draper/version.rb +1 -1
- data/lib/draper/view_context/build_strategy.rb +48 -0
- data/lib/draper/view_context.rb +81 -24
- data/lib/draper/view_helpers.rb +37 -0
- data/lib/draper.rb +50 -8
- data/lib/generators/decorator/decorator_generator.rb +10 -2
- data/lib/generators/decorator/templates/decorator.rb +12 -27
- data/lib/generators/mini_test/decorator_generator.rb +20 -0
- data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
- data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
- data/lib/generators/test_unit/templates/decorator_test.rb +1 -1
- data/spec/draper/collection_decorator_spec.rb +259 -0
- data/spec/draper/decoratable/equality_spec.rb +10 -0
- data/spec/draper/decoratable_spec.rb +167 -0
- data/spec/draper/decorated_association_spec.rb +145 -0
- data/spec/draper/decorator_spec.rb +598 -0
- data/spec/draper/finders_spec.rb +166 -0
- data/spec/draper/helper_proxy_spec.rb +42 -0
- 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 +8 -0
- data/spec/dummy/.rspec +2 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/dummy/app/controllers/localized_urls.rb +5 -0
- data/spec/dummy/app/controllers/posts_controller.rb +18 -0
- data/spec/dummy/app/decorators/post_decorator.rb +56 -0
- data/spec/dummy/app/helpers/application_helper.rb +5 -0
- data/spec/dummy/app/mailers/application_mailer.rb +3 -0
- data/spec/dummy/app/mailers/post_mailer.rb +19 -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/post.rb +3 -0
- data/spec/dummy/app/models/user.rb +5 -0
- data/spec/dummy/app/views/layouts/application.html.erb +11 -0
- data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +1 -0
- data/spec/dummy/app/views/posts/_post.html.erb +34 -0
- data/spec/dummy/app/views/posts/show.html.erb +1 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/config/application.rb +70 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +33 -0
- data/spec/dummy/config/environments/production.rb +57 -0
- data/spec/dummy/config/environments/test.rb +31 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +8 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/mongoid.yml +80 -0
- data/spec/dummy/config/routes.rb +9 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
- data/spec/dummy/db/schema.rb +21 -0
- data/spec/dummy/db/seeds.rb +2 -0
- data/spec/dummy/fast_spec/post_decorator_spec.rb +38 -0
- data/spec/dummy/lib/tasks/test.rake +16 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- 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 +46 -0
- 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 +33 -0
- data/spec/dummy/spec/models/mongoid_post_spec.rb +10 -0
- data/spec/dummy/spec/models/post_spec.rb +14 -0
- data/spec/dummy/spec/spec_helper.rb +9 -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/test/decorators/minitest/spec_type_test.rb +52 -0
- 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 +48 -7
- data/spec/integration/integration_spec.rb +58 -0
- data/{performance → spec/performance}/decorators.rb +2 -4
- data/spec/spec_helper.rb +25 -43
- data/spec/support/dummy_app.rb +85 -0
- data/spec/support/matchers/have_text.rb +50 -0
- data/spec/support/shared_examples/decoratable_equality.rb +40 -0
- data/spec/support/shared_examples/view_helpers.rb +39 -0
- metadata +253 -87
- data/CHANGELOG.markdown +0 -57
- data/Readme.markdown +0 -387
- data/lib/draper/active_model_support.rb +0 -27
- data/lib/draper/base.rb +0 -312
- data/lib/draper/decorated_enumerable_proxy.rb +0 -90
- data/lib/draper/model_support.rb +0 -25
- data/lib/draper/rspec_integration.rb +0 -2
- data/lib/draper/system.rb +0 -18
- data/spec/draper/base_spec.rb +0 -873
- data/spec/draper/decorated_enumerable_proxy_spec.rb +0 -45
- data/spec/draper/model_support_spec.rb +0 -48
- data/spec/minitest-rails/spec_type_spec.rb +0 -63
- data/spec/support/samples/active_record.rb +0 -17
- data/spec/support/samples/decorator.rb +0 -5
- data/spec/support/samples/decorator_with_allows.rb +0 -3
- data/spec/support/samples/decorator_with_application_helper.rb +0 -25
- data/spec/support/samples/decorator_with_denies.rb +0 -3
- data/spec/support/samples/decorator_with_denies_all.rb +0 -3
- data/spec/support/samples/decorator_with_multiple_allows.rb +0 -4
- data/spec/support/samples/decorator_with_special_methods.rb +0 -13
- data/spec/support/samples/enumerable_proxy.rb +0 -3
- data/spec/support/samples/namespaced_product.rb +0 -51
- data/spec/support/samples/namespaced_product_decorator.rb +0 -7
- data/spec/support/samples/non_active_model_product.rb +0 -2
- data/spec/support/samples/product.rb +0 -80
- data/spec/support/samples/product_decorator.rb +0 -7
- data/spec/support/samples/products_decorator.rb +0 -10
- data/spec/support/samples/sequel_product.rb +0 -13
- data/spec/support/samples/some_thing.rb +0 -2
- data/spec/support/samples/some_thing_decorator.rb +0 -3
- data/spec/support/samples/specific_product_decorator.rb +0 -4
- data/spec/support/samples/widget.rb +0 -2
- data/spec/support/samples/widget_decorator.rb +0 -2
- /data/{performance → spec/performance}/active_record.rb +0 -0
- /data/{performance/bechmark.rb → spec/performance/benchmark.rb} +0 -0
- /data/{performance → spec/performance}/models.rb +0 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'support/shared_examples/view_helpers'
|
|
3
|
+
|
|
4
|
+
module Draper
|
|
5
|
+
describe Decorator do
|
|
6
|
+
it_behaves_like "view helpers", Decorator.new(Model.new)
|
|
7
|
+
|
|
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
|
|
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
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "sets the source" do
|
|
21
|
+
source = Model.new
|
|
22
|
+
decorator = Decorator.new(source)
|
|
23
|
+
|
|
24
|
+
expect(decorator.source).to be source
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "stores context" do
|
|
28
|
+
context = {some: "context"}
|
|
29
|
+
decorator = Decorator.new(Model.new, context: context)
|
|
30
|
+
|
|
31
|
+
expect(decorator.context).to be context
|
|
32
|
+
end
|
|
33
|
+
|
|
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
|
+
end
|
|
42
|
+
|
|
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
|
|
51
|
+
end
|
|
52
|
+
|
|
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
|
|
62
|
+
end
|
|
63
|
+
|
|
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
|
|
70
|
+
|
|
71
|
+
context "when it has been applied previously" do
|
|
72
|
+
it "warns" do
|
|
73
|
+
decorated = OtherDecorator.new(Decorator.new(Model.new))
|
|
74
|
+
|
|
75
|
+
warning_message = nil
|
|
76
|
+
Object.any_instance.stub(:warn) {|message| warning_message = message }
|
|
77
|
+
|
|
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
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "decorates anyway" do
|
|
84
|
+
decorated = OtherDecorator.new(Decorator.new(Model.new))
|
|
85
|
+
Object.any_instance.stub(:warn)
|
|
86
|
+
redecorated = Decorator.decorate(decorated)
|
|
87
|
+
|
|
88
|
+
expect(redecorated.source).to be decorated
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe "#context=" do
|
|
94
|
+
it "modifies the context" do
|
|
95
|
+
decorator = Decorator.new(Model.new, context: {some: "context"})
|
|
96
|
+
new_context = {other: "context"}
|
|
97
|
+
|
|
98
|
+
decorator.context = new_context
|
|
99
|
+
expect(decorator.context).to be new_context
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
describe ".decorate_collection" do
|
|
104
|
+
describe "options validation" do
|
|
105
|
+
before { CollectionDecorator.stub(:new) }
|
|
106
|
+
|
|
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
|
|
111
|
+
|
|
112
|
+
it "raises error on invalid options" do
|
|
113
|
+
expect{Decorator.decorate_collection([], foo: "bar")}.to raise_error ArgumentError, /Unknown key/
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
context "without a custom collection decorator" do
|
|
118
|
+
it "creates a CollectionDecorator using itself for each item" do
|
|
119
|
+
source = [Model.new]
|
|
120
|
+
|
|
121
|
+
CollectionDecorator.should_receive(:new).with(source, with: Decorator)
|
|
122
|
+
Decorator.decorate_collection(source)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "passes options to the collection decorator" do
|
|
126
|
+
options = {with: OtherDecorator, context: {some: "context"}}
|
|
127
|
+
|
|
128
|
+
CollectionDecorator.should_receive(:new).with([], options)
|
|
129
|
+
Decorator.decorate_collection([], options)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
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]
|
|
136
|
+
|
|
137
|
+
ProductsDecorator.should_receive(:new).with(source, with: ProductDecorator)
|
|
138
|
+
ProductDecorator.decorate_collection(source)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "passes options to the collection decorator" do
|
|
142
|
+
options = {with: OtherDecorator, context: {some: "context"}}
|
|
143
|
+
|
|
144
|
+
ProductsDecorator.should_receive(:new).with([], options)
|
|
145
|
+
ProductDecorator.decorate_collection([], options)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
describe ".decorates" do
|
|
151
|
+
protect_class Decorator
|
|
152
|
+
|
|
153
|
+
it "sets .source_class with a symbol" do
|
|
154
|
+
Decorator.decorates :product
|
|
155
|
+
|
|
156
|
+
expect(Decorator.source_class).to be Product
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "sets .source_class with a string" do
|
|
160
|
+
Decorator.decorates "product"
|
|
161
|
+
|
|
162
|
+
expect(Decorator.source_class).to be Product
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it "sets .source_class with a class" do
|
|
166
|
+
Decorator.decorates Product
|
|
167
|
+
|
|
168
|
+
expect(Decorator.source_class).to be Product
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
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
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "raises an error for anonymous decorators" do
|
|
179
|
+
expect{Class.new(Decorator).source_class}.to raise_error UninferrableSourceError
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it "raises an error for a decorator without a model" do
|
|
183
|
+
expect{OtherDecorator.source_class}.to raise_error UninferrableSourceError
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "raises an error for other naming conventions" do
|
|
187
|
+
expect{ProductPresenter.source_class}.to raise_error UninferrableSourceError
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it "infers the source for '<Model>Decorator'" do
|
|
191
|
+
expect(ProductDecorator.source_class).to be Product
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it "infers namespaced sources" do
|
|
195
|
+
expect(Namespaced::ProductDecorator.source_class).to be Namespaced::Product
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
describe ".source_class?" do
|
|
201
|
+
it "returns truthy when .source_class is set" do
|
|
202
|
+
Decorator.stub(:source_class).and_return(Model)
|
|
203
|
+
|
|
204
|
+
expect(Decorator.source_class?).to be_true
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it "returns false when .source_class is not inferrable" do
|
|
208
|
+
Decorator.stub(:source_class).and_raise(UninferrableSourceError.new(Decorator))
|
|
209
|
+
|
|
210
|
+
expect(Decorator.source_class?).to be_false
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
describe ".decorates_association" do
|
|
215
|
+
protect_class Decorator
|
|
216
|
+
|
|
217
|
+
describe "options validation" do
|
|
218
|
+
before { DecoratedAssociation.stub(:new).and_return(->{}) }
|
|
219
|
+
|
|
220
|
+
it "does not raise error on valid options" do
|
|
221
|
+
valid_options = {with: Class, scope: :sorted, context: {}}
|
|
222
|
+
expect{Decorator.decorates_association(:children, valid_options)}.not_to raise_error
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it "raises error on invalid options" do
|
|
226
|
+
expect{Decorator.decorates_association(:children, foo: "bar")}.to raise_error ArgumentError, /Unknown key/
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
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)
|
|
235
|
+
|
|
236
|
+
DecoratedAssociation.should_receive(:new).with(decorator, :children, options).and_return(->{})
|
|
237
|
+
decorator.children
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it "memoizes the DecoratedAssociation" do
|
|
241
|
+
Decorator.decorates_association :children
|
|
242
|
+
decorator = Decorator.new(Model.new)
|
|
243
|
+
|
|
244
|
+
DecoratedAssociation.should_receive(:new).once.and_return(->{})
|
|
245
|
+
decorator.children
|
|
246
|
+
decorator.children
|
|
247
|
+
end
|
|
248
|
+
|
|
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)
|
|
254
|
+
|
|
255
|
+
decorated_association.should_receive(:call).and_return(:decorated)
|
|
256
|
+
expect(decorator.children).to be :decorated
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
describe ".decorates_associations" do
|
|
262
|
+
protect_class Decorator
|
|
263
|
+
|
|
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
|
|
269
|
+
|
|
270
|
+
it "dispatches options" do
|
|
271
|
+
options = {with: Class.new, scope: :foo, context: {}}
|
|
272
|
+
|
|
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
|
|
277
|
+
end
|
|
278
|
+
|
|
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)))
|
|
282
|
+
|
|
283
|
+
expect(decorator.applied_decorators).to eq [Decorator, OtherDecorator, ProductDecorator]
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
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))
|
|
290
|
+
|
|
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
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
describe "#decorated?" do
|
|
298
|
+
it "returns true" do
|
|
299
|
+
decorator = Decorator.new(Model.new)
|
|
300
|
+
|
|
301
|
+
expect(decorator).to be_decorated
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
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
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
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
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
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
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
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
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
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
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
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
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
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
|
|
364
|
+
end
|
|
365
|
+
|
|
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
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
it "is false when source #== is false" do
|
|
376
|
+
source = Model.new
|
|
377
|
+
decorator = Decorator.new(source)
|
|
378
|
+
other = double(source: Model.new)
|
|
379
|
+
|
|
380
|
+
source.should_receive(:==).with(other).and_return(false)
|
|
381
|
+
expect(decorator == other).to be_false
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
end
|
|
385
|
+
|
|
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)
|
|
390
|
+
|
|
391
|
+
expect(decorator === :anything).to be_true
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
it "is false when #== is false" do
|
|
395
|
+
decorator = Decorator.new(Model.new)
|
|
396
|
+
decorator.stub(:==).with(:anything).and_return(false)
|
|
397
|
+
|
|
398
|
+
expect(decorator === :anything).to be_false
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
describe ".delegate" do
|
|
403
|
+
protect_class Decorator
|
|
404
|
+
|
|
405
|
+
it "defaults the :to option to :source" do
|
|
406
|
+
Object.should_receive(:delegate).with(:foo, :bar, to: :source)
|
|
407
|
+
Decorator.delegate :foo, :bar
|
|
408
|
+
end
|
|
409
|
+
|
|
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
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
context "with .delegate_all" do
|
|
417
|
+
protect_class Decorator
|
|
418
|
+
|
|
419
|
+
before { Decorator.delegate_all }
|
|
420
|
+
|
|
421
|
+
describe "#method_missing" do
|
|
422
|
+
it "delegates missing methods that exist on the source" do
|
|
423
|
+
decorator = Decorator.new(double(hello_world: :delegated))
|
|
424
|
+
|
|
425
|
+
expect(decorator.hello_world).to be :delegated
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
it "adds delegated methods to the decorator when they are used" do
|
|
429
|
+
decorator = Decorator.new(double(hello_world: :delegated))
|
|
430
|
+
|
|
431
|
+
expect(decorator.methods).not_to include :hello_world
|
|
432
|
+
decorator.hello_world
|
|
433
|
+
expect(decorator.methods).to include :hello_world
|
|
434
|
+
end
|
|
435
|
+
|
|
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
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
it "does not confuse Kernel#Array" do
|
|
445
|
+
decorator = Decorator.new(Model.new)
|
|
446
|
+
|
|
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)
|
|
454
|
+
|
|
455
|
+
expect(decorator.bar).to be :delegated
|
|
456
|
+
end
|
|
457
|
+
|
|
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
|
|
463
|
+
end
|
|
464
|
+
|
|
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
|
|
473
|
+
|
|
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
|
|
479
|
+
end
|
|
480
|
+
|
|
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
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
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))
|
|
508
|
+
|
|
509
|
+
expect(decorator).to respond_to :hello_world
|
|
510
|
+
end
|
|
511
|
+
|
|
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
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
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}
|
|
533
|
+
|
|
534
|
+
expect(Decorator).to respond_to :hello_world
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
it "returns false for other class methods" do
|
|
538
|
+
expect(Decorator).not_to respond_to :goodnight_moon
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
|
|
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
|
|
546
|
+
|
|
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)
|
|
552
|
+
|
|
553
|
+
expect(Decorator).to respond_to :hello_world
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
describe "class spoofing" do
|
|
560
|
+
it "pretends to be a kind of the source class" do
|
|
561
|
+
decorator = Decorator.new(Model.new)
|
|
562
|
+
|
|
563
|
+
expect(decorator.kind_of?(Model)).to be_true
|
|
564
|
+
expect(decorator.is_a?(Model)).to be_true
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
it "is still a kind of its own class" do
|
|
568
|
+
decorator = Decorator.new(Model.new)
|
|
569
|
+
|
|
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)
|
|
582
|
+
|
|
583
|
+
expect(decorator.instance_of?(Decorator)).to be_true
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
describe ".decorates_finders" do
|
|
588
|
+
protect_class Decorator
|
|
589
|
+
|
|
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
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
end
|
|
598
|
+
end
|