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.
- data/.travis.yml +6 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +20 -0
- data/Gemfile +11 -0
- data/README.md +14 -17
- data/Rakefile +5 -3
- data/draper.gemspec +2 -2
- data/lib/draper.rb +2 -1
- data/lib/draper/automatic_delegation.rb +50 -0
- data/lib/draper/collection_decorator.rb +26 -7
- data/lib/draper/decoratable.rb +71 -32
- data/lib/draper/decorated_association.rb +11 -7
- data/lib/draper/decorator.rb +114 -148
- data/lib/draper/delegation.rb +13 -0
- data/lib/draper/finders.rb +9 -6
- data/lib/draper/helper_proxy.rb +4 -3
- data/lib/draper/lazy_helpers.rb +10 -6
- data/lib/draper/railtie.rb +5 -4
- data/lib/draper/tasks/test.rake +22 -0
- data/lib/draper/test/devise_helper.rb +34 -0
- data/lib/draper/test/minitest_integration.rb +2 -3
- data/lib/draper/test/rspec_integration.rb +4 -59
- data/lib/draper/test_case.rb +33 -0
- data/lib/draper/version.rb +1 -1
- data/lib/draper/view_helpers.rb +4 -3
- data/lib/generators/decorator/templates/decorator.rb +7 -25
- data/lib/generators/mini_test/decorator_generator.rb +20 -0
- data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
- data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
- data/lib/generators/test_unit/templates/decorator_test.rb +1 -1
- data/spec/draper/collection_decorator_spec.rb +25 -3
- data/spec/draper/decorated_association_spec.rb +18 -7
- data/spec/draper/decorator_spec.rb +125 -165
- data/spec/draper/finders_spec.rb +0 -13
- data/spec/dummy/app/controllers/localized_urls.rb +1 -1
- data/spec/dummy/app/controllers/posts_controller.rb +3 -9
- data/spec/dummy/app/decorators/post_decorator.rb +4 -1
- data/spec/dummy/config/application.rb +3 -3
- data/spec/dummy/config/environments/development.rb +4 -4
- data/spec/dummy/config/environments/test.rb +2 -2
- data/spec/dummy/lib/tasks/test.rake +10 -0
- data/spec/dummy/mini_test/mini_test_integration_test.rb +46 -0
- data/spec/dummy/spec/decorators/post_decorator_spec.rb +2 -2
- data/spec/dummy/spec/decorators/rspec_integration_spec.rb +19 -0
- data/spec/dummy/spec/mailers/post_mailer_spec.rb +2 -2
- data/spec/dummy/spec/spec_helper.rb +0 -1
- data/spec/generators/decorator/decorator_generator_spec.rb +43 -2
- data/spec/integration/integration_spec.rb +2 -2
- data/spec/spec_helper.rb +17 -21
- data/spec/support/active_record.rb +0 -13
- data/spec/support/dummy_app.rb +4 -3
- metadata +26 -23
- data/lib/draper/security.rb +0 -48
- data/lib/draper/tasks/tu.rake +0 -5
- data/lib/draper/test/test_unit_integration.rb +0 -18
- data/spec/draper/security_spec.rb +0 -158
- data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/spec/dummy/lib/tasks/spec.rake +0 -5
- 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,
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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,
|
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
|
-
|
96
|
-
subject.
|
97
|
-
|
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
|
-
|
101
|
-
subject
|
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,
|
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 "
|
419
|
-
|
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
|
-
|
472
|
-
|
473
|
-
|
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
|
-
|
477
|
-
|
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 "
|
490
|
-
|
491
|
-
|
456
|
+
describe ".delegate_all" do
|
457
|
+
let(:decorator_class) { Class.new(ProductDecorator) }
|
458
|
+
before { decorator_class.delegate_all }
|
492
459
|
|
493
|
-
|
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
|
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 "
|
502
|
-
source.stub(:hello_world).and_return(:
|
503
|
-
subject.hello_world.should be :
|
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
|
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
|
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 "
|
488
|
+
it "delegates already-delegated methods" do
|
521
489
|
subject.delegated_method.should == "Yay, delegation"
|
522
490
|
end
|
523
491
|
|
524
|
-
it "does not
|
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
|
-
|
529
|
-
|
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
|
-
|
534
|
-
|
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
|
-
|
538
|
-
|
539
|
-
|
506
|
+
context "with a source class" do
|
507
|
+
let(:source_class) { Product }
|
508
|
+
before { subject.decorates source_class }
|
540
509
|
|
541
|
-
|
542
|
-
|
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 "
|
546
|
-
|
547
|
-
subject.
|
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
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
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 "
|
561
|
-
subject.
|
526
|
+
it "returns true for the source's methods" do
|
527
|
+
subject.should respond_to :title
|
562
528
|
end
|
563
529
|
|
564
|
-
|
565
|
-
|
566
|
-
|
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
|
-
|
572
|
-
|
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
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
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
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
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
|
-
|
587
|
-
|
588
|
-
|
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
|
data/spec/draper/finders_spec.rb
CHANGED
@@ -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,17 +1,11 @@
|
|
1
1
|
class PostsController < ApplicationController
|
2
2
|
def show
|
3
|
-
|
3
|
+
@post = Post.find(params[:id]).decorate
|
4
4
|
end
|
5
5
|
|
6
6
|
def mail
|
7
|
-
|
8
|
-
email = PostMailer.decorated_email(
|
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"
|