draper 1.0.0.beta6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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"