draper 1.0.0.beta6 → 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.
Files changed (59) hide show
  1. data/.travis.yml +6 -0
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +20 -0
  4. data/Gemfile +11 -0
  5. data/README.md +14 -17
  6. data/Rakefile +5 -3
  7. data/draper.gemspec +2 -2
  8. data/lib/draper.rb +2 -1
  9. data/lib/draper/automatic_delegation.rb +50 -0
  10. data/lib/draper/collection_decorator.rb +26 -7
  11. data/lib/draper/decoratable.rb +71 -32
  12. data/lib/draper/decorated_association.rb +11 -7
  13. data/lib/draper/decorator.rb +114 -148
  14. data/lib/draper/delegation.rb +13 -0
  15. data/lib/draper/finders.rb +9 -6
  16. data/lib/draper/helper_proxy.rb +4 -3
  17. data/lib/draper/lazy_helpers.rb +10 -6
  18. data/lib/draper/railtie.rb +5 -4
  19. data/lib/draper/tasks/test.rake +22 -0
  20. data/lib/draper/test/devise_helper.rb +34 -0
  21. data/lib/draper/test/minitest_integration.rb +2 -3
  22. data/lib/draper/test/rspec_integration.rb +4 -59
  23. data/lib/draper/test_case.rb +33 -0
  24. data/lib/draper/version.rb +1 -1
  25. data/lib/draper/view_helpers.rb +4 -3
  26. data/lib/generators/decorator/templates/decorator.rb +7 -25
  27. data/lib/generators/mini_test/decorator_generator.rb +20 -0
  28. data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
  29. data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
  30. data/lib/generators/test_unit/templates/decorator_test.rb +1 -1
  31. data/spec/draper/collection_decorator_spec.rb +25 -3
  32. data/spec/draper/decorated_association_spec.rb +18 -7
  33. data/spec/draper/decorator_spec.rb +125 -165
  34. data/spec/draper/finders_spec.rb +0 -13
  35. data/spec/dummy/app/controllers/localized_urls.rb +1 -1
  36. data/spec/dummy/app/controllers/posts_controller.rb +3 -9
  37. data/spec/dummy/app/decorators/post_decorator.rb +4 -1
  38. data/spec/dummy/config/application.rb +3 -3
  39. data/spec/dummy/config/environments/development.rb +4 -4
  40. data/spec/dummy/config/environments/test.rb +2 -2
  41. data/spec/dummy/lib/tasks/test.rake +10 -0
  42. data/spec/dummy/mini_test/mini_test_integration_test.rb +46 -0
  43. data/spec/dummy/spec/decorators/post_decorator_spec.rb +2 -2
  44. data/spec/dummy/spec/decorators/rspec_integration_spec.rb +19 -0
  45. data/spec/dummy/spec/mailers/post_mailer_spec.rb +2 -2
  46. data/spec/dummy/spec/spec_helper.rb +0 -1
  47. data/spec/generators/decorator/decorator_generator_spec.rb +43 -2
  48. data/spec/integration/integration_spec.rb +2 -2
  49. data/spec/spec_helper.rb +17 -21
  50. data/spec/support/active_record.rb +0 -13
  51. data/spec/support/dummy_app.rb +4 -3
  52. metadata +26 -23
  53. data/lib/draper/security.rb +0 -48
  54. data/lib/draper/tasks/tu.rake +0 -5
  55. data/lib/draper/test/test_unit_integration.rb +0 -18
  56. data/spec/draper/security_spec.rb +0 -158
  57. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
  58. data/spec/dummy/lib/tasks/spec.rake +0 -5
  59. data/spec/minitest-rails/spec_type_spec.rb +0 -63
@@ -15,7 +15,7 @@ describe Draper::Decorator do
15
15
  end
16
16
 
17
17
  it "raises error on invalid options" do
18
- expect { decorator_class.new(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
18
+ expect { decorator_class.new(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, /Unknown key/)
19
19
  end
20
20
  end
21
21
 
@@ -49,21 +49,29 @@ describe Draper::Decorator do
49
49
  end
50
50
  end
51
51
 
52
- it "decorates other decorators" do
53
- decorator = ProductDecorator.new(source)
54
- SpecificProductDecorator.new(decorator).source.should be decorator
55
- end
52
+ context "when decorating other decorators" do
53
+ it "redecorates" do
54
+ decorator = ProductDecorator.new(source)
55
+ SpecificProductDecorator.new(decorator).source.should be decorator
56
+ end
57
+
58
+ context "when the same decorator has been applied earlier in the chain" do
59
+ let(:decorator) { SpecificProductDecorator.new(ProductDecorator.new(Product.new)) }
60
+
61
+ it "warns" do
62
+ warning_message = nil
63
+ Object.any_instance.stub(:warn) {|message| warning_message = message }
56
64
 
57
- it "warns if target is already decorated with the same decorator class" do
58
- warning_message = nil
59
- Object.any_instance.stub(:warn) { |message| warning_message = message }
65
+ expect{ProductDecorator.new(decorator)}.to change{warning_message}
66
+ warning_message.should =~ /ProductDecorator/
67
+ warning_message.should include caller(1).first
68
+ end
60
69
 
61
- deep_decorator = SpecificProductDecorator.new(ProductDecorator.new(Product.new))
62
- expect {
63
- ProductDecorator.new(deep_decorator)
64
- }.to change { warning_message }
65
- warning_message.should =~ /ProductDecorator/
66
- warning_message.should include caller(1).first
70
+ it "redecorates" do
71
+ Object.any_instance.stub(:warn)
72
+ ProductDecorator.new(decorator).source.should be decorator
73
+ end
74
+ end
67
75
  end
68
76
  end
69
77
 
@@ -76,7 +84,6 @@ describe Draper::Decorator do
76
84
  end
77
85
 
78
86
  describe ".decorate_collection" do
79
- subject { ProductDecorator.decorate_collection(source) }
80
87
  let(:source) { [Product.new, Widget.new] }
81
88
 
82
89
  describe "options validation" do
@@ -88,17 +95,34 @@ describe Draper::Decorator do
88
95
  end
89
96
 
90
97
  it "raises error on invalid options" do
91
- expect { ProductDecorator.decorate_collection(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
98
+ expect { ProductDecorator.decorate_collection(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, /Unknown key/)
92
99
  end
93
100
  end
94
101
 
95
- it "returns a collection decorator" do
96
- subject.should be_a Draper::CollectionDecorator
97
- subject.should == source
102
+ context "when a custom collection decorator does not exist" do
103
+ subject { WidgetDecorator.decorate_collection(source) }
104
+
105
+ it "returns a regular collection decorator" do
106
+ subject.should be_a Draper::CollectionDecorator
107
+ subject.should == source
108
+ end
109
+
110
+ it "uses itself as the item decorator by default" do
111
+ subject.each {|item| item.should be_a WidgetDecorator}
112
+ end
98
113
  end
99
114
 
100
- it "uses itself as the item decorator by default" do
101
- subject.each {|item| item.should be_a ProductDecorator}
115
+ context "when a custom collection decorator exists" do
116
+ subject { ProductDecorator.decorate_collection(source) }
117
+
118
+ it "returns the custom collection decorator" do
119
+ subject.should be_a ProductsDecorator
120
+ subject.should == source
121
+ end
122
+
123
+ it "uses itself as the item decorator by default" do
124
+ subject.each {|item| item.should be_a ProductDecorator}
125
+ end
102
126
  end
103
127
 
104
128
  context "with context" do
@@ -256,7 +280,7 @@ describe Draper::Decorator do
256
280
  end
257
281
 
258
282
  it "raises error on invalid options" do
259
- expect { decorator_class.decorates_association :similar_products, valid_options.merge(foo: 'bar') }.to raise_error(ArgumentError, 'Unknown key: foo')
283
+ expect { decorator_class.decorates_association :similar_products, valid_options.merge(foo: 'bar') }.to raise_error(ArgumentError, /Unknown key/)
260
284
  end
261
285
  end
262
286
 
@@ -415,101 +439,45 @@ describe Draper::Decorator do
415
439
  end
416
440
  end
417
441
 
418
- describe "#respond_to?" do
419
- let(:decorator_class) { Class.new(ProductDecorator) }
420
-
421
- it "returns true for its own methods" do
422
- subject.should respond_to :awesome_title
423
- end
424
-
425
- it "returns true for the source's methods" do
426
- subject.should respond_to :title
427
- end
428
-
429
- context "with include_private" do
430
- it "returns true for its own private methods" do
431
- subject.respond_to?(:awesome_private_title, true).should be_true
432
- end
433
-
434
- it "returns false for the source's private methods" do
435
- subject.respond_to?(:private_title, true).should be_false
436
- end
437
- end
438
-
439
- context "with method security" do
440
- it "respects allows" do
441
- subject.class.allows :hello_world
442
-
443
- subject.should respond_to :hello_world
444
- subject.should_not respond_to :goodnight_moon
445
- end
446
-
447
- it "respects denies" do
448
- subject.class.denies :goodnight_moon
449
-
450
- subject.should respond_to :hello_world
451
- subject.should_not respond_to :goodnight_moon
452
- end
453
-
454
- it "respects denies_all" do
455
- subject.class.denies_all
456
-
457
- subject.should_not respond_to :hello_world
458
- subject.should_not respond_to :goodnight_moon
459
- end
460
- end
461
- end
462
-
463
- describe ".respond_to?" do
464
- subject { Class.new(ProductDecorator) }
465
-
466
- context "without a source class" do
467
- it "returns true for its own class methods" do
468
- subject.should respond_to :my_class_method
469
- end
442
+ describe ".delegate" do
443
+ subject { Class.new(Draper::Decorator) }
470
444
 
471
- it "returns false for other class methods" do
472
- subject.should_not respond_to :sample_class_method
473
- end
445
+ it "defaults the :to option to :source" do
446
+ Draper::Decorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :source)
447
+ subject.delegate :foo, :bar
474
448
  end
475
449
 
476
- context "with a source_class" do
477
- before { subject.decorates :product }
478
-
479
- it "returns true for its own class methods" do
480
- subject.should respond_to :my_class_method
481
- end
482
-
483
- it "returns true for the source's class methods" do
484
- subject.should respond_to :sample_class_method
485
- end
450
+ it "does not overwrite the :to option if supplied" do
451
+ Draper::Decorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :baz)
452
+ subject.delegate :foo, :bar, to: :baz
486
453
  end
487
454
  end
488
455
 
489
- describe "proxying" do
490
- context "instance methods" do
491
- let(:decorator_class) { Class.new(ProductDecorator) }
456
+ describe ".delegate_all" do
457
+ let(:decorator_class) { Class.new(ProductDecorator) }
458
+ before { decorator_class.delegate_all }
492
459
 
493
- it "does not proxy methods that are defined on the decorator" do
460
+ describe "#method_missing" do
461
+ it "does not delegate methods that are defined on the decorator" do
494
462
  subject.overridable.should be :overridden
495
463
  end
496
464
 
497
- it "does not proxy methods inherited from Object" do
465
+ it "does not delegate methods inherited from Object" do
498
466
  subject.inspect.should_not be source.inspect
499
467
  end
500
468
 
501
- it "proxies missing methods that exist on the source" do
502
- source.stub(:hello_world).and_return(:proxied)
503
- subject.hello_world.should be :proxied
469
+ it "delegates missing methods that exist on the source" do
470
+ source.stub(:hello_world).and_return(:delegated)
471
+ subject.hello_world.should be :delegated
504
472
  end
505
473
 
506
- it "adds proxied methods to the decorator when they are used" do
474
+ it "adds delegated methods to the decorator when they are used" do
507
475
  subject.methods.should_not include :hello_world
508
476
  subject.hello_world
509
477
  subject.methods.should include :hello_world
510
478
  end
511
479
 
512
- it "passes blocks to proxied methods" do
480
+ it "passes blocks to delegated methods" do
513
481
  subject.block{"marker"}.should == "marker"
514
482
  end
515
483
 
@@ -517,75 +485,83 @@ describe Draper::Decorator do
517
485
  Array(subject).should be_a Array
518
486
  end
519
487
 
520
- it "proxies delegated methods" do
488
+ it "delegates already-delegated methods" do
521
489
  subject.delegated_method.should == "Yay, delegation"
522
490
  end
523
491
 
524
- it "does not proxy private methods" do
492
+ it "does not delegate private methods" do
525
493
  expect{subject.private_title}.to raise_error NoMethodError
526
494
  end
495
+ end
527
496
 
528
- context "with method security" do
529
- it "respects allows" do
530
- source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
531
- subject.class.allows :hello_world
497
+ context ".method_missing" do
498
+ subject { decorator_class }
532
499
 
533
- subject.hello_world.should be :proxied
534
- expect{subject.goodnight_moon}.to raise_error NameError
500
+ context "without a source class" do
501
+ it "raises a NoMethodError on missing methods" do
502
+ expect{subject.hello_world}.to raise_error NoMethodError
535
503
  end
504
+ end
536
505
 
537
- it "respects denies" do
538
- source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
539
- subject.class.denies :goodnight_moon
506
+ context "with a source class" do
507
+ let(:source_class) { Product }
508
+ before { subject.decorates source_class }
540
509
 
541
- subject.hello_world.should be :proxied
542
- expect{subject.goodnight_moon}.to raise_error NameError
510
+ it "does not delegate methods that are defined on the decorator" do
511
+ subject.overridable.should be :overridden
543
512
  end
544
513
 
545
- it "respects denies_all" do
546
- source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
547
- subject.class.denies_all
548
-
549
- expect{subject.hello_world}.to raise_error NameError
550
- expect{subject.goodnight_moon}.to raise_error NameError
514
+ it "delegates missing methods that exist on the source" do
515
+ source_class.stub(:hello_world).and_return(:delegated)
516
+ subject.hello_world.should be :delegated
551
517
  end
552
518
  end
553
519
  end
554
520
 
555
- context "class methods" do
556
- subject { Class.new(ProductDecorator) }
557
- let(:source_class) { Product }
558
- before { subject.decorates source_class }
521
+ describe "#respond_to?" do
522
+ it "returns true for its own methods" do
523
+ subject.should respond_to :awesome_title
524
+ end
559
525
 
560
- it "does not proxy methods that are defined on the decorator" do
561
- subject.overridable.should be :overridden
526
+ it "returns true for the source's methods" do
527
+ subject.should respond_to :title
562
528
  end
563
529
 
564
- it "proxies missing methods that exist on the source" do
565
- source_class.stub(:hello_world).and_return(:proxied)
566
- subject.hello_world.should be :proxied
530
+ context "with include_private" do
531
+ it "returns true for its own private methods" do
532
+ subject.respond_to?(:awesome_private_title, true).should be_true
533
+ end
534
+
535
+ it "returns false for the source's private methods" do
536
+ subject.respond_to?(:private_title, true).should be_false
537
+ end
567
538
  end
568
539
  end
569
- end
570
540
 
571
- describe "method security" do
572
- subject(:decorator_class) { Draper::Decorator }
573
- let(:security) { stub }
574
- before { decorator_class.stub(:security).and_return(security) }
541
+ describe ".respond_to?" do
542
+ subject { decorator_class }
575
543
 
576
- it "delegates .denies to Draper::Security" do
577
- security.should_receive(:denies).with(:foo, :bar)
578
- decorator_class.denies :foo, :bar
579
- end
544
+ context "without a source class" do
545
+ it "returns true for its own class methods" do
546
+ subject.should respond_to :my_class_method
547
+ end
580
548
 
581
- it "delegates .denies_all to Draper::Security" do
582
- security.should_receive(:denies_all)
583
- decorator_class.denies_all
584
- end
549
+ it "returns false for other class methods" do
550
+ subject.should_not respond_to :sample_class_method
551
+ end
552
+ end
553
+
554
+ context "with a source_class" do
555
+ before { subject.decorates :product }
585
556
 
586
- it "delegates .allows to Draper::Security" do
587
- security.should_receive(:allows).with(:foo, :bar)
588
- decorator_class.allows :foo, :bar
557
+ it "returns true for its own class methods" do
558
+ subject.should respond_to :my_class_method
559
+ end
560
+
561
+ it "returns true for the source's class methods" do
562
+ subject.should respond_to :sample_class_method
563
+ end
564
+ end
589
565
  end
590
566
  end
591
567
 
@@ -623,6 +599,14 @@ describe Draper::Decorator do
623
599
  subject.is_a?(subject.class).should be_true
624
600
  end
625
601
 
602
+ it "pretends to be an instance of the source class" do
603
+ subject.instance_of?(source.class).should be_true
604
+ end
605
+
606
+ it "is still an instance of its own class" do
607
+ subject.instance_of?(subject.class).should be_true
608
+ end
609
+
626
610
  describe ".decorates_finders" do
627
611
  it "extends the Finders module" do
628
612
  ProductDecorator.should be_a_kind_of Draper::Finders
@@ -637,28 +621,4 @@ describe Draper::Decorator do
637
621
  end
638
622
  end
639
623
 
640
- describe ".method_missing" do
641
- context "when called on an anonymous decorator" do
642
- subject { ->{ Class.new(Draper::Decorator).fizzbuzz } }
643
- it { should raise_error NoMethodError }
644
- end
645
-
646
- context "when called on an uninferrable decorator" do
647
- subject { ->{ SpecificProductDecorator.fizzbuzz } }
648
- it { should raise_error NoMethodError }
649
- end
650
-
651
- context "when called on an inferrable decorator" do
652
- context "for a method known to the inferred class" do
653
- subject { ->{ ProductDecorator.model_name } }
654
- it { should_not raise_error }
655
- end
656
-
657
- context "for a method unknown to the inferred class" do
658
- subject { ->{ ProductDecorator.fizzbuzz } }
659
- it { should raise_error NoMethodError }
660
- end
661
- end
662
- end
663
-
664
624
  end
@@ -129,17 +129,4 @@ describe Draper::Finders do
129
129
  decorator.context.should == {some: 'context'}
130
130
  end
131
131
  end
132
-
133
- describe "scopes" do
134
- it "proxies to the model class" do
135
- Product.should_receive(:where).with({name: "apples"})
136
- ProductDecorator.where(name: "apples")
137
- end
138
-
139
- it "doesn't decorate the result" do
140
- found = [Product.new]
141
- Product.stub(:where).and_return(found)
142
- ProductDecorator.where(name: "apples").should be found
143
- end
144
- end
145
132
  end
@@ -1,5 +1,5 @@
1
1
  module LocalizedUrls
2
2
  def default_url_options(options = {})
3
- {locale: I18n.locale, host: "www.example.com", port: nil}
3
+ {locale: I18n.locale, host: "www.example.com", port: 12345}
4
4
  end
5
5
  end
@@ -1,17 +1,11 @@
1
1
  class PostsController < ApplicationController
2
2
  def show
3
- fetch_post
3
+ @post = Post.find(params[:id]).decorate
4
4
  end
5
5
 
6
6
  def mail
7
- fetch_post
8
- email = PostMailer.decorated_email(@post).deliver
7
+ post = Post.find(params[:id])
8
+ email = PostMailer.decorated_email(post).deliver
9
9
  render text: email.body
10
10
  end
11
-
12
- private
13
-
14
- def fetch_post
15
- @post = Post.find(params[:id]).decorate
16
- end
17
11
  end
@@ -1,6 +1,9 @@
1
1
  class PostDecorator < Draper::Decorator
2
+ # need to delegate id and new_record? for AR::Base#== (Rails 3.0 only)
3
+ delegate :id, :new_record?
4
+
2
5
  def posted_date
3
- if created_at.to_date == DateTime.now.utc.to_date
6
+ if source.created_at.to_date == DateTime.now.utc.to_date
4
7
  "Today"
5
8
  else
6
9
  "Not Today"