draper 0.18.0 → 1.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.markdown +8 -0
- data/Gemfile +4 -0
- data/Rakefile +50 -31
- data/Readme.markdown +42 -48
- data/draper.gemspec +2 -1
- data/lib/draper.rb +42 -7
- data/lib/draper/collection_decorator.rb +88 -0
- data/lib/draper/decoratable.rb +44 -0
- data/lib/draper/decorated_association.rb +55 -0
- data/lib/draper/decorator.rb +208 -0
- data/lib/draper/finders.rb +44 -0
- data/lib/draper/helper_proxy.rb +16 -0
- data/lib/draper/railtie.rb +17 -21
- data/lib/draper/security.rb +48 -0
- data/lib/draper/tasks/tu.rake +5 -0
- data/lib/draper/test/minitest_integration.rb +1 -1
- data/lib/draper/test/test_unit_integration.rb +9 -0
- data/lib/draper/version.rb +1 -1
- data/lib/draper/view_context.rb +6 -13
- data/lib/draper/view_helpers.rb +36 -0
- data/lib/generators/decorator/decorator_generator.rb +1 -1
- data/lib/generators/decorator/templates/decorator.rb +0 -1
- data/spec/draper/collection_decorator_spec.rb +240 -0
- data/spec/draper/decoratable_spec.rb +164 -0
- data/spec/draper/decorated_association_spec.rb +130 -0
- data/spec/draper/decorator_spec.rb +497 -0
- data/spec/draper/finders_spec.rb +156 -0
- data/spec/draper/helper_proxy_spec.rb +12 -0
- data/spec/draper/security_spec.rb +158 -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 +17 -0
- data/spec/dummy/app/decorators/post_decorator.rb +25 -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.ru +4 -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/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +7 -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/spec.rake +5 -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 +23 -0
- data/spec/dummy/spec/mailers/post_mailer_spec.rb +29 -0
- data/spec/dummy/spec/spec_helper.rb +9 -0
- data/spec/generators/decorator/decorator_generator_spec.rb +3 -4
- data/spec/integration/integration_spec.rb +33 -0
- data/{performance → spec/performance}/active_record.rb +0 -0
- data/{performance/bechmark.rb → spec/performance/benchmark.rb} +0 -0
- data/{performance → spec/performance}/decorators.rb +2 -4
- data/{performance → spec/performance}/models.rb +0 -0
- data/spec/spec_helper.rb +20 -41
- data/spec/support/action_controller.rb +12 -0
- data/spec/support/active_model.rb +7 -0
- data/spec/support/{samples/active_record.rb → active_record.rb} +5 -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 +19 -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/{samples → decorators}/widget_decorator.rb +0 -0
- data/spec/support/dummy_app.rb +84 -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 +13 -2
- data/spec/support/models/some_thing.rb +5 -0
- data/spec/support/models/uninferrable_decorator_model.rb +3 -0
- data/spec/support/{samples → models}/widget.rb +0 -0
- metadata +185 -68
- 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/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/lib/draper/system.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
module Draper
|
2
|
-
class System
|
3
|
-
def self.setup_action_controller(component)
|
4
|
-
component.class_eval do
|
5
|
-
include Draper::ViewContext
|
6
|
-
extend Draper::HelperSupport
|
7
|
-
before_filter lambda {|controller|
|
8
|
-
Draper::ViewContext.current = nil
|
9
|
-
Draper::ViewContext.current_controller = controller
|
10
|
-
}
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.setup_action_mailer(component)
|
15
|
-
include Draper::ViewContext
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/spec/draper/base_spec.rb
DELETED
@@ -1,873 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Draper::Base do
|
4
|
-
before(:each){ ApplicationController.new.view_context }
|
5
|
-
subject{ Decorator.new(source) }
|
6
|
-
let(:source){ Product.new }
|
7
|
-
let(:non_active_model_source){ NonActiveModelProduct.new }
|
8
|
-
|
9
|
-
context("proxying class methods") do
|
10
|
-
it "pass missing class method calls on to the wrapped class" do
|
11
|
-
subject.class.sample_class_method.should == "sample class method"
|
12
|
-
end
|
13
|
-
|
14
|
-
it "respond_to a wrapped class method" do
|
15
|
-
subject.class.should respond_to(:sample_class_method)
|
16
|
-
end
|
17
|
-
|
18
|
-
it "still respond_to its own class methods" do
|
19
|
-
subject.class.should respond_to(:own_class_method)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
context(".helpers") do
|
24
|
-
it "have a valid view_context" do
|
25
|
-
subject.helpers.should be
|
26
|
-
end
|
27
|
-
|
28
|
-
it "is aliased to .h" do
|
29
|
-
subject.h.should == subject.helpers
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
context("#helpers") do
|
34
|
-
it "have a valid view_context" do
|
35
|
-
Decorator.helpers.should be
|
36
|
-
end
|
37
|
-
|
38
|
-
it "is aliased to #h" do
|
39
|
-
Decorator.h.should == Decorator.helpers
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
context(".decorates") do
|
44
|
-
it "sets the model class for the decorator" do
|
45
|
-
ProductDecorator.new(source).model_class.should == Product
|
46
|
-
end
|
47
|
-
|
48
|
-
it "returns decorator if it's decorated model already" do
|
49
|
-
product_decorator = ProductDecorator.new(source)
|
50
|
-
ProductDecorator.new(product_decorator).model.should be_instance_of Product
|
51
|
-
end
|
52
|
-
|
53
|
-
it "handle plural-like words properly'" do
|
54
|
-
class Business; end
|
55
|
-
expect do
|
56
|
-
class BusinessDecorator < Draper::Base
|
57
|
-
decorates:business
|
58
|
-
end
|
59
|
-
BusinessDecorator.model_class.should == Business
|
60
|
-
end.to_not raise_error
|
61
|
-
end
|
62
|
-
|
63
|
-
context("accepts ActiveRecord like :class_name option too") do
|
64
|
-
it "accepts constants for :class" do
|
65
|
-
expect do
|
66
|
-
class CustomDecorator < Draper::Base
|
67
|
-
decorates :product, :class => Product
|
68
|
-
end
|
69
|
-
CustomDecorator.model_class.should == Product
|
70
|
-
end.to_not raise_error
|
71
|
-
end
|
72
|
-
|
73
|
-
it "accepts constants for :class_name" do
|
74
|
-
expect do
|
75
|
-
class CustomDecorator < Draper::Base
|
76
|
-
decorates :product, :class_name => Product
|
77
|
-
end
|
78
|
-
CustomDecorator.model_class.should == Product
|
79
|
-
end.to_not raise_error
|
80
|
-
end
|
81
|
-
|
82
|
-
it "accepts strings for :class" do
|
83
|
-
expect do
|
84
|
-
class CustomDecorator < Draper::Base
|
85
|
-
decorates :product, :class => 'Product'
|
86
|
-
end
|
87
|
-
CustomDecorator.model_class.should == Product
|
88
|
-
end.to_not raise_error
|
89
|
-
end
|
90
|
-
|
91
|
-
it "accepts strings for :class_name" do
|
92
|
-
expect do
|
93
|
-
class CustomDecorator < Draper::Base
|
94
|
-
decorates :product, :class_name => 'Product'
|
95
|
-
end
|
96
|
-
CustomDecorator.model_class.should == Product
|
97
|
-
end.to_not raise_error
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
it "creates a named accessor for the wrapped model" do
|
102
|
-
pd = ProductDecorator.new(source)
|
103
|
-
pd.send(:product).should == source
|
104
|
-
end
|
105
|
-
|
106
|
-
context("namespaced model supporting") do
|
107
|
-
let(:source){ Namespace::Product.new }
|
108
|
-
|
109
|
-
it "sets the model class for the decorator" do
|
110
|
-
decorator = Namespace::ProductDecorator.new(source)
|
111
|
-
decorator.model_class.should == Namespace::Product
|
112
|
-
end
|
113
|
-
|
114
|
-
it "creates a named accessor for the wrapped model" do
|
115
|
-
pd = Namespace::ProductDecorator.new(source)
|
116
|
-
pd.send(:product).should == source
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
context(".decorates_association") do
|
122
|
-
context "for ActiveModel collection associations" do
|
123
|
-
before(:each){ subject.class_eval{ decorates_association :similar_products } }
|
124
|
-
it "causes the association's method to return a collection of wrapped objects" do
|
125
|
-
subject.similar_products.each{ |decorated| decorated.should be_instance_of(ProductDecorator) }
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
context "for Plain Old Ruby Object collection associations" do
|
130
|
-
before(:each){ subject.class_eval{ decorates_association :poro_similar_products } }
|
131
|
-
it "causes the association's method to return a collection of wrapped objects" do
|
132
|
-
subject.poro_similar_products.each{ |decorated| decorated.should be_instance_of(ProductDecorator) }
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
context "for an ActiveModel singular association" do
|
137
|
-
before(:each){ subject.class_eval{ decorates_association :previous_version } }
|
138
|
-
it "causes the association's method to return a single wrapped object if the association is singular" do
|
139
|
-
subject.previous_version.should be_instance_of(ProductDecorator)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
context "for a Plain Old Ruby Object singular association" do
|
144
|
-
before(:each){ subject.class_eval{ decorates_association :poro_previous_version } }
|
145
|
-
it "causes the association's method to return a single wrapped object" do
|
146
|
-
subject.poro_previous_version.should be_instance_of(ProductDecorator)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
context "with a specific decorator specified" do
|
151
|
-
before(:each){ subject.class_eval{ decorates_association :previous_version, :with => SpecificProductDecorator } }
|
152
|
-
it "causes the association to be decorated with the specified association" do
|
153
|
-
subject.previous_version.should be_instance_of(SpecificProductDecorator)
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
context "with a scope specified" do
|
158
|
-
before(:each){ subject.class_eval{ decorates_association :thing, :scope => :foo } }
|
159
|
-
it "applies the scope before decoration" do
|
160
|
-
SomeThing.any_instance.should_receive(:foo).and_return(:bar)
|
161
|
-
subject.thing.model.should == :bar
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
context "for a polymorphic association" do
|
166
|
-
before(:each){ subject.class_eval{ decorates_association :thing, :polymorphic => true } }
|
167
|
-
it "causes the association to be decorated with the right decorator" do
|
168
|
-
subject.thing.should be_instance_of(SomeThingDecorator)
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
context "when the association is nil" do
|
173
|
-
before(:each) do
|
174
|
-
subject.class_eval{ decorates_association :previous_version }
|
175
|
-
source.stub(:previous_version){ nil }
|
176
|
-
end
|
177
|
-
it "causes the association's method to return nil" do
|
178
|
-
subject.previous_version.should be_nil
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
context "#find" do
|
183
|
-
before(:each){ subject.class_eval{ decorates_association :similar_products } }
|
184
|
-
context "with a block" do
|
185
|
-
it "delegates to #each" do
|
186
|
-
subject.similar_products.source.should_receive :find
|
187
|
-
subject.similar_products.find {|p| p.title == "title" }
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
context "without a block" do
|
192
|
-
it "calls a finder method" do
|
193
|
-
subject.similar_products.source.should_not_receive :find
|
194
|
-
subject.similar_products.find 1
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
context('.decorates_associations') do
|
201
|
-
subject { Decorator }
|
202
|
-
it "decorates each of the associations" do
|
203
|
-
subject.should_receive(:decorates_association).with(:similar_products, {})
|
204
|
-
subject.should_receive(:decorates_association).with(:previous_version, {})
|
205
|
-
|
206
|
-
subject.decorates_associations :similar_products, :previous_version
|
207
|
-
end
|
208
|
-
|
209
|
-
it "dispatches options" do
|
210
|
-
subject.should_receive(:decorates_association).with(:similar_products, :with => ProductDecorator)
|
211
|
-
subject.should_receive(:decorates_association).with(:previous_version, :with => ProductDecorator)
|
212
|
-
|
213
|
-
subject.decorates_associations :similar_products, :previous_version, :with => ProductDecorator
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
context(".wrapped_object") do
|
218
|
-
it "return the wrapped object" do
|
219
|
-
subject.wrapped_object.should == source
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
context(".source / .to_source") do
|
224
|
-
it "return the wrapped object" do
|
225
|
-
subject.to_source == source
|
226
|
-
subject.source == source
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
describe "method selection" do
|
231
|
-
it "echos the methods of the wrapped class except default exclusions" do
|
232
|
-
source.methods.each do |method|
|
233
|
-
unless Draper::Base::DEFAULT_DENIED.include?(method)
|
234
|
-
subject.should respond_to(method.to_sym)
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
it "not override a defined method with a source method" do
|
240
|
-
DecoratorWithApplicationHelper.new(source).length.should == "overridden"
|
241
|
-
end
|
242
|
-
|
243
|
-
it "not copy the .class, .inspect, or other existing methods" do
|
244
|
-
source.class.should_not == subject.class
|
245
|
-
source.inspect.should_not == subject.inspect
|
246
|
-
source.to_s.should_not == subject.to_s
|
247
|
-
end
|
248
|
-
|
249
|
-
context "when an ActiveModel descendant" do
|
250
|
-
it "always proxy to_param if it is not defined on the decorator itself" do
|
251
|
-
source.stub(:to_param).and_return(1)
|
252
|
-
Draper::Base.new(source).to_param.should == 1
|
253
|
-
end
|
254
|
-
|
255
|
-
it "always proxy id if it is not defined on the decorator itself" do
|
256
|
-
source.stub(:id).and_return(123456789)
|
257
|
-
Draper::Base.new(source).id.should == 123456789
|
258
|
-
end
|
259
|
-
|
260
|
-
it "always proxy errors if it is not defined on the decorator itself" do
|
261
|
-
Draper::Base.new(source).errors.should be_an_instance_of ActiveModel::Errors
|
262
|
-
end
|
263
|
-
|
264
|
-
it "never proxy to_param if it is defined on the decorator itself" do
|
265
|
-
source.stub(:to_param).and_return(1)
|
266
|
-
DecoratorWithSpecialMethods.new(source).to_param.should == "foo"
|
267
|
-
end
|
268
|
-
|
269
|
-
it "never proxy id if it is defined on the decorator itself" do
|
270
|
-
source.stub(:id).and_return(123456789)
|
271
|
-
DecoratorWithSpecialMethods.new(source).id.should == 1337
|
272
|
-
end
|
273
|
-
|
274
|
-
it "never proxy errors if it is defined on the decorator itself" do
|
275
|
-
DecoratorWithSpecialMethods.new(source).errors.should be_an_instance_of Array
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
context 'the decorated model' do
|
281
|
-
it 'receives the mixin' do
|
282
|
-
source.class.ancestors.include?(Draper::ModelSupport)
|
283
|
-
end
|
284
|
-
|
285
|
-
it 'includes ActiveModel support' do
|
286
|
-
source.class.ancestors.include?(Draper::ActiveModelSupport)
|
287
|
-
end
|
288
|
-
end
|
289
|
-
|
290
|
-
it "wrap source methods so they still accept blocks" do
|
291
|
-
subject.block{"marker"}.should == "marker"
|
292
|
-
end
|
293
|
-
|
294
|
-
context ".find" do
|
295
|
-
it "lookup the associated model when passed an integer" do
|
296
|
-
pd = ProductDecorator.find(1)
|
297
|
-
pd.should be_instance_of(ProductDecorator)
|
298
|
-
pd.model.should be_instance_of(Product)
|
299
|
-
end
|
300
|
-
|
301
|
-
it "lookup the associated model when passed a string" do
|
302
|
-
pd = ProductDecorator.find("1")
|
303
|
-
pd.should be_instance_of(ProductDecorator)
|
304
|
-
pd.model.should be_instance_of(Product)
|
305
|
-
end
|
306
|
-
|
307
|
-
it "accept and store a context" do
|
308
|
-
pd = ProductDecorator.find(1, :context => :admin)
|
309
|
-
pd.context.should == :admin
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
context ".find_by_(x)" do
|
314
|
-
it "runs the similarly named finder" do
|
315
|
-
Product.should_receive(:find_by_name)
|
316
|
-
ProductDecorator.find_by_name("apples")
|
317
|
-
end
|
318
|
-
|
319
|
-
it "returns a decorated result" do
|
320
|
-
ProductDecorator.find_by_name("apples").should be_kind_of(ProductDecorator)
|
321
|
-
end
|
322
|
-
|
323
|
-
it "runs complex finders" do
|
324
|
-
Product.should_receive(:find_by_name_and_size)
|
325
|
-
ProductDecorator.find_by_name_and_size("apples", "large")
|
326
|
-
end
|
327
|
-
|
328
|
-
it "runs find_all_by_(x) finders" do
|
329
|
-
Product.should_receive(:find_all_by_name_and_size)
|
330
|
-
ProductDecorator.find_all_by_name_and_size("apples", "large")
|
331
|
-
end
|
332
|
-
|
333
|
-
it "runs find_last_by_(x) finders" do
|
334
|
-
Product.should_receive(:find_last_by_name_and_size)
|
335
|
-
ProductDecorator.find_last_by_name_and_size("apples", "large")
|
336
|
-
end
|
337
|
-
|
338
|
-
it "runs find_or_initialize_by_(x) finders" do
|
339
|
-
Product.should_receive(:find_or_initialize_by_name_and_size)
|
340
|
-
ProductDecorator.find_or_initialize_by_name_and_size("apples", "large")
|
341
|
-
end
|
342
|
-
|
343
|
-
it "runs find_or_create_by_(x) finders" do
|
344
|
-
Product.should_receive(:find_or_create_by_name_and_size)
|
345
|
-
ProductDecorator.find_or_create_by_name_and_size("apples", "large")
|
346
|
-
end
|
347
|
-
|
348
|
-
it "accepts an options hash" do
|
349
|
-
Product.should_receive(:find_by_name_and_size).with("apples", "large", {:role => :admin})
|
350
|
-
ProductDecorator.find_by_name_and_size("apples", "large", {:role => :admin})
|
351
|
-
end
|
352
|
-
|
353
|
-
it "uses the options hash in the decorator instantiation" do
|
354
|
-
Product.should_receive(:find_by_name_and_size).with("apples", "large", {:role => :admin})
|
355
|
-
pd = ProductDecorator.find_by_name_and_size("apples", "large", {:role => :admin})
|
356
|
-
pd.context[:role].should == :admin
|
357
|
-
end
|
358
|
-
end
|
359
|
-
|
360
|
-
context ".decorate" do
|
361
|
-
context "without any context" do
|
362
|
-
subject { Draper::Base.decorate(source) }
|
363
|
-
|
364
|
-
context "when given a collection of source objects" do
|
365
|
-
let(:source) { [Product.new, Product.new] }
|
366
|
-
|
367
|
-
its(:size) { should == source.size }
|
368
|
-
|
369
|
-
it "returns a collection of wrapped objects" do
|
370
|
-
subject.each{ |decorated| decorated.should be_instance_of(Draper::Base) }
|
371
|
-
end
|
372
|
-
|
373
|
-
it 'should accepted and store a context for a collection' do
|
374
|
-
subject.context = :admin
|
375
|
-
subject.each { |decorated| decorated.context.should == :admin }
|
376
|
-
end
|
377
|
-
end
|
378
|
-
|
379
|
-
context "when given a struct" do
|
380
|
-
# Struct objects implement #each
|
381
|
-
let(:source) { Struct.new(:title).new("Godzilla") }
|
382
|
-
|
383
|
-
it "returns a wrapped object" do
|
384
|
-
subject.should be_instance_of(Draper::Base)
|
385
|
-
end
|
386
|
-
end
|
387
|
-
|
388
|
-
context "when given a collection of sequel models" do
|
389
|
-
# Sequel models implement #each
|
390
|
-
let(:source) { [SequelProduct.new, SequelProduct.new] }
|
391
|
-
|
392
|
-
it "returns a collection of wrapped objects" do
|
393
|
-
subject.each{ |decorated| decorated.should be_instance_of(Draper::Base) }
|
394
|
-
end
|
395
|
-
end
|
396
|
-
|
397
|
-
context "when given a single source object" do
|
398
|
-
let(:source) { Product.new }
|
399
|
-
|
400
|
-
it { should be_instance_of(Draper::Base) }
|
401
|
-
|
402
|
-
context "when the input is already decorated" do
|
403
|
-
it "does not perform double-decoration" do
|
404
|
-
decorated = ProductDecorator.decorate(source)
|
405
|
-
ProductDecorator.decorate(decorated).object_id.should == decorated.object_id
|
406
|
-
end
|
407
|
-
|
408
|
-
it "overwrites options with provided options" do
|
409
|
-
first_run = ProductDecorator.decorate(source, :context => {:role => :user})
|
410
|
-
second_run = ProductDecorator.decorate(first_run, :context => {:role => :admin})
|
411
|
-
second_run.context[:role].should == :admin
|
412
|
-
end
|
413
|
-
|
414
|
-
it "leaves existing options if none are supplied" do
|
415
|
-
first_run = ProductDecorator.decorate(source, :context => {:role => :user})
|
416
|
-
second_run = ProductDecorator.decorate(first_run)
|
417
|
-
second_run.context[:role].should == :user
|
418
|
-
end
|
419
|
-
end
|
420
|
-
end
|
421
|
-
end
|
422
|
-
|
423
|
-
context "with a context" do
|
424
|
-
let(:context) {{ :some => 'data' }}
|
425
|
-
|
426
|
-
subject { Draper::Base.decorate(source, :context => context) }
|
427
|
-
|
428
|
-
context "when given a collection of source objects" do
|
429
|
-
let(:source) { [Product.new, Product.new] }
|
430
|
-
|
431
|
-
it "returns a collection of wrapped objects with the context" do
|
432
|
-
subject.each{ |decorated| decorated.context.should eq(context) }
|
433
|
-
end
|
434
|
-
end
|
435
|
-
|
436
|
-
context "when given a single source object" do
|
437
|
-
let(:source) { Product.new }
|
438
|
-
|
439
|
-
its(:context) { should eq(context) }
|
440
|
-
end
|
441
|
-
end
|
442
|
-
|
443
|
-
context "with options" do
|
444
|
-
let(:options) {{ :more => "settings" }}
|
445
|
-
|
446
|
-
subject { Draper::Base.decorate(source, options ) }
|
447
|
-
|
448
|
-
its(:options) { should eq(options) }
|
449
|
-
end
|
450
|
-
|
451
|
-
context "does not infer collections by default" do
|
452
|
-
subject { Draper::Base.decorate(source).to_ary }
|
453
|
-
|
454
|
-
let(:source) { [Product.new, Widget.new] }
|
455
|
-
|
456
|
-
it "returns a collection of wrapped objects all with the same decorator" do
|
457
|
-
subject.first.class.name.should eql 'Draper::Base'
|
458
|
-
subject.last.class.name.should eql 'Draper::Base'
|
459
|
-
end
|
460
|
-
end
|
461
|
-
|
462
|
-
context "does not infer single items by default" do
|
463
|
-
subject { Draper::Base.decorate(source) }
|
464
|
-
|
465
|
-
let(:source) { Product.new }
|
466
|
-
|
467
|
-
it "returns a decorator of the type explicity used in the call" do
|
468
|
-
subject.class.should eql Draper::Base
|
469
|
-
end
|
470
|
-
end
|
471
|
-
|
472
|
-
context "returns a collection containing only the explicit decorator used in the call" do
|
473
|
-
subject { Draper::Base.decorate(source, :infer => true).to_ary }
|
474
|
-
|
475
|
-
let(:source) { [Product.new, Widget.new] }
|
476
|
-
|
477
|
-
it "returns a mixed collection of wrapped objects" do
|
478
|
-
subject.first.class.should eql ProductDecorator
|
479
|
-
subject.last.class.should eql WidgetDecorator
|
480
|
-
end
|
481
|
-
end
|
482
|
-
|
483
|
-
context "when given a single object" do
|
484
|
-
subject { Draper::Base.decorate(source, :infer => true) }
|
485
|
-
|
486
|
-
let(:source) { Product.new }
|
487
|
-
|
488
|
-
it "can also infer its decorator" do
|
489
|
-
subject.class.should eql ProductDecorator
|
490
|
-
end
|
491
|
-
end
|
492
|
-
end
|
493
|
-
|
494
|
-
context('.==') do
|
495
|
-
it "compare the decorated models" do
|
496
|
-
other = Draper::Base.new(source)
|
497
|
-
subject.should == other
|
498
|
-
end
|
499
|
-
end
|
500
|
-
|
501
|
-
context ".respond_to?" do
|
502
|
-
# respond_to? is called by some proxies (id, to_param, errors).
|
503
|
-
# This is, why I stub it this way.
|
504
|
-
it "delegate respond_to? to the decorated model" do
|
505
|
-
other = Draper::Base.new(source)
|
506
|
-
source.stub(:respond_to?).and_return(false)
|
507
|
-
source.stub(:respond_to?).with(:whatever, true).once.and_return("mocked")
|
508
|
-
subject.respond_to?(:whatever, true).should == "mocked"
|
509
|
-
end
|
510
|
-
end
|
511
|
-
|
512
|
-
context 'position accessors' do
|
513
|
-
[:first, :last].each do |method|
|
514
|
-
context "##{method}" do
|
515
|
-
it "return a decorated instance" do
|
516
|
-
ProductDecorator.send(method).should be_instance_of ProductDecorator
|
517
|
-
end
|
518
|
-
|
519
|
-
it "return the #{method} instance of the wrapped class" do
|
520
|
-
ProductDecorator.send(method).model.should == Product.send(method)
|
521
|
-
end
|
522
|
-
|
523
|
-
it "accept an optional context" do
|
524
|
-
ProductDecorator.send(method, :context => :admin).context.should == :admin
|
525
|
-
end
|
526
|
-
end
|
527
|
-
end
|
528
|
-
end
|
529
|
-
|
530
|
-
describe "collection decoration" do
|
531
|
-
|
532
|
-
# Implementation of #decorate that returns an array
|
533
|
-
# of decorated objects is insufficient to deal with
|
534
|
-
# situations where the original collection has been
|
535
|
-
# expanded with the use of modules (as often the case
|
536
|
-
# with paginator gems) or is just more complex then
|
537
|
-
# an array.
|
538
|
-
module Paginator; def page_number; "magic_value"; end; end
|
539
|
-
Array.send(:include, Paginator)
|
540
|
-
let(:paged_array) { [Product.new, Product.new] }
|
541
|
-
let(:empty_collection) { [] }
|
542
|
-
subject { ProductDecorator.decorate(paged_array) }
|
543
|
-
|
544
|
-
it "proxy all calls to decorated collection" do
|
545
|
-
paged_array.page_number.should == "magic_value"
|
546
|
-
subject.page_number.should == "magic_value"
|
547
|
-
end
|
548
|
-
|
549
|
-
it "support Rails partial lookup for a collection" do
|
550
|
-
# to support Rails render @collection the returned collection
|
551
|
-
# (or its proxy) should implement #to_ary.
|
552
|
-
subject.respond_to?(:to_ary).should be true
|
553
|
-
subject.to_ary.first.should == ProductDecorator.decorate(paged_array.first)
|
554
|
-
end
|
555
|
-
|
556
|
-
it "delegate respond_to? to the wrapped collection" do
|
557
|
-
decorator = ProductDecorator.decorate(paged_array)
|
558
|
-
paged_array.should_receive(:respond_to?).with(:whatever, true)
|
559
|
-
decorator.respond_to?(:whatever, true)
|
560
|
-
end
|
561
|
-
|
562
|
-
it "return blank for a decorated empty collection" do
|
563
|
-
# This tests that respond_to? is defined for the DecoratedEnumerableProxy
|
564
|
-
# since activesupport calls respond_to?(:empty) in #blank
|
565
|
-
decorator = ProductDecorator.decorate(empty_collection)
|
566
|
-
decorator.should be_blank
|
567
|
-
end
|
568
|
-
|
569
|
-
it "return whether the member is in the array for a decorated wrapped collection" do
|
570
|
-
# This tests that include? is defined for the DecoratedEnumerableProxy
|
571
|
-
member = paged_array.first
|
572
|
-
subject.respond_to?(:include?)
|
573
|
-
subject.include?(member).should == true
|
574
|
-
subject.include?(subject.first).should == true
|
575
|
-
subject.include?(Product.new).should == false
|
576
|
-
end
|
577
|
-
|
578
|
-
it "equal each other when decorating the same collection" do
|
579
|
-
subject_one = ProductDecorator.decorate(paged_array)
|
580
|
-
subject_two = ProductDecorator.decorate(paged_array)
|
581
|
-
subject_one.should == subject_two
|
582
|
-
end
|
583
|
-
|
584
|
-
it "not equal each other when decorating different collections" do
|
585
|
-
subject_one = ProductDecorator.decorate(paged_array)
|
586
|
-
new_paged_array = paged_array + [Product.new]
|
587
|
-
subject_two = ProductDecorator.decorate(new_paged_array)
|
588
|
-
subject_one.should_not == subject_two
|
589
|
-
end
|
590
|
-
|
591
|
-
it "allow decorated access by index" do
|
592
|
-
subject = ProductDecorator.decorate(paged_array)
|
593
|
-
subject[0].should be_instance_of ProductDecorator
|
594
|
-
end
|
595
|
-
|
596
|
-
context "pretends to be of kind of wrapped collection class" do
|
597
|
-
subject { ProductDecorator.decorate(paged_array) }
|
598
|
-
|
599
|
-
it "#kind_of? DecoratedEnumerableProxy" do
|
600
|
-
subject.should be_kind_of Draper::DecoratedEnumerableProxy
|
601
|
-
end
|
602
|
-
|
603
|
-
it "#is_a? DecoratedEnumerableProxy" do
|
604
|
-
subject.is_a?(Draper::DecoratedEnumerableProxy).should be_true
|
605
|
-
end
|
606
|
-
|
607
|
-
it "#kind_of? Array" do
|
608
|
-
subject.should be_kind_of Array
|
609
|
-
end
|
610
|
-
|
611
|
-
it "#is_a? Array" do
|
612
|
-
subject.is_a?(Array).should be_true
|
613
|
-
end
|
614
|
-
end
|
615
|
-
|
616
|
-
context '#all' do
|
617
|
-
it "return a decorated collection" do
|
618
|
-
ProductDecorator.all.first.should be_instance_of ProductDecorator
|
619
|
-
end
|
620
|
-
|
621
|
-
it "accept a context" do
|
622
|
-
collection = ProductDecorator.all(:context => :admin)
|
623
|
-
collection.first.context.should == :admin
|
624
|
-
end
|
625
|
-
end
|
626
|
-
|
627
|
-
context(".source / .to_source") do
|
628
|
-
it "return the wrapped object" do
|
629
|
-
subject.to_source == source
|
630
|
-
subject.source == source
|
631
|
-
end
|
632
|
-
end
|
633
|
-
end
|
634
|
-
|
635
|
-
describe "a sample usage with denies" do
|
636
|
-
let(:subject_with_denies){ DecoratorWithDenies.new(source) }
|
637
|
-
|
638
|
-
it "proxy methods not listed in denies" do
|
639
|
-
subject_with_denies.should respond_to(:hello_world)
|
640
|
-
end
|
641
|
-
|
642
|
-
it "not echo methods specified with denies" do
|
643
|
-
subject_with_denies.should_not respond_to(:goodnight_moon)
|
644
|
-
end
|
645
|
-
|
646
|
-
it "not clobber other decorators' methods" do
|
647
|
-
subject.should respond_to(:hello_world)
|
648
|
-
end
|
649
|
-
|
650
|
-
it "not allow method_missing to circumvent a deny" do
|
651
|
-
expect{subject_with_denies.title}.to raise_error(NoMethodError)
|
652
|
-
end
|
653
|
-
end
|
654
|
-
|
655
|
-
describe "a sample usage with allows" do
|
656
|
-
let(:subject_with_allows){ DecoratorWithAllows.new(source) }
|
657
|
-
|
658
|
-
let(:subject_with_multiple_allows){ DecoratorWithMultipleAllows.new(source) }
|
659
|
-
|
660
|
-
it "echo the allowed method" do
|
661
|
-
subject_with_allows.should respond_to(:goodnight_moon)
|
662
|
-
end
|
663
|
-
|
664
|
-
it "echo _only_ the allowed method" do
|
665
|
-
subject_with_allows.should_not respond_to(:hello_world)
|
666
|
-
end
|
667
|
-
|
668
|
-
it "echo the combined allowed methods" do
|
669
|
-
subject_with_multiple_allows.should respond_to(:goodnight_moon)
|
670
|
-
subject_with_multiple_allows.should respond_to(:hello_world)
|
671
|
-
end
|
672
|
-
|
673
|
-
it "echo _only_ the combined allowed methods" do
|
674
|
-
subject_with_multiple_allows.should_not respond_to(:title)
|
675
|
-
end
|
676
|
-
end
|
677
|
-
|
678
|
-
describe "invalid usages of allows and denies" do
|
679
|
-
let(:blank_allows){
|
680
|
-
class DecoratorWithInvalidAllows < Draper::Base
|
681
|
-
allows
|
682
|
-
end
|
683
|
-
}
|
684
|
-
|
685
|
-
let(:blank_denies){
|
686
|
-
class DecoratorWithInvalidDenies < Draper::Base
|
687
|
-
denies
|
688
|
-
end
|
689
|
-
}
|
690
|
-
|
691
|
-
let(:using_allows_then_denies){
|
692
|
-
class DecoratorWithAllowsAndDenies < Draper::Base
|
693
|
-
allows :hello_world
|
694
|
-
denies :goodnight_moon
|
695
|
-
end
|
696
|
-
}
|
697
|
-
|
698
|
-
let(:using_denies_then_allows){
|
699
|
-
class DecoratorWithDeniesAndAllows < Draper::Base
|
700
|
-
denies :goodnight_moon
|
701
|
-
allows :hello_world
|
702
|
-
end
|
703
|
-
}
|
704
|
-
|
705
|
-
it "raise an exception for a blank allows" do
|
706
|
-
expect {blank_allows}.to raise_error(ArgumentError)
|
707
|
-
end
|
708
|
-
|
709
|
-
it "raise an exception for a blank denies" do
|
710
|
-
expect {blank_denies}.to raise_error(ArgumentError)
|
711
|
-
end
|
712
|
-
|
713
|
-
it "raise an exception for calling allows then denies" do
|
714
|
-
expect {using_allows_then_denies}.to raise_error(ArgumentError)
|
715
|
-
end
|
716
|
-
|
717
|
-
it "raise an exception for calling denies then allows" do
|
718
|
-
expect {using_denies_then_allows}.to raise_error(ArgumentError)
|
719
|
-
end
|
720
|
-
end
|
721
|
-
|
722
|
-
describe "a sample usage with denies_all" do
|
723
|
-
let(:subject_with_denies_all){ DecoratorWithDeniesAll.new(source) }
|
724
|
-
|
725
|
-
[:goodnight_moon, :hello_world, :title].each do |method|
|
726
|
-
it "does echo #{method} method" do
|
727
|
-
subject_with_denies_all.should_not respond_to(method)
|
728
|
-
end
|
729
|
-
end
|
730
|
-
|
731
|
-
let(:using_denies_all_then_denies_all) {
|
732
|
-
class DecoratorWithDeniesAllAndDeniesAll < Draper::Base
|
733
|
-
denies_all
|
734
|
-
denies_all
|
735
|
-
end
|
736
|
-
}
|
737
|
-
|
738
|
-
it "allows multple calls to .denies_all" do
|
739
|
-
expect { using_denies_all_then_denies_all }.to_not raise_error(ArgumentError)
|
740
|
-
end
|
741
|
-
end
|
742
|
-
|
743
|
-
describe "invalid usages of denies_all" do
|
744
|
-
let(:using_allows_then_denies_all) {
|
745
|
-
class DecoratorWithAllowsAndDeniesAll < Draper::Base
|
746
|
-
allows :hello_world
|
747
|
-
denies_all
|
748
|
-
end
|
749
|
-
}
|
750
|
-
let(:using_denies_then_denies_all) {
|
751
|
-
class DecoratorWithDeniesAndDeniesAll < Draper::Base
|
752
|
-
denies :goodnight_moon
|
753
|
-
denies_all
|
754
|
-
end
|
755
|
-
}
|
756
|
-
let(:using_denies_all_then_allows) {
|
757
|
-
class DecoratorWithDeniesAllAndAllows < Draper::Base
|
758
|
-
denies_all
|
759
|
-
allows :hello_world
|
760
|
-
end
|
761
|
-
}
|
762
|
-
let(:using_denies_all_then_denies) {
|
763
|
-
class DecoratorWithDeniesAllAndDenies < Draper::Base
|
764
|
-
denies_all
|
765
|
-
denies :goodnight_moon
|
766
|
-
end
|
767
|
-
}
|
768
|
-
it "raises an exception when calling allows then denies_all" do
|
769
|
-
expect {using_allows_then_denies_all}.to raise_error(ArgumentError)
|
770
|
-
end
|
771
|
-
|
772
|
-
it "raises an exception when calling denies then denies_all" do
|
773
|
-
expect {using_denies_then_denies_all}.to raise_error(ArgumentError)
|
774
|
-
end
|
775
|
-
|
776
|
-
it "raises an exception when calling denies_all then allows" do
|
777
|
-
expect {using_denies_all_then_allows}.to raise_error(ArgumentError)
|
778
|
-
end
|
779
|
-
|
780
|
-
it "raises an exception when calling denies_all then denies" do
|
781
|
-
expect {using_denies_all_then_denies}.to raise_error(ArgumentError)
|
782
|
-
end
|
783
|
-
end
|
784
|
-
|
785
|
-
context "in a Rails application" do
|
786
|
-
let(:decorator){ DecoratorWithApplicationHelper.decorate(Object.new) }
|
787
|
-
|
788
|
-
it "have access to ApplicationHelper helpers" do
|
789
|
-
decorator.uses_hello_world == "Hello, World!"
|
790
|
-
end
|
791
|
-
|
792
|
-
it "is able to use the content_tag helper" do
|
793
|
-
decorator.sample_content.to_s.should == "<span>Hello, World!</span>"
|
794
|
-
end
|
795
|
-
|
796
|
-
it "is able to use the link_to helper" do
|
797
|
-
decorator.sample_link.should == "<a href=\"/World\">Hello</a>"
|
798
|
-
end
|
799
|
-
|
800
|
-
it "is able to use the pluralize helper" do
|
801
|
-
decorator.sample_truncate.should == "Once..."
|
802
|
-
end
|
803
|
-
|
804
|
-
it "is able to use l rather than helpers.l" do
|
805
|
-
now = Time.now
|
806
|
-
helper_proxy = decorator.helpers.instance_variable_get(:@helpers)
|
807
|
-
helper_proxy.should_receive(:localize).with(now, :format => :long)
|
808
|
-
decorator.l now, :format => :long
|
809
|
-
end
|
810
|
-
|
811
|
-
it "is able to access html_escape, a private method" do
|
812
|
-
decorator.sample_html_escaped_text.should == '<script>danger</script>'
|
813
|
-
end
|
814
|
-
end
|
815
|
-
|
816
|
-
context "#method_missing" do
|
817
|
-
context "with an isolated decorator class" do
|
818
|
-
let(:decorator_class) { Class.new(Decorator) }
|
819
|
-
subject{ decorator_class.new(source) }
|
820
|
-
|
821
|
-
context "when #hello_world is called again" do
|
822
|
-
it "proxies method directly after first hit" do
|
823
|
-
subject.methods.should_not include(:hello_world)
|
824
|
-
subject.hello_world
|
825
|
-
subject.methods.should include(:hello_world)
|
826
|
-
end
|
827
|
-
end
|
828
|
-
|
829
|
-
context "when #hello_world is called for the first time" do
|
830
|
-
it "hits method missing" do
|
831
|
-
subject.should_receive(:method_missing)
|
832
|
-
subject.hello_world
|
833
|
-
end
|
834
|
-
end
|
835
|
-
end
|
836
|
-
|
837
|
-
context "when the delegated method calls a non-existant method" do
|
838
|
-
it 'should not try to delegate to non-existant methods to not confuse Kernel#Array' do
|
839
|
-
Array(subject).should be_kind_of(Array)
|
840
|
-
end
|
841
|
-
|
842
|
-
it "raises the correct NoMethodError" do
|
843
|
-
begin
|
844
|
-
subject.some_action
|
845
|
-
rescue NoMethodError => e
|
846
|
-
e.name.should_not == :some_action
|
847
|
-
else
|
848
|
-
fail("No exception raised")
|
849
|
-
end
|
850
|
-
end
|
851
|
-
end
|
852
|
-
end
|
853
|
-
|
854
|
-
describe "#kind_of?" do
|
855
|
-
context "pretends to be of kind of model class" do
|
856
|
-
it "#kind_of? decorator class" do
|
857
|
-
subject.should be_kind_of subject.class
|
858
|
-
end
|
859
|
-
|
860
|
-
it "#is_a? decorator class" do
|
861
|
-
subject.is_a?(subject.class).should be_true
|
862
|
-
end
|
863
|
-
|
864
|
-
it "#kind_of? source class" do
|
865
|
-
subject.should be_kind_of source.class
|
866
|
-
end
|
867
|
-
|
868
|
-
it "#is_a? source class" do
|
869
|
-
subject.is_a?(source.class).should be_true
|
870
|
-
end
|
871
|
-
end
|
872
|
-
end
|
873
|
-
end
|