draper 3.0.0 → 4.0.2
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.
- 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
|
|