draper 0.10.0 → 0.11.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.
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