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.
- 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"
|