draper 3.0.0 → 4.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +55 -0
- data/CHANGELOG.md +46 -0
- data/Gemfile +11 -2
- data/README.md +43 -5
- data/bin/bundle +114 -0
- data/bin/rake +29 -0
- data/draper.gemspec +9 -8
- data/lib/draper.rb +3 -0
- data/lib/draper/automatic_delegation.rb +4 -2
- data/lib/draper/collection_decorator.rb +4 -2
- data/lib/draper/compatibility/global_id.rb +22 -0
- data/lib/draper/configuration.rb +8 -0
- data/lib/draper/decoratable.rb +6 -7
- data/lib/draper/decorated_association.rb +0 -2
- data/lib/draper/decorator.rb +19 -13
- data/lib/draper/delegation.rb +1 -1
- data/lib/draper/finders.rb +0 -1
- data/lib/draper/helper_proxy.rb +2 -2
- data/lib/draper/lazy_helpers.rb +1 -3
- data/lib/draper/query_methods.rb +23 -0
- data/lib/draper/query_methods/load_strategy.rb +21 -0
- data/lib/draper/test_case.rb +3 -3
- data/lib/draper/undecorate.rb +1 -1
- data/lib/draper/version.rb +1 -1
- data/lib/draper/view_context/build_strategy.rb +1 -3
- data/lib/draper/view_helpers.rb +5 -5
- data/lib/generators/draper/install_generator.rb +1 -1
- data/lib/generators/mini_test/decorator_generator.rb +1 -1
- data/lib/generators/rails/decorator_generator.rb +1 -1
- data/lib/generators/rspec/decorator_generator.rb +6 -4
- data/lib/generators/test_unit/decorator_generator.rb +7 -4
- data/spec/draper/collection_decorator_spec.rb +5 -6
- data/spec/draper/configuration_spec.rb +32 -8
- data/spec/draper/decoratable_spec.rb +24 -4
- data/spec/draper/decorated_association_spec.rb +0 -2
- data/spec/draper/decorator_spec.rb +36 -3
- data/spec/draper/draper_spec.rb +1 -0
- data/spec/draper/factory_spec.rb +0 -4
- data/spec/draper/query_methods/load_strategy_spec.rb +26 -0
- data/spec/draper/query_methods_spec.rb +70 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/controllers/application_controller.rb +2 -0
- data/spec/dummy/app/jobs/publish_post_job.rb +7 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/post.rb +1 -1
- data/spec/dummy/config/application.rb +0 -3
- data/spec/dummy/config/environments/development.rb +2 -0
- data/spec/dummy/config/environments/production.rb +2 -0
- data/spec/dummy/config/environments/test.rb +4 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/storage.yml +7 -0
- data/spec/dummy/db/migrate/20121019115657_create_posts.rb +1 -1
- data/spec/dummy/spec/jobs/publish_post_job_spec.rb +9 -0
- data/spec/dummy/spec/models/application_spec.rb +7 -0
- data/spec/dummy/spec/models/post_spec.rb +11 -2
- data/spec/generators/controller/controller_generator_spec.rb +1 -0
- data/spec/generators/decorator/decorator_generator_spec.rb +1 -1
- data/spec/integration/integration_spec.rb +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +72 -20
- data/.travis.yml +0 -9
@@ -1,9 +1,12 @@
|
|
1
1
|
module TestUnit
|
2
|
-
|
3
|
-
|
2
|
+
module Generators
|
3
|
+
class DecoratorGenerator < ::Rails::Generators::NamedBase
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
5
|
+
check_class_collision suffix: "DecoratorTest"
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
+
def create_test_file
|
8
|
+
template 'decorator_test.rb', File.join('test/decorators', class_path, "#{singular_name}_decorator_test.rb")
|
9
|
+
end
|
7
10
|
end
|
8
11
|
end
|
9
12
|
end
|
@@ -7,7 +7,6 @@ module Draper
|
|
7
7
|
|
8
8
|
describe "#initialize" do
|
9
9
|
describe "options validation" do
|
10
|
-
|
11
10
|
it "does not raise error on valid options" do
|
12
11
|
valid_options = {with: Decorator, context: {}}
|
13
12
|
expect{CollectionDecorator.new([], valid_options)}.not_to raise_error
|
@@ -61,10 +60,11 @@ module Draper
|
|
61
60
|
|
62
61
|
context "when the collection has not yet been decorated" do
|
63
62
|
it "does not trigger decoration" do
|
64
|
-
|
63
|
+
decorated = CollectionDecorator.new([]).tap(&:to_a)
|
64
|
+
undecorated = CollectionDecorator.new([])
|
65
65
|
|
66
|
-
expect(
|
67
|
-
|
66
|
+
expect(decorated.instance_variable_defined?(:@decorated_collection)).to be_truthy
|
67
|
+
expect(undecorated.instance_variable_defined?(:@decorated_collection)).to be_falsy
|
68
68
|
end
|
69
69
|
|
70
70
|
it "sets context after decoration is triggered" do
|
@@ -218,7 +218,7 @@ module Draper
|
|
218
218
|
it "uses the custom class name" do
|
219
219
|
decorator = ProductsDecorator.new([])
|
220
220
|
|
221
|
-
expect(decorator.to_s).to match
|
221
|
+
expect(decorator.to_s).to match(/ProductsDecorator/)
|
222
222
|
end
|
223
223
|
end
|
224
224
|
end
|
@@ -287,6 +287,5 @@ module Draper
|
|
287
287
|
expect(decorator.replace([:foo, :bar])).to be decorator
|
288
288
|
end
|
289
289
|
end
|
290
|
-
|
291
290
|
end
|
292
291
|
end
|
@@ -6,20 +6,44 @@ module Draper
|
|
6
6
|
Draper.configure { |config| expect(config).to be Draper }
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
describe '#default_controller' do
|
10
|
+
it 'defaults default_controller to ApplicationController' do
|
11
|
+
expect(Draper.default_controller).to be ApplicationController
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'allows customizing default_controller through configure' do
|
15
|
+
default = Draper.default_controller
|
16
|
+
|
17
|
+
Draper.configure do |config|
|
18
|
+
config.default_controller = CustomController
|
19
|
+
end
|
20
|
+
|
21
|
+
expect(Draper.default_controller).to be CustomController
|
22
|
+
|
23
|
+
Draper.default_controller = default
|
24
|
+
end
|
11
25
|
end
|
12
26
|
|
13
|
-
|
14
|
-
default
|
27
|
+
describe '#default_query_methods_strategy' do
|
28
|
+
let!(:default) { Draper.default_query_methods_strategy }
|
29
|
+
|
30
|
+
subject { Draper.default_query_methods_strategy }
|
15
31
|
|
16
|
-
|
17
|
-
|
32
|
+
context 'when there is no custom strategy' do
|
33
|
+
it { is_expected.to eq(:active_record) }
|
18
34
|
end
|
19
35
|
|
20
|
-
|
36
|
+
context 'when using a custom strategy' do
|
37
|
+
before do
|
38
|
+
Draper.configure do |config|
|
39
|
+
config.default_query_methods_strategy = :mongoid
|
40
|
+
end
|
41
|
+
end
|
21
42
|
|
22
|
-
|
43
|
+
after { Draper.default_query_methods_strategy = default }
|
44
|
+
|
45
|
+
it { is_expected.to eq(:mongoid) }
|
46
|
+
end
|
23
47
|
end
|
24
48
|
end
|
25
49
|
end
|
@@ -3,7 +3,6 @@ require 'support/shared_examples/decoratable_equality'
|
|
3
3
|
|
4
4
|
module Draper
|
5
5
|
describe Decoratable do
|
6
|
-
|
7
6
|
describe "#decorate" do
|
8
7
|
it "returns a decorator for self" do
|
9
8
|
product = Product.new
|
@@ -11,7 +10,7 @@ module Draper
|
|
11
10
|
|
12
11
|
expect(decorator).to be_a ProductDecorator
|
13
12
|
expect(decorator.object).to be product
|
14
|
-
|
13
|
+
end
|
15
14
|
|
16
15
|
it "accepts context" do
|
17
16
|
context = {some: "context"}
|
@@ -73,6 +72,16 @@ module Draper
|
|
73
72
|
expect(Product).to receive(:decorator_class).and_return(:some_decorator)
|
74
73
|
expect(product.decorator_class).to be :some_decorator
|
75
74
|
end
|
75
|
+
|
76
|
+
it "specifies the class that #decorator_class was first called on (superclass)" do
|
77
|
+
person = Person.new
|
78
|
+
expect { person.decorator_class }.to raise_error(Draper::UninferrableDecoratorError, 'Could not infer a decorator for Person.')
|
79
|
+
end
|
80
|
+
|
81
|
+
it "specifies the class that #decorator_class was first called on (subclass)" do
|
82
|
+
child = Child.new
|
83
|
+
expect { child.decorator_class }.to raise_error(Draper::UninferrableDecoratorError, 'Could not infer a decorator for Child.')
|
84
|
+
end
|
76
85
|
end
|
77
86
|
|
78
87
|
describe "#==" do
|
@@ -205,11 +214,22 @@ module Draper
|
|
205
214
|
|
206
215
|
context "when an unrelated NameError is thrown" do
|
207
216
|
it "re-raises that error" do
|
208
|
-
|
217
|
+
# Not related to safe_constantize behavior, we just want to raise a NameError inside the function
|
218
|
+
allow_any_instance_of(String).to receive(:safe_constantize) { Draper::Base }
|
209
219
|
expect{Product.decorator_class}.to raise_error NameError, /Draper::Base/
|
210
220
|
end
|
211
221
|
end
|
212
|
-
end
|
213
222
|
|
223
|
+
context "when an anonymous class is given" do
|
224
|
+
it "infers the decorator from a superclass" do
|
225
|
+
anonymous_class = Class.new(Product) do
|
226
|
+
def self.name
|
227
|
+
to_s
|
228
|
+
end
|
229
|
+
end
|
230
|
+
expect(anonymous_class.decorator_class).to be ProductDecorator
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
214
234
|
end
|
215
235
|
end
|
@@ -2,7 +2,6 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Draper
|
4
4
|
describe DecoratedAssociation do
|
5
|
-
|
6
5
|
describe "#initialize" do
|
7
6
|
it "accepts valid options" do
|
8
7
|
valid_options = {with: Decorator, scope: :foo, context: {}}
|
@@ -79,6 +78,5 @@ module Draper
|
|
79
78
|
end
|
80
79
|
end
|
81
80
|
end
|
82
|
-
|
83
81
|
end
|
84
82
|
end
|
@@ -202,7 +202,8 @@ module Draper
|
|
202
202
|
|
203
203
|
context "when an unrelated NameError is thrown" do
|
204
204
|
it "re-raises that error" do
|
205
|
-
|
205
|
+
# Not related to safe_constantize behavior, we just want to raise a NameError inside the function
|
206
|
+
allow_any_instance_of(String).to receive(:safe_constantize) { SomethingThatDoesntExist }
|
206
207
|
expect{ProductDecorator.object_class}.to raise_error NameError, /SomethingThatDoesntExist/
|
207
208
|
end
|
208
209
|
end
|
@@ -225,7 +226,7 @@ module Draper
|
|
225
226
|
|
226
227
|
describe '.collection_decorator_class' do
|
227
228
|
it 'defaults to CollectionDecorator' do
|
228
|
-
allow_any_instance_of(String).to receive(:
|
229
|
+
allow_any_instance_of(String).to receive(:safe_constantize) { nil }
|
229
230
|
expect(ProductDecorator.collection_decorator_class).to be Draper::CollectionDecorator
|
230
231
|
end
|
231
232
|
|
@@ -438,7 +439,7 @@ module Draper
|
|
438
439
|
it "returns a detailed description of the decorator" do
|
439
440
|
decorator = ProductDecorator.new(double)
|
440
441
|
|
441
|
-
expect(decorator.inspect).to match
|
442
|
+
expect(decorator.inspect).to match(/#<ProductDecorator:0x\h+ .+>/)
|
442
443
|
end
|
443
444
|
|
444
445
|
it "includes the object" do
|
@@ -663,6 +664,38 @@ module Draper
|
|
663
664
|
expect{decorator.hello_world}.to raise_error NoMethodError
|
664
665
|
expect(decorator.methods).not_to include :hello_world
|
665
666
|
end
|
667
|
+
|
668
|
+
context 'when decorator overrides a public method defined on the object with a private' do
|
669
|
+
let(:decorator_class) do
|
670
|
+
Class.new(Decorator) do
|
671
|
+
private
|
672
|
+
|
673
|
+
def hello_world
|
674
|
+
'hello world'
|
675
|
+
end
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
let(:object) { Class.new { def hello_world; end }.new }
|
680
|
+
|
681
|
+
it 'does not delegate the public method defined on the object' do
|
682
|
+
decorator = decorator_class.new(object)
|
683
|
+
|
684
|
+
expect{ decorator.hello_world }.to raise_error NoMethodError
|
685
|
+
end
|
686
|
+
end
|
687
|
+
|
688
|
+
context 'when delegated method has the same name as private method defined on another object' do
|
689
|
+
let(:decorator_class) { Class.new(Decorator) }
|
690
|
+
let(:object) { Class.new { def print; end }.new }
|
691
|
+
|
692
|
+
it 'delegates the public method defined on the object' do
|
693
|
+
decorator = decorator_class.new(object)
|
694
|
+
|
695
|
+
# `print` private method is defined on `Object`
|
696
|
+
expect{ decorator.print }.not_to raise_error
|
697
|
+
end
|
698
|
+
end
|
666
699
|
end
|
667
700
|
|
668
701
|
context ".method_missing" do
|
data/spec/draper/draper_spec.rb
CHANGED
data/spec/draper/factory_spec.rb
CHANGED
@@ -2,7 +2,6 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Draper
|
4
4
|
describe Factory do
|
5
|
-
|
6
5
|
describe "#initialize" do
|
7
6
|
it "accepts valid options" do
|
8
7
|
valid_options = {with: Decorator, context: {foo: "bar"}}
|
@@ -88,11 +87,9 @@ module Draper
|
|
88
87
|
end
|
89
88
|
end
|
90
89
|
end
|
91
|
-
|
92
90
|
end
|
93
91
|
|
94
92
|
describe Factory::Worker do
|
95
|
-
|
96
93
|
describe "#call" do
|
97
94
|
it "calls the decorator method" do
|
98
95
|
object = double
|
@@ -246,6 +243,5 @@ module Draper
|
|
246
243
|
end
|
247
244
|
end
|
248
245
|
end
|
249
|
-
|
250
246
|
end
|
251
247
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
module Draper
|
5
|
+
module QueryMethods
|
6
|
+
describe LoadStrategy do
|
7
|
+
describe '#new' do
|
8
|
+
subject { described_class.new(:active_record) }
|
9
|
+
|
10
|
+
it { is_expected.to be_an_instance_of(LoadStrategy::ActiveRecord) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe LoadStrategy::ActiveRecord do
|
15
|
+
describe '#allowed?' do
|
16
|
+
it 'checks whether or not ActiveRecord::Relation::VALUE_METHODS has the given method' do
|
17
|
+
allow(::ActiveRecord::Relation::VALUE_METHODS).to receive(:include?)
|
18
|
+
|
19
|
+
described_class.new.allowed? :foo
|
20
|
+
|
21
|
+
expect(::ActiveRecord::Relation::VALUE_METHODS).to have_received(:include?).with(:foo)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../dummy/app/decorators/post_decorator'
|
3
|
+
|
4
|
+
Post = Struct.new(:id) { }
|
5
|
+
|
6
|
+
module Draper
|
7
|
+
describe QueryMethods do
|
8
|
+
let(:fake_strategy) { instance_double(QueryMethods::LoadStrategy::ActiveRecord) }
|
9
|
+
|
10
|
+
before { allow(QueryMethods::LoadStrategy).to receive(:new).and_return(fake_strategy) }
|
11
|
+
|
12
|
+
describe '#method_missing' do
|
13
|
+
let(:collection) { [ Post.new, Post.new ] }
|
14
|
+
let(:collection_context) { { user: 'foo' } }
|
15
|
+
let(:collection_decorator) { PostDecorator.decorate_collection(collection, context: collection_context) }
|
16
|
+
|
17
|
+
context 'when strategy allows collection to call the method' do
|
18
|
+
let(:results) { spy(:results) }
|
19
|
+
|
20
|
+
before do
|
21
|
+
allow(fake_strategy).to receive(:allowed?).with(:some_query_method).and_return(true)
|
22
|
+
allow(collection).to receive(:send).with(:some_query_method).and_return(results)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'calls the method on the collection and decorate it results' do
|
26
|
+
collection_decorator.some_query_method
|
27
|
+
|
28
|
+
expect(results).to have_received(:decorate)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'calls the method on the collection and keeps the decoration options' do
|
32
|
+
collection_decorator.some_query_method
|
33
|
+
|
34
|
+
expect(results).to have_received(:decorate).with({ context: collection_context, with: PostDecorator })
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when strategy does not allow collection to call the method' do
|
39
|
+
before { allow(fake_strategy).to receive(:allowed?).with(:some_query_method).and_return(false) }
|
40
|
+
|
41
|
+
it 'raises NoMethodError' do
|
42
|
+
expect { collection_decorator.some_query_method }.to raise_exception(NoMethodError)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#respond_to?" do
|
48
|
+
let(:collection) { [ Post.new, Post.new ] }
|
49
|
+
let(:collection_decorator) { PostDecorator.decorate_collection(collection) }
|
50
|
+
|
51
|
+
subject { collection_decorator.respond_to?(:some_query_method) }
|
52
|
+
|
53
|
+
context 'when strategy allows collection to call the method' do
|
54
|
+
before do
|
55
|
+
allow(fake_strategy).to receive(:allowed?).with(:some_query_method).and_return(true)
|
56
|
+
end
|
57
|
+
|
58
|
+
it { is_expected.to eq(true) }
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when strategy does not allow collection to call the method' do
|
62
|
+
before do
|
63
|
+
allow(fake_strategy).to receive(:allowed?).with(:some_query_method).and_return(false)
|
64
|
+
end
|
65
|
+
|
66
|
+
it { is_expected.to eq(false) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -38,9 +38,6 @@ module Dummy
|
|
38
38
|
# Configure the default encoding used in templates for Ruby 1.9.
|
39
39
|
config.encoding = "utf-8"
|
40
40
|
|
41
|
-
# Configure sensitive parameters which will be filtered from the log file.
|
42
|
-
config.filter_parameters += [:password]
|
43
|
-
|
44
41
|
# Enable escaping HTML in JSON.
|
45
42
|
config.active_support.escape_html_entities_in_json = true
|
46
43
|
|