draper 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.travis.yml +0 -1
  2. data/Gemfile +3 -3
  3. data/Rakefile +8 -8
  4. data/Readme.markdown +41 -22
  5. data/doc/css/full_list.css +2 -2
  6. data/doc/css/style.css +30 -30
  7. data/doc/js/app.js +10 -10
  8. data/doc/js/full_list.js +8 -8
  9. data/draper.gemspec +2 -2
  10. data/lib/draper.rb +1 -1
  11. data/lib/draper/base.rb +65 -22
  12. data/lib/draper/decorated_enumerable_proxy.rb +6 -1
  13. data/lib/draper/model_support.rb +2 -2
  14. data/lib/draper/railtie.rb +19 -0
  15. data/lib/draper/system.rb +7 -4
  16. data/lib/draper/version.rb +1 -1
  17. data/lib/generators/draper/decorator/decorator_generator.rb +26 -4
  18. data/lib/generators/draper/decorator/templates/decorator.rb +2 -2
  19. data/lib/generators/{rspec → draper/decorator}/templates/decorator_spec.rb +1 -1
  20. data/lib/generators/{test_unit → draper/decorator}/templates/decorator_test.rb +1 -1
  21. data/lib/generators/draper/install/install_generator.rb +6 -6
  22. data/lib/generators/rails/decorator_generator.rb +3 -3
  23. data/performance/bechmark.rb +5 -5
  24. data/performance/decorators.rb +4 -4
  25. data/performance/models.rb +2 -2
  26. data/spec/draper/base_spec.rb +176 -10
  27. data/spec/draper/helper_support_spec.rb +1 -1
  28. data/spec/draper/model_support_spec.rb +23 -14
  29. data/spec/draper/view_context_spec.rb +4 -4
  30. data/spec/generators/draper/decorator/decorator_generator_spec.rb +81 -1
  31. data/spec/generators/draper/install/install_generator_spec.rb +5 -5
  32. data/spec/spec_helper.rb +3 -0
  33. data/spec/support/samples/application_controller.rb +8 -11
  34. data/spec/support/samples/decorator_with_allows.rb +1 -1
  35. data/spec/support/samples/decorator_with_application_helper.rb +5 -5
  36. data/spec/support/samples/decorator_with_denies.rb +1 -1
  37. data/spec/support/samples/decorator_with_multiple_allows.rb +4 -0
  38. data/spec/support/samples/product.rb +28 -7
  39. data/spec/support/samples/some_thing.rb +2 -0
  40. data/spec/support/samples/some_thing_decorator.rb +3 -0
  41. metadata +62 -37
  42. data/lib/generators/rspec/decorator_generator.rb +0 -11
  43. data/lib/generators/test_unit/decorator_generator.rb +0 -11
  44. data/spec/generators/rspec/decorator_generator_spec.rb +0 -22
  45. data/spec/generators/test_unit/decorator_generator_spec.rb +0 -22
@@ -3,7 +3,7 @@ class Product < ActiveRecord::Base
3
3
  def self.sample_class_method
4
4
  "sample class method"
5
5
  end
6
-
6
+
7
7
  def hello_world
8
8
  "Hello, World"
9
9
  end
@@ -13,7 +13,7 @@ class FastProduct < ActiveRecord::Base
13
13
  def self.sample_class_method
14
14
  "sample class method"
15
15
  end
16
-
16
+
17
17
  def hello_world
18
18
  "Hello, World"
19
19
  end
@@ -54,6 +54,44 @@ describe Draper::Base do
54
54
  end.should_not raise_error
55
55
  end
56
56
 
57
+ context("accepts ActiveRecord like :class_name option too") do
58
+ it "accepts constants for :class" do
59
+ expect do
60
+ class CustomDecorator < Draper::Base
61
+ decorates :product, :class => Product
62
+ end
63
+ CustomDecorator.model_class.should == Product
64
+ end.should_not raise_error
65
+ end
66
+
67
+ it "accepts constants for :class_name" do
68
+ expect do
69
+ class CustomDecorator < Draper::Base
70
+ decorates :product, :class_name => Product
71
+ end
72
+ CustomDecorator.model_class.should == Product
73
+ end.should_not raise_error
74
+ end
75
+
76
+ it "accepts strings for :class" do
77
+ expect do
78
+ class CustomDecorator < Draper::Base
79
+ decorates :product, :class => 'Product'
80
+ end
81
+ CustomDecorator.model_class.should == Product
82
+ end.should_not raise_error
83
+ end
84
+
85
+ it "accepts strings for :class_name" do
86
+ expect do
87
+ class CustomDecorator < Draper::Base
88
+ decorates :product, :class_name => 'Product'
89
+ end
90
+ CustomDecorator.model_class.should == Product
91
+ end.should_not raise_error
92
+ end
93
+ end
94
+
57
95
  it "creates a named accessor for the wrapped model" do
58
96
  pd = ProductDecorator.new(source)
59
97
  pd.send(:product).should == source
@@ -75,22 +113,31 @@ describe Draper::Base do
75
113
  end
76
114
 
77
115
  context(".decorates_association") do
78
- context "for collection associations" do
116
+ context "for ActiveModel collection associations" do
79
117
  before(:each){ subject.class_eval{ decorates_association :similar_products } }
80
118
  it "causes the association's method to return a collection of wrapped objects" do
81
119
  subject.similar_products.each{ |decorated| decorated.should be_instance_of(ProductDecorator) }
82
120
  end
83
121
  end
84
122
 
85
- context "for a singular association" do
123
+ context "for Plain Old Ruby Object collection associations" do
124
+ before(:each){ subject.class_eval{ decorates_association :poro_similar_products } }
125
+ it "causes the association's method to return a collection of wrapped objects" do
126
+ subject.poro_similar_products.each{ |decorated| decorated.should be_instance_of(ProductDecorator) }
127
+ end
128
+ end
129
+
130
+ context "for an ActiveModel singular association" do
86
131
  before(:each){ subject.class_eval{ decorates_association :previous_version } }
87
132
  it "causes the association's method to return a single wrapped object if the association is singular" do
88
133
  subject.previous_version.should be_instance_of(ProductDecorator)
89
134
  end
135
+ end
90
136
 
91
- it "causes the association's method to return nil if the association is nil" do
92
- source.stub(:previous_version){ nil }
93
- subject.previous_version.should be_nil
137
+ context "for a Plain Old Ruby Object singular association" do
138
+ before(:each){ subject.class_eval{ decorates_association :poro_previous_version } }
139
+ it "causes the association's method to return a single wrapped object" do
140
+ subject.poro_previous_version.should be_instance_of(ProductDecorator)
94
141
  end
95
142
  end
96
143
 
@@ -100,6 +147,23 @@ describe Draper::Base do
100
147
  subject.previous_version.should be_instance_of(SpecificProductDecorator)
101
148
  end
102
149
  end
150
+
151
+ context "for a polymorphic association" do
152
+ before(:each){ subject.class_eval{ decorates_association :thing, :polymorphic => true } }
153
+ it "causes the association to be decorated with the right decorator" do
154
+ subject.thing.should be_instance_of(SomeThingDecorator)
155
+ end
156
+ end
157
+
158
+ context "when the association is nil" do
159
+ before(:each) do
160
+ subject.class_eval{ decorates_association :previous_version }
161
+ source.stub(:previous_version){ nil }
162
+ end
163
+ it "causes the association's method to return nil" do
164
+ subject.previous_version.should be_nil
165
+ end
166
+ end
103
167
  end
104
168
 
105
169
  context('.decorates_associations') do
@@ -119,6 +183,13 @@ describe Draper::Base do
119
183
  end
120
184
  end
121
185
 
186
+ context(".source / .to_source") do
187
+ it "should return the wrapped object" do
188
+ subject.to_source == source
189
+ subject.source == source
190
+ end
191
+ end
192
+
122
193
  context("selecting methods") do
123
194
  it "echos the methods of the wrapped class except default exclusions" do
124
195
  source.methods.each do |method|
@@ -178,6 +249,34 @@ describe Draper::Base do
178
249
  end
179
250
  end
180
251
 
252
+ context ".find_by_(x)" do
253
+ it "runs the similarly named finder" do
254
+ Product.should_receive(:find_by_name)
255
+ ProductDecorator.find_by_name("apples")
256
+ end
257
+
258
+ it "returns a decorated result" do
259
+ ProductDecorator.find_by_name("apples").should be_kind_of(ProductDecorator)
260
+ end
261
+
262
+ it "runs complex finders" do
263
+ Product.should_receive(:find_by_name_and_size)
264
+ ProductDecorator.find_by_name_and_size("apples", "large")
265
+ end
266
+
267
+ it "accepts an options hash" do
268
+ Product.should_receive(:find_by_name_and_size).with("apples", "large", {:role => :admin})
269
+ ProductDecorator.find_by_name_and_size("apples", "large", {:role => :admin})
270
+ end
271
+
272
+ it "uses the options hash in the decorator instantiation" do
273
+ pending "Figure out an implementation that supports multiple args (find_by_name_and_count_and_size) plus an options hash"
274
+ Product.should_receive(:find_by_name_and_size).with("apples", "large", {:role => :admin})
275
+ pd = ProductDecorator.find_by_name_and_size("apples", "large", {:role => :admin})
276
+ pd.context[:role].should == :admin
277
+ end
278
+ end
279
+
181
280
  context ".decorate" do
182
281
  context "without any context" do
183
282
  subject { Draper::Base.decorate(source) }
@@ -196,6 +295,25 @@ describe Draper::Base do
196
295
  let(:source) { Product.new }
197
296
 
198
297
  it { should be_instance_of(Draper::Base) }
298
+
299
+ context "when the input is already decorated" do
300
+ it "does not perform double-decoration" do
301
+ decorated = ProductDecorator.decorate(source)
302
+ ProductDecorator.decorate(decorated).object_id.should == decorated.object_id
303
+ end
304
+
305
+ it "overwrites options with provided options" do
306
+ first_run = ProductDecorator.decorate(source, :context => {:role => :user})
307
+ second_run = ProductDecorator.decorate(first_run, :context => {:role => :admin})
308
+ second_run.context[:role].should == :admin
309
+ end
310
+
311
+ it "leaves existing options if none are supplied" do
312
+ first_run = ProductDecorator.decorate(source, :context => {:role => :user})
313
+ second_run = ProductDecorator.decorate(first_run)
314
+ second_run.context[:role].should == :user
315
+ end
316
+ end
199
317
  end
200
318
  end
201
319
 
@@ -219,6 +337,14 @@ describe Draper::Base do
219
337
  end
220
338
  end
221
339
 
340
+ context "with options" do
341
+ let(:options) {{ :more => "settings" }}
342
+
343
+ subject { Draper::Base.decorate(source, options ) }
344
+
345
+ its(:options) { should eq(options) }
346
+ end
347
+
222
348
  context "does not infer collections by default" do
223
349
  subject { Draper::Base.decorate(source).to_ary }
224
350
 
@@ -352,10 +478,10 @@ describe Draper::Base do
352
478
  subject = ProductDecorator.decorate(paged_array)
353
479
  subject[0].should be_instance_of ProductDecorator
354
480
  end
355
-
481
+
356
482
  context "pretends to be of kind of wrapped collection class" do
357
483
  subject { ProductDecorator.decorate(paged_array) }
358
-
484
+
359
485
  it "#kind_of? DecoratedEnumerableProxy" do
360
486
  subject.should be_kind_of Draper::DecoratedEnumerableProxy
361
487
  end
@@ -383,6 +509,13 @@ describe Draper::Base do
383
509
  collection.first.context.should == :admin
384
510
  end
385
511
  end
512
+
513
+ context(".source / .to_source") do
514
+ it "should return the wrapped object" do
515
+ subject.to_source == source
516
+ subject.source == source
517
+ end
518
+ end
386
519
  end
387
520
 
388
521
  describe "a sample usage with denies" do
@@ -408,6 +541,8 @@ describe Draper::Base do
408
541
  describe "a sample usage with allows" do
409
542
  let(:subject_with_allows){ DecoratorWithAllows.new(source) }
410
543
 
544
+ let(:subject_with_multiple_allows){ DecoratorWithMultipleAllows.new(source) }
545
+
411
546
  it "should echo the allowed method" do
412
547
  subject_with_allows.should respond_to(:goodnight_moon)
413
548
  end
@@ -415,6 +550,15 @@ describe Draper::Base do
415
550
  it "should echo _only_ the allowed method" do
416
551
  subject_with_allows.should_not respond_to(:hello_world)
417
552
  end
553
+
554
+ it "should echo the combined allowed methods" do
555
+ subject_with_multiple_allows.should respond_to(:goodnight_moon)
556
+ subject_with_multiple_allows.should respond_to(:hello_world)
557
+ end
558
+
559
+ it "should echo _only_ the combined allowed methods" do
560
+ subject_with_multiple_allows.should_not respond_to(:title)
561
+ end
418
562
  end
419
563
 
420
564
  describe "invalid usages of allows and denies" do
@@ -479,8 +623,14 @@ describe Draper::Base do
479
623
  it "should be able to use the pluralize helper" do
480
624
  decorator.sample_truncate.should == "Once..."
481
625
  end
626
+
627
+ it "should be able to use l rather than helpers.l" do
628
+ now = Time.now
629
+ decorator.helpers.should_receive(:localize).with(now)
630
+ decorator.l now
631
+ end
482
632
  end
483
-
633
+
484
634
  describe "#method_missing" do
485
635
  context "when #hello_world is called for the first time" do
486
636
  it "hits method missing" do
@@ -488,7 +638,7 @@ describe Draper::Base do
488
638
  subject.hello_world
489
639
  end
490
640
  end
491
-
641
+
492
642
  context "when #hello_world is called again" do
493
643
  before { subject.hello_world }
494
644
  it "proxies method directly after first hit" do
@@ -496,10 +646,26 @@ describe Draper::Base do
496
646
  subject.hello_world
497
647
  end
498
648
  end
649
+
650
+ context "when the delegated method calls a non-existant method" do
651
+ it 'should not try to delegate to non-existant methods to not confuse Kernel#Array' do
652
+ Array(subject).should be_kind_of(Array)
653
+ end
654
+
655
+ it "raises the correct NoMethodError" do
656
+ begin
657
+ subject.some_action
658
+ rescue NoMethodError => e
659
+ e.name.should_not == :some_action
660
+ else
661
+ fail("No exception raised")
662
+ end
663
+ end
664
+ end
499
665
  end
500
666
 
501
667
  describe "#kind_of?" do
502
- context "pretends to be of kind of model class" do
668
+ context "pretends to be of kind of model class" do
503
669
  it "#kind_of? decorator class" do
504
670
  subject.should be_kind_of subject.class
505
671
  end
@@ -8,7 +8,7 @@ describe Draper::HelperSupport do
8
8
  output = ApplicationController.decorate(@product){|p| p.model.object_id }
9
9
  output.should == @product.object_id
10
10
  end
11
-
11
+
12
12
  it 'uses #capture so Rails only renders the content once' do
13
13
  ApplicationController.decorate(@product){|p| p.model.object_id }
14
14
  ApplicationController.capture_triggered.should be
@@ -11,29 +11,38 @@ describe Draper::ModelSupport do
11
11
  a = Product.new.decorator { |d| d.awesome_title }
12
12
  a.should eql "Awesome Title"
13
13
  end
14
-
14
+
15
15
  it 'should be aliased to .decorate' do
16
16
  subject.decorator.model.should == subject.decorate.model
17
17
  end
18
18
  end
19
19
 
20
- describe '#decorate - decorate collections of AR objects' do
21
- subject { Product.limit }
22
- its(:decorate) { should be_kind_of(Draper::DecoratedEnumerableProxy) }
20
+ describe Draper::ModelSupport::ClassMethods do
21
+ shared_examples_for "a call to Draper::ModelSupport::ClassMethods#decorate" do
22
+ subject { klass.limit }
23
+
24
+ its(:decorate) { should be_kind_of(Draper::DecoratedEnumerableProxy) }
23
25
 
24
- it "should decorate the collection" do
25
- subject.decorate.size.should == 1
26
- subject.decorate.to_ary[0].model.should be_a(Product)
26
+ it "should decorate the collection" do
27
+ subject.decorate.size.should == 1
28
+ subject.decorate.to_ary[0].model.should be_a(klass)
29
+ end
30
+
31
+ it "should return a new instance each time it is called" do
32
+ subject.decorate.should_not == subject.decorate
33
+ end
34
+ end
35
+
36
+ describe '#decorate - decorate collections of AR objects' do
37
+ let(:klass) { Product }
38
+
39
+ it_should_behave_like "a call to Draper::ModelSupport::ClassMethods#decorate"
27
40
  end
28
- end
29
41
 
30
- describe '#decorate - decorate collections of namespaced AR objects' do
31
- subject { Namespace::Product.limit }
32
- its(:decorate) { should be_kind_of(Draper::DecoratedEnumerableProxy) }
42
+ describe '#decorate - decorate collections of namespaced AR objects' do
43
+ let(:klass) { Namespace::Product }
33
44
 
34
- it "should decorate the collection" do
35
- subject.decorate.size.should == 1
36
- subject.decorate.to_ary[0].model.should be_a(Namespace::Product)
45
+ it_should_behave_like "a call to Draper::ModelSupport::ClassMethods#decorate"
37
46
  end
38
47
  end
39
48
  end
@@ -4,19 +4,19 @@ describe Draper::ViewContext do
4
4
  let (:app_controller) do
5
5
  ApplicationController
6
6
  end
7
-
7
+
8
8
  let (:app_controller_instance) do
9
9
  app_controller.new
10
10
  end
11
-
11
+
12
12
  it "implements #set_current_view_context" do
13
13
  app_controller_instance.should respond_to(:set_current_view_context)
14
14
  end
15
-
15
+
16
16
  it "calls #before_filter with #set_current_view_context" do
17
17
  app_controller.before_filters.should include(:set_current_view_context)
18
18
  end
19
-
19
+
20
20
  it "raises an exception if the view_context is fetched without being set" do
21
21
  Draper::ViewContext.current = nil
22
22
  expect {app_controller.current_view_context}.should raise_exception(Exception)
@@ -9,6 +9,49 @@ describe Draper::DecoratorGenerator do
9
9
 
10
10
  before { prepare_destination }
11
11
 
12
+ context 'decorator context' do
13
+ before { run_generator ["product"] }
14
+
15
+ describe 'app/decorators/product_decorator.rb' do
16
+ subject { file('app/decorators/product_decorator.rb') }
17
+ it { should exist }
18
+ it { should contain "class ProductDecorator < ApplicationDecorator" }
19
+ end
20
+ end
21
+
22
+ context 'default test framework' do
23
+ before { run_generator ["product"] }
24
+
25
+ describe 'spec/decorators/product_decorator_spec.rb' do
26
+ subject { file('spec/decorators/product_decorator_spec.rb') }
27
+ it { should exist }
28
+ it { should contain "describe ProductDecorator" }
29
+ end
30
+ end
31
+
32
+ context 'using rspec' do
33
+ before { run_generator ["product", "-t=rspec"] }
34
+
35
+ describe 'spec/decorators/product_decorator_spec.rb' do
36
+ subject { file('spec/decorators/product_decorator_spec.rb') }
37
+ it { should exist }
38
+ it { should contain "describe ProductDecorator" }
39
+ end
40
+ end
41
+
42
+ context 'using rspec' do
43
+ before { run_generator ["product", "-t=test_unit"] }
44
+
45
+ describe 'test/decorators/product_decorator_test.rb' do
46
+ subject { file('test/decorators/product_decorator_test.rb') }
47
+ it { should exist }
48
+ it { should contain "class ProductDecoratorTest < ActiveSupport::TestCase" }
49
+ end
50
+ end
51
+ end
52
+
53
+
54
+ =begin
12
55
  describe 'no arguments' do
13
56
  before { run_generator %w(products) }
14
57
 
@@ -17,6 +60,43 @@ describe Draper::DecoratorGenerator do
17
60
  it { should exist }
18
61
  it { should contain "class ProductsDecorator < ApplicationDecorator" }
19
62
  end
63
+ end
64
+
20
65
 
66
+ context 'simple' do
67
+ before { run_generator %w(products) }
68
+
69
+ describe 'app/decorators/products_decorator.rb' do
70
+ subject { file('app/decorators/products_decorator.rb') }
71
+ it { should exist }
72
+ it { should contain "class ProductsDecorator < ApplicationDecorator" }
73
+ end
21
74
  end
22
- end
75
+
76
+
77
+
78
+
79
+
80
+ context 'using rspec' do
81
+
82
+ describe 'app/decorators/products_decorator.rb' do
83
+ subject { file('app/decorators/products_decorator.rb') }
84
+ it { should exist }
85
+ it { should contain "class ProductsDecorator < ApplicationDecorator" }
86
+ end
87
+
88
+ shared_examples_for "ApplicationDecoratorGenerator" do
89
+ describe 'app/decorators/application_decorator.rb' do
90
+ subject { file('app/decorators/application_decorator.rb') }
91
+ it { should exist }
92
+ it { should contain "class ApplicationDecorator < Draper::Base" }
93
+ end
94
+ end
95
+
96
+ describe 'spec/decorators/application_decorator_spec.rb' do
97
+ subject { file('spec/decorators/application_decorator_spec.rb') }
98
+ it { should exist }
99
+ it { should contain "describe ApplicationDecorator do" }
100
+ end
101
+ end
102
+ =end