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.
- data/.gitignore +3 -0
- data/.travis.yml +11 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +161 -0
- data/CONTRIBUTING.md +11 -0
- data/Gemfile +20 -0
- data/LICENSE +7 -0
- data/README.md +324 -0
- data/Rakefile +52 -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.rb +83 -0
- data/lib/draper/decorated_association.rb +74 -0
- data/lib/draper/decorator.rb +238 -0
- data/lib/draper/delegation.rb +13 -0
- data/lib/draper/finders.rb +37 -0
- data/lib/draper/helper_proxy.rb +17 -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 +34 -0
- data/lib/draper/test/minitest_integration.rb +2 -3
- data/lib/draper/test/rspec_integration.rb +3 -13
- data/lib/draper/test_case.rb +33 -0
- data/lib/draper/version.rb +1 -1
- data/lib/draper/view_context.rb +12 -17
- data/lib/draper/view_helpers.rb +37 -0
- data/lib/draper.rb +47 -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 +286 -0
- data/spec/draper/decoratable_spec.rb +192 -0
- data/spec/draper/decorated_association_spec.rb +142 -0
- data/spec/draper/decorator_spec.rb +624 -0
- data/spec/draper/finders_spec.rb +132 -0
- data/spec/draper/helper_proxy_spec.rb +12 -0
- data/spec/draper/view_helpers_spec.rb +41 -0
- data/spec/dummy/.rspec +2 -0
- data/spec/dummy/README.rdoc +261 -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 +11 -0
- data/spec/dummy/app/decorators/post_decorator.rb +32 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/application_mailer.rb +3 -0
- data/spec/dummy/app/mailers/post_mailer.rb +9 -0
- data/spec/dummy/app/models/post.rb +3 -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 +19 -0
- data/spec/dummy/app/views/posts/show.html.erb +1 -0
- data/spec/dummy/config/application.rb +64 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +34 -0
- data/spec/dummy/config/environments/production.rb +55 -0
- data/spec/dummy/config/environments/test.rb +32 -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 +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +7 -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/lib/tasks/test.rake +10 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/mini_test/mini_test_integration_test.rb +46 -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/post_decorator_spec.rb +26 -0
- data/spec/dummy/spec/decorators/rspec_integration_spec.rb +19 -0
- data/spec/dummy/spec/mailers/post_mailer_spec.rb +29 -0
- data/spec/dummy/spec/models/post_spec.rb +14 -0
- data/spec/dummy/spec/spec_helper.rb +8 -0
- data/spec/generators/decorator/decorator_generator_spec.rb +47 -7
- data/spec/integration/integration_spec.rb +33 -0
- data/{performance → spec/performance}/decorators.rb +2 -4
- data/spec/spec_helper.rb +20 -45
- data/spec/support/action_controller.rb +12 -0
- data/spec/support/active_model.rb +7 -0
- data/spec/support/active_record.rb +9 -0
- data/spec/support/{samples → decorators}/decorator_with_application_helper.rb +1 -1
- data/spec/support/decorators/namespaced_product_decorator.rb +5 -0
- data/spec/support/decorators/non_active_model_product_decorator.rb +2 -0
- data/spec/support/decorators/product_decorator.rb +23 -0
- data/spec/support/{samples → decorators}/products_decorator.rb +1 -1
- data/spec/support/decorators/some_thing_decorator.rb +2 -0
- data/spec/support/{samples → decorators}/specific_product_decorator.rb +0 -2
- data/spec/support/dummy_app.rb +85 -0
- data/spec/support/matchers/have_text.rb +50 -0
- data/spec/support/{samples → models}/namespaced_product.rb +1 -3
- data/spec/support/{samples → models}/non_active_model_product.rb +1 -0
- data/spec/support/{samples → models}/product.rb +17 -2
- data/spec/support/models/some_thing.rb +5 -0
- data/spec/support/models/uninferrable_decorator_model.rb +3 -0
- metadata +241 -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_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_decorator.rb +0 -7
- data/spec/support/samples/product_decorator.rb +0 -7
- 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/{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
- /data/spec/support/{samples → decorators}/widget_decorator.rb +0 -0
- /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
|