draper 2.1.0 → 3.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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +16 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +24 -0
- data/.gitignore +3 -1
- data/.rubocop.yml +11 -0
- data/.travis.yml +3 -7
- data/CHANGELOG.md +26 -0
- data/Gemfile +4 -5
- data/Guardfile +5 -5
- data/README.md +61 -11
- data/Rakefile +1 -1
- data/draper.gemspec +12 -10
- data/lib/draper.rb +8 -3
- data/lib/draper/automatic_delegation.rb +5 -3
- data/lib/draper/collection_decorator.rb +1 -11
- data/lib/draper/compatibility/api_only.rb +23 -0
- data/lib/draper/configuration.rb +15 -0
- data/lib/draper/decoratable.rb +3 -4
- data/lib/draper/decorator.rb +4 -24
- data/lib/draper/finders.rb +0 -0
- data/lib/draper/helper_proxy.rb +1 -8
- data/lib/draper/railtie.rb +12 -21
- data/lib/draper/tasks/test.rake +2 -15
- data/lib/draper/test/devise_helper.rb +1 -8
- data/lib/draper/test/minitest_integration.rb +0 -0
- data/lib/draper/test/rspec_integration.rb +1 -5
- data/lib/draper/test_case.rb +4 -8
- data/lib/draper/undecorate.rb +8 -0
- data/lib/draper/version.rb +1 -1
- data/lib/draper/view_context.rb +3 -19
- data/lib/draper/view_context/build_strategy.rb +11 -2
- data/lib/generators/controller_override.rb +2 -2
- data/lib/generators/draper/install_generator.rb +14 -0
- data/lib/generators/draper/templates/application_decorator.rb +8 -0
- data/lib/generators/mini_test/decorator_generator.rb +1 -1
- data/lib/generators/rails/decorator_generator.rb +1 -8
- data/lib/generators/rspec/templates/decorator_spec.rb +2 -2
- data/spec/draper/collection_decorator_spec.rb +11 -26
- data/spec/draper/configuration_spec.rb +25 -0
- data/spec/draper/decoratable_spec.rb +29 -16
- data/spec/draper/decorated_association_spec.rb +9 -9
- data/spec/draper/decorates_assigned_spec.rb +6 -6
- data/spec/draper/decorator_spec.rb +112 -89
- data/spec/draper/draper_spec.rb +24 -0
- data/spec/draper/factory_spec.rb +26 -26
- data/spec/draper/finders_spec.rb +21 -21
- data/spec/draper/helper_proxy_spec.rb +3 -3
- data/spec/draper/lazy_helpers_spec.rb +2 -2
- data/spec/draper/undecorate_chain_spec.rb +20 -0
- data/spec/draper/view_context/build_strategy_spec.rb +26 -10
- data/spec/draper/view_context_spec.rb +49 -21
- data/spec/dummy/app/controllers/base_controller.rb +4 -0
- data/spec/dummy/app/controllers/posts_controller.rb +2 -2
- data/spec/dummy/app/decorators/post_decorator.rb +0 -0
- data/spec/dummy/app/views/posts/_post.html.erb +8 -6
- data/spec/dummy/config/boot.rb +1 -1
- data/spec/dummy/config/initializers/draper.rb +3 -0
- data/spec/dummy/config/mongoid.yml +104 -41
- data/spec/dummy/db/schema.rb +4 -4
- data/spec/dummy/fast_spec/post_decorator_spec.rb +1 -1
- data/spec/dummy/lib/tasks/test.rake +1 -1
- data/spec/dummy/spec/decorators/active_model_serializers_spec.rb +4 -8
- data/spec/dummy/spec/decorators/devise_spec.rb +0 -9
- data/spec/dummy/spec/decorators/post_decorator_spec.rb +2 -4
- data/spec/dummy/spec/mailers/post_mailer_spec.rb +0 -8
- data/spec/dummy/spec/shared_examples/decoratable.rb +0 -2
- data/spec/dummy/test/decorators/minitest/devise_test.rb +0 -9
- data/spec/dummy/test/decorators/minitest/view_context_test.rb +3 -3
- data/spec/dummy/test/decorators/test_unit/devise_test.rb +0 -9
- data/spec/dummy/test/decorators/test_unit/view_context_test.rb +1 -1
- data/spec/generators/controller/controller_generator_spec.rb +3 -3
- data/spec/generators/decorator/decorator_generator_spec.rb +14 -12
- data/spec/generators/install/install_generator_spec.rb +19 -0
- data/spec/integration/integration_spec.rb +11 -8
- data/spec/performance/benchmark.rb +1 -1
- data/spec/spec_helper.rb +4 -4
- data/spec/support/matchers/have_text.rb +2 -2
- data/spec/support/shared_examples/view_helpers.rb +8 -8
- metadata +71 -29
- data/gemfiles/4.0.gemfile +0 -3
- data/gemfiles/4.1.gemfile +0 -3
- data/gemfiles/4.2.gemfile +0 -3
- data/spec/dummy/app/controllers/application_controller.rb +0 -4
@@ -7,7 +7,7 @@ module MiniTest
|
|
7
7
|
File.expand_path('../templates', __FILE__)
|
8
8
|
end
|
9
9
|
|
10
|
-
class_option :spec, :
|
10
|
+
class_option :spec, type: :boolean, default: false, desc: "Use MiniTest::Spec DSL"
|
11
11
|
|
12
12
|
check_class_collision suffix: "DecoratorTest"
|
13
13
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Rails
|
2
2
|
module Generators
|
3
|
-
|
3
|
+
class DecoratorGenerator < NamedBase
|
4
4
|
source_root File.expand_path("../templates", __FILE__)
|
5
5
|
check_class_collision suffix: "Decorator"
|
6
6
|
|
@@ -24,13 +24,6 @@ module Rails
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
27
|
-
|
28
|
-
# Rails 3.0.X compatibility, stolen from https://github.com/jnunemaker/mongomapper/pull/385/files#L1R32
|
29
|
-
unless methods.include?(:module_namespacing)
|
30
|
-
def module_namespacing
|
31
|
-
yield if block_given?
|
32
|
-
end
|
33
|
-
end
|
34
27
|
end
|
35
28
|
end
|
36
29
|
end
|
@@ -63,7 +63,7 @@ module Draper
|
|
63
63
|
it "does not trigger decoration" do
|
64
64
|
decorator = CollectionDecorator.new([])
|
65
65
|
|
66
|
-
decorator.
|
66
|
+
expect(decorator).not_to receive(:decorated_collection)
|
67
67
|
decorator.context = {other: "context"}
|
68
68
|
end
|
69
69
|
|
@@ -110,37 +110,22 @@ module Draper
|
|
110
110
|
protect_class ProductsDecorator
|
111
111
|
|
112
112
|
it "defaults the :to option to :object" do
|
113
|
-
Object.
|
113
|
+
expect(Object).to receive(:delegate).with(:foo, :bar, to: :object)
|
114
114
|
ProductsDecorator.delegate :foo, :bar
|
115
115
|
end
|
116
116
|
|
117
117
|
it "does not overwrite the :to option if supplied" do
|
118
|
-
Object.
|
118
|
+
expect(Object).to receive(:delegate).with(:foo, :bar, to: :baz)
|
119
119
|
ProductsDecorator.delegate :foo, :bar, to: :baz
|
120
120
|
end
|
121
121
|
end
|
122
122
|
|
123
123
|
describe "#find" do
|
124
|
-
|
125
|
-
|
126
|
-
decorator = CollectionDecorator.new([])
|
127
|
-
|
128
|
-
decorator.decorated_collection.should_receive(:find).and_return(:delegated)
|
129
|
-
expect(decorator.find{|p| p.title == "title"}).to be :delegated
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
context "without a block" do
|
134
|
-
it "decorates object.find" do
|
135
|
-
object = []
|
136
|
-
found = double(decorate: :decorated)
|
137
|
-
decorator = CollectionDecorator.new(object)
|
124
|
+
it "decorates Enumerable#find" do
|
125
|
+
decorator = CollectionDecorator.new([])
|
138
126
|
|
139
|
-
|
140
|
-
|
141
|
-
expect(decorator.find(1)).to be :decorated
|
142
|
-
end
|
143
|
-
end
|
127
|
+
expect(decorator.decorated_collection).to receive(:find).and_return(:delegated)
|
128
|
+
expect(decorator.find{|p| p.title == "title"}).to be :delegated
|
144
129
|
end
|
145
130
|
end
|
146
131
|
|
@@ -149,7 +134,7 @@ module Draper
|
|
149
134
|
it "delegates to the decorated collection" do
|
150
135
|
decorator = CollectionDecorator.new([])
|
151
136
|
|
152
|
-
decorator.decorated_collection.
|
137
|
+
expect(decorator.decorated_collection).to receive(:to_ary).and_return(:delegated)
|
153
138
|
expect(decorator.to_ary).to be :delegated
|
154
139
|
end
|
155
140
|
end
|
@@ -157,7 +142,7 @@ module Draper
|
|
157
142
|
it "delegates array methods to the decorated collection" do
|
158
143
|
decorator = CollectionDecorator.new([])
|
159
144
|
|
160
|
-
decorator.
|
145
|
+
allow(decorator).to receive_message_chain(:decorated_collection, :[]).with(42).and_return(:delegated)
|
161
146
|
expect(decorator[42]).to be :delegated
|
162
147
|
end
|
163
148
|
|
@@ -267,14 +252,14 @@ module Draper
|
|
267
252
|
describe '#kind_of?' do
|
268
253
|
it 'asks the kind of its decorated collection' do
|
269
254
|
decorator = ProductsDecorator.new([])
|
270
|
-
decorator.decorated_collection.
|
255
|
+
expect(decorator.decorated_collection).to receive(:kind_of?).with(Array).and_return("true")
|
271
256
|
expect(decorator.kind_of?(Array)).to eq "true"
|
272
257
|
end
|
273
258
|
|
274
259
|
context 'when asking the underlying collection returns false' do
|
275
260
|
it 'asks the CollectionDecorator instance itself' do
|
276
261
|
decorator = ProductsDecorator.new([])
|
277
|
-
decorator.decorated_collection.
|
262
|
+
allow(decorator.decorated_collection).to receive(:kind_of?).with(::Draper::CollectionDecorator).and_return(false)
|
278
263
|
expect(decorator.kind_of?(::Draper::CollectionDecorator)).to be true
|
279
264
|
end
|
280
265
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Draper
|
4
|
+
RSpec.describe Configuration do
|
5
|
+
it 'yields Draper on configure' do
|
6
|
+
Draper.configure { |config| expect(config).to be Draper }
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'defaults default_controller to ApplicationController' do
|
10
|
+
expect(Draper.default_controller).to be ApplicationController
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'allows customizing default_controller through configure' do
|
14
|
+
default = Draper.default_controller
|
15
|
+
|
16
|
+
Draper.configure do |config|
|
17
|
+
config.default_controller = CustomController
|
18
|
+
end
|
19
|
+
|
20
|
+
expect(Draper.default_controller).to be CustomController
|
21
|
+
|
22
|
+
Draper.default_controller = default
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -22,7 +22,7 @@ module Draper
|
|
22
22
|
|
23
23
|
it "uses the #decorator_class" do
|
24
24
|
product = Product.new
|
25
|
-
product.
|
25
|
+
allow(product).to receive_messages decorator_class: OtherDecorator
|
26
26
|
|
27
27
|
expect(product.decorate).to be_an_instance_of OtherDecorator
|
28
28
|
end
|
@@ -70,7 +70,7 @@ module Draper
|
|
70
70
|
it "delegates to .decorator_class" do
|
71
71
|
product = Product.new
|
72
72
|
|
73
|
-
Product.
|
73
|
+
expect(Product).to receive(:decorator_class).and_return(:some_decorator)
|
74
74
|
expect(product.decorator_class).to be :some_decorator
|
75
75
|
end
|
76
76
|
end
|
@@ -83,14 +83,14 @@ module Draper
|
|
83
83
|
it "is true when #== is true" do
|
84
84
|
product = Product.new
|
85
85
|
|
86
|
-
product.
|
86
|
+
expect(product).to receive(:==).and_return(true)
|
87
87
|
expect(product === :anything).to be_truthy
|
88
88
|
end
|
89
89
|
|
90
90
|
it "is false when #== is false" do
|
91
91
|
product = Product.new
|
92
92
|
|
93
|
-
product.
|
93
|
+
expect(product).to receive(:==).and_return(false)
|
94
94
|
expect(product === :anything).to be_falsey
|
95
95
|
end
|
96
96
|
end
|
@@ -109,40 +109,44 @@ module Draper
|
|
109
109
|
end
|
110
110
|
|
111
111
|
it "is true for a decorated instance" do
|
112
|
-
decorator =
|
112
|
+
decorator = Product.new.decorate
|
113
113
|
|
114
114
|
expect(Product === decorator).to be_truthy
|
115
115
|
end
|
116
116
|
|
117
117
|
it "is true for a decorated derived instance" do
|
118
|
-
decorator =
|
118
|
+
decorator = Class.new(Product).new.decorate
|
119
119
|
|
120
120
|
expect(Product === decorator).to be_truthy
|
121
121
|
end
|
122
122
|
|
123
123
|
it "is false for a decorated unrelated instance" do
|
124
|
-
decorator =
|
124
|
+
decorator = Other.new.decorate
|
125
|
+
|
126
|
+
expect(Product === decorator).to be_falsey
|
127
|
+
end
|
128
|
+
|
129
|
+
it "is false for a non-decorator which happens to respond to object" do
|
130
|
+
decorator = double(object: Product.new)
|
125
131
|
|
126
132
|
expect(Product === decorator).to be_falsey
|
127
133
|
end
|
128
134
|
end
|
129
135
|
|
130
136
|
describe ".decorate" do
|
131
|
-
let(:scoping_method) { Rails::VERSION::MAJOR >= 4 ? :all : :scoped }
|
132
|
-
|
133
137
|
it "calls #decorate_collection on .decorator_class" do
|
134
138
|
scoped = [Product.new]
|
135
|
-
Product.
|
139
|
+
allow(Product).to receive(:all).and_return(scoped)
|
136
140
|
|
137
|
-
Product.decorator_class.
|
141
|
+
expect(Product.decorator_class).to receive(:decorate_collection).with(scoped, with: nil).and_return(:decorated_collection)
|
138
142
|
expect(Product.decorate).to be :decorated_collection
|
139
143
|
end
|
140
144
|
|
141
145
|
it "accepts options" do
|
142
146
|
options = {with: ProductDecorator, context: {some: "context"}}
|
143
|
-
Product.
|
147
|
+
allow(Product).to receive(:all).and_return([])
|
144
148
|
|
145
|
-
Product.decorator_class.
|
149
|
+
expect(Product.decorator_class).to receive(:decorate_collection).with([], options)
|
146
150
|
Product.decorate(options)
|
147
151
|
end
|
148
152
|
end
|
@@ -162,7 +166,7 @@ module Draper
|
|
162
166
|
|
163
167
|
context "for ActiveModel classes" do
|
164
168
|
it "infers the decorator from the model name" do
|
165
|
-
Product.
|
169
|
+
allow(Product).to receive(:model_name){"Other"}
|
166
170
|
|
167
171
|
expect(Product.decorator_class).to be OtherDecorator
|
168
172
|
end
|
@@ -177,13 +181,22 @@ module Draper
|
|
177
181
|
|
178
182
|
context "for ActiveModel classes" do
|
179
183
|
it "infers the decorator from the model name" do
|
180
|
-
Namespaced::Product.
|
184
|
+
allow(Namespaced::Product).to receive(:model_name).and_return("Namespaced::Other")
|
181
185
|
|
182
186
|
expect(Namespaced::Product.decorator_class).to be Namespaced::OtherDecorator
|
183
187
|
end
|
184
188
|
end
|
185
189
|
end
|
186
190
|
|
191
|
+
context "when the decorator contains name error" do
|
192
|
+
it "throws an NameError" do
|
193
|
+
# We imitate ActiveSupport::Autoload behavior here in order to cause lazy NameError exception raising
|
194
|
+
allow_any_instance_of(Module).to receive(:const_missing) { Class.new { any_nonexisting_method_name } }
|
195
|
+
|
196
|
+
expect{Model.decorator_class}.to raise_error { |error| expect(error).to be_an_instance_of(NameError) }
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
187
200
|
context "when the decorator can't be inferred" do
|
188
201
|
it "throws an UninferrableDecoratorError" do
|
189
202
|
expect{Model.decorator_class}.to raise_error UninferrableDecoratorError
|
@@ -192,7 +205,7 @@ module Draper
|
|
192
205
|
|
193
206
|
context "when an unrelated NameError is thrown" do
|
194
207
|
it "re-raises that error" do
|
195
|
-
String.
|
208
|
+
allow_any_instance_of(String).to receive(:constantize) { Draper::Base }
|
196
209
|
expect{Product.decorator_class}.to raise_error NameError, /Draper::Base/
|
197
210
|
end
|
198
211
|
end
|
@@ -16,20 +16,20 @@ module Draper
|
|
16
16
|
it "creates a factory" do
|
17
17
|
options = {with: Decorator, context: {foo: "bar"}}
|
18
18
|
|
19
|
-
Factory.
|
19
|
+
expect(Factory).to receive(:new).with(options)
|
20
20
|
DecoratedAssociation.new(double, :association, options)
|
21
21
|
end
|
22
22
|
|
23
23
|
describe ":with option" do
|
24
24
|
it "defaults to nil" do
|
25
|
-
Factory.
|
25
|
+
expect(Factory).to receive(:new).with(with: nil, context: anything())
|
26
26
|
DecoratedAssociation.new(double, :association, {})
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
30
|
describe ":context option" do
|
31
31
|
it "defaults to the identity function" do
|
32
|
-
Factory.
|
32
|
+
expect(Factory).to receive(:new) do |options|
|
33
33
|
options[:context].call(:anything) == :anything
|
34
34
|
end
|
35
35
|
DecoratedAssociation.new(double, :association, {})
|
@@ -40,7 +40,7 @@ module Draper
|
|
40
40
|
describe "#call" do
|
41
41
|
it "calls the factory" do
|
42
42
|
factory = double
|
43
|
-
Factory.
|
43
|
+
allow(Factory).to receive_messages(new: factory)
|
44
44
|
associated = double
|
45
45
|
owner_context = {foo: "bar"}
|
46
46
|
object = double(association: associated)
|
@@ -48,18 +48,18 @@ module Draper
|
|
48
48
|
decorated_association = DecoratedAssociation.new(owner, :association, {})
|
49
49
|
decorated = double
|
50
50
|
|
51
|
-
factory.
|
51
|
+
expect(factory).to receive(:decorate).with(associated, context_args: owner_context).and_return(decorated)
|
52
52
|
expect(decorated_association.call).to be decorated
|
53
53
|
end
|
54
54
|
|
55
55
|
it "memoizes" do
|
56
56
|
factory = double
|
57
|
-
Factory.
|
57
|
+
allow(Factory).to receive_messages(new: factory)
|
58
58
|
owner = double(object: double(association: double), context: {})
|
59
59
|
decorated_association = DecoratedAssociation.new(owner, :association, {})
|
60
60
|
decorated = double
|
61
61
|
|
62
|
-
factory.
|
62
|
+
expect(factory).to receive(:decorate).once.and_return(decorated)
|
63
63
|
expect(decorated_association.call).to be decorated
|
64
64
|
expect(decorated_association.call).to be decorated
|
65
65
|
end
|
@@ -67,14 +67,14 @@ module Draper
|
|
67
67
|
context "when the :scope option was given" do
|
68
68
|
it "applies the scope before decoration" do
|
69
69
|
factory = double
|
70
|
-
Factory.
|
70
|
+
allow(Factory).to receive_messages(new: factory)
|
71
71
|
scoped = double
|
72
72
|
object = double(association: double(applied_scope: scoped))
|
73
73
|
owner = double(object: object, context: {})
|
74
74
|
decorated_association = DecoratedAssociation.new(owner, :association, scope: :applied_scope)
|
75
75
|
decorated = double
|
76
76
|
|
77
|
-
factory.
|
77
|
+
expect(factory).to receive(:decorate).with(scoped, anything()).and_return(decorated)
|
78
78
|
expect(decorated_association.call).to be decorated
|
79
79
|
end
|
80
80
|
end
|
@@ -28,14 +28,14 @@ module Draper
|
|
28
28
|
end
|
29
29
|
|
30
30
|
it "creates a factory" do
|
31
|
-
Factory.
|
31
|
+
allow(Factory).to receive(:new).once
|
32
32
|
controller_class.decorates_assigned :article, :author
|
33
33
|
end
|
34
34
|
|
35
35
|
it "passes options to the factory" do
|
36
36
|
options = {foo: "bar"}
|
37
37
|
|
38
|
-
Factory.
|
38
|
+
allow(Factory).to receive(:new).with(options)
|
39
39
|
controller_class.decorates_assigned :article, :author, options
|
40
40
|
end
|
41
41
|
|
@@ -43,24 +43,24 @@ module Draper
|
|
43
43
|
it "decorates the instance variable" do
|
44
44
|
object = double
|
45
45
|
factory = double
|
46
|
-
Factory.
|
46
|
+
allow(Factory).to receive_messages(new: factory)
|
47
47
|
|
48
48
|
controller_class.decorates_assigned :article
|
49
49
|
controller = controller_class.new
|
50
50
|
controller.instance_variable_set "@article", object
|
51
51
|
|
52
|
-
factory.
|
52
|
+
expect(factory).to receive(:decorate).with(object, context_args: controller).and_return(:decorated)
|
53
53
|
expect(controller.article).to be :decorated
|
54
54
|
end
|
55
55
|
|
56
56
|
it "memoizes" do
|
57
57
|
factory = double
|
58
|
-
Factory.
|
58
|
+
allow(Factory).to receive_messages(new: factory)
|
59
59
|
|
60
60
|
controller_class.decorates_assigned :article
|
61
61
|
controller = controller_class.new
|
62
62
|
|
63
|
-
factory.
|
63
|
+
expect(factory).to receive(:decorate).once
|
64
64
|
controller.article
|
65
65
|
controller.article
|
66
66
|
end
|
@@ -73,7 +73,7 @@ module Draper
|
|
73
73
|
decorated = OtherDecorator.new(Decorator.new(Model.new))
|
74
74
|
|
75
75
|
warning_message = nil
|
76
|
-
Object.
|
76
|
+
allow_any_instance_of(Object).to receive(:warn) { |instance, message| warning_message = message }
|
77
77
|
|
78
78
|
expect{Decorator.new(decorated)}.to change{warning_message}
|
79
79
|
expect(warning_message).to start_with "Reapplying Draper::Decorator"
|
@@ -82,7 +82,7 @@ module Draper
|
|
82
82
|
|
83
83
|
it "decorates anyway" do
|
84
84
|
decorated = OtherDecorator.new(Decorator.new(Model.new))
|
85
|
-
Object.
|
85
|
+
allow_any_instance_of(Object).to receive(:warn)
|
86
86
|
redecorated = Decorator.decorate(decorated)
|
87
87
|
|
88
88
|
expect(redecorated.object).to be decorated
|
@@ -102,7 +102,7 @@ module Draper
|
|
102
102
|
|
103
103
|
describe ".decorate_collection" do
|
104
104
|
describe "options validation" do
|
105
|
-
before { CollectionDecorator.
|
105
|
+
before { allow(CollectionDecorator).to receive(:new) }
|
106
106
|
|
107
107
|
it "does not raise error on valid options" do
|
108
108
|
valid_options = {with: OtherDecorator, context: {}}
|
@@ -118,14 +118,14 @@ module Draper
|
|
118
118
|
it "creates a CollectionDecorator using itself for each item" do
|
119
119
|
object = [Model.new]
|
120
120
|
|
121
|
-
CollectionDecorator.
|
121
|
+
expect(CollectionDecorator).to receive(:new).with(object, with: Decorator)
|
122
122
|
Decorator.decorate_collection(object)
|
123
123
|
end
|
124
124
|
|
125
125
|
it "passes options to the collection decorator" do
|
126
126
|
options = {with: OtherDecorator, context: {some: "context"}}
|
127
127
|
|
128
|
-
CollectionDecorator.
|
128
|
+
expect(CollectionDecorator).to receive(:new).with([], options)
|
129
129
|
Decorator.decorate_collection([], options)
|
130
130
|
end
|
131
131
|
end
|
@@ -134,24 +134,17 @@ module Draper
|
|
134
134
|
it "creates a custom collection decorator using itself for each item" do
|
135
135
|
object = [Model.new]
|
136
136
|
|
137
|
-
ProductsDecorator.
|
137
|
+
expect(ProductsDecorator).to receive(:new).with(object, with: ProductDecorator)
|
138
138
|
ProductDecorator.decorate_collection(object)
|
139
139
|
end
|
140
140
|
|
141
141
|
it "passes options to the collection decorator" do
|
142
142
|
options = {with: OtherDecorator, context: {some: "context"}}
|
143
143
|
|
144
|
-
ProductsDecorator.
|
144
|
+
expect(ProductsDecorator).to receive(:new).with([], options)
|
145
145
|
ProductDecorator.decorate_collection([], options)
|
146
146
|
end
|
147
147
|
end
|
148
|
-
|
149
|
-
context "when a NameError is thrown" do
|
150
|
-
it "re-raises that error" do
|
151
|
-
String.any_instance.stub(:constantize) { Draper::DecoratedEnumerableProxy }
|
152
|
-
expect{ProductDecorator.decorate_collection([])}.to raise_error NameError, /Draper::DecoratedEnumerableProxy/
|
153
|
-
end
|
154
|
-
end
|
155
148
|
end
|
156
149
|
|
157
150
|
describe ".decorates" do
|
@@ -181,61 +174,67 @@ module Draper
|
|
181
174
|
protect_class Namespaced::ProductDecorator
|
182
175
|
|
183
176
|
context "when not set by .decorates" do
|
184
|
-
it "raises an
|
185
|
-
expect{Decorator.object_class}.to raise_error
|
177
|
+
it "raises an UninferrableObjectError for a so-named 'Decorator'" do
|
178
|
+
expect{Decorator.object_class}.to raise_error UninferrableObjectError
|
186
179
|
end
|
187
180
|
|
188
|
-
it "raises an
|
189
|
-
expect{Class.new(Decorator).object_class}.to raise_error
|
181
|
+
it "raises an UninferrableObjectError for anonymous decorators" do
|
182
|
+
expect{Class.new(Decorator).object_class}.to raise_error UninferrableObjectError
|
190
183
|
end
|
191
184
|
|
192
|
-
it "raises an
|
193
|
-
|
194
|
-
expect{
|
185
|
+
it "raises an UninferrableObjectError for a decorator without a model" do
|
186
|
+
SomeDecorator = Class.new(Draper::Decorator)
|
187
|
+
expect{SomeDecorator.object_class}.to raise_error UninferrableObjectError
|
195
188
|
end
|
196
189
|
|
197
|
-
it "raises an
|
198
|
-
|
190
|
+
it "raises an UninferrableObjectError for other naming conventions" do
|
191
|
+
ProductPresenter = Class.new(Draper::Decorator)
|
192
|
+
expect{ProductPresenter.object_class}.to raise_error UninferrableObjectError
|
199
193
|
end
|
200
194
|
|
201
|
-
it "infers the
|
195
|
+
it "infers the object class for '<Model>Decorator'" do
|
202
196
|
expect(ProductDecorator.object_class).to be Product
|
203
197
|
end
|
204
198
|
|
205
|
-
it "infers namespaced
|
199
|
+
it "infers the object class for namespaced decorators" do
|
206
200
|
expect(Namespaced::ProductDecorator.object_class).to be Namespaced::Product
|
207
201
|
end
|
208
202
|
|
209
203
|
context "when an unrelated NameError is thrown" do
|
210
204
|
it "re-raises that error" do
|
211
|
-
String.
|
205
|
+
allow_any_instance_of(String).to receive(:constantize) { SomethingThatDoesntExist }
|
212
206
|
expect{ProductDecorator.object_class}.to raise_error NameError, /SomethingThatDoesntExist/
|
213
207
|
end
|
214
208
|
end
|
215
209
|
end
|
216
|
-
|
217
|
-
it "is aliased to .source_class" do
|
218
|
-
expect(ProductDecorator.source_class).to be Product
|
219
|
-
end
|
220
210
|
end
|
221
211
|
|
222
212
|
describe ".object_class?" do
|
223
213
|
it "returns truthy when .object_class is set" do
|
224
|
-
Decorator.
|
214
|
+
allow(Decorator).to receive(:object_class).and_return(Model)
|
225
215
|
|
226
216
|
expect(Decorator.object_class?).to be_truthy
|
227
217
|
end
|
228
218
|
|
229
219
|
it "returns false when .object_class is not inferrable" do
|
230
|
-
Decorator.
|
220
|
+
allow(Decorator).to receive(:object_class).and_raise(UninferrableObjectError.new(Decorator))
|
231
221
|
|
232
222
|
expect(Decorator.object_class?).to be_falsey
|
233
223
|
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe '.collection_decorator_class' do
|
227
|
+
it 'defaults to CollectionDecorator' do
|
228
|
+
allow_any_instance_of(String).to receive(:constantize) { SomethingThatDoesntExist }
|
229
|
+
expect(ProductDecorator.collection_decorator_class).to be Draper::CollectionDecorator
|
230
|
+
end
|
234
231
|
|
235
|
-
it
|
236
|
-
|
232
|
+
it 'infers collection decorator based on name' do
|
233
|
+
expect(ProductDecorator.collection_decorator_class).to be ProductsDecorator
|
234
|
+
end
|
237
235
|
|
238
|
-
|
236
|
+
it 'infers collection decorator base on name for namespeced model' do
|
237
|
+
expect(Namespaced::ProductDecorator.collection_decorator_class).to be Namespaced::ProductsDecorator
|
239
238
|
end
|
240
239
|
end
|
241
240
|
|
@@ -243,7 +242,7 @@ module Draper
|
|
243
242
|
protect_class Decorator
|
244
243
|
|
245
244
|
describe "options validation" do
|
246
|
-
before { DecoratedAssociation.
|
245
|
+
before { allow(DecoratedAssociation).to receive(:new).and_return(->{}) }
|
247
246
|
|
248
247
|
it "does not raise error on valid options" do
|
249
248
|
valid_options = {with: Class, scope: :sorted, context: {}}
|
@@ -261,7 +260,7 @@ module Draper
|
|
261
260
|
Decorator.decorates_association :children, options
|
262
261
|
decorator = Decorator.new(Model.new)
|
263
262
|
|
264
|
-
DecoratedAssociation.
|
263
|
+
expect(DecoratedAssociation).to receive(:new).with(decorator, :children, options).and_return(->{})
|
265
264
|
decorator.children
|
266
265
|
end
|
267
266
|
|
@@ -269,7 +268,7 @@ module Draper
|
|
269
268
|
Decorator.decorates_association :children
|
270
269
|
decorator = Decorator.new(Model.new)
|
271
270
|
|
272
|
-
DecoratedAssociation.
|
271
|
+
expect(DecoratedAssociation).to receive(:new).once.and_return(->{})
|
273
272
|
decorator.children
|
274
273
|
decorator.children
|
275
274
|
end
|
@@ -278,9 +277,9 @@ module Draper
|
|
278
277
|
Decorator.decorates_association :children
|
279
278
|
decorator = Decorator.new(Model.new)
|
280
279
|
decorated_association = ->{}
|
281
|
-
DecoratedAssociation.
|
280
|
+
allow(DecoratedAssociation).to receive(:new).and_return(decorated_association)
|
282
281
|
|
283
|
-
decorated_association.
|
282
|
+
expect(decorated_association).to receive(:call).and_return(:decorated)
|
284
283
|
expect(decorator.children).to be :decorated
|
285
284
|
end
|
286
285
|
end
|
@@ -290,16 +289,16 @@ module Draper
|
|
290
289
|
protect_class Decorator
|
291
290
|
|
292
291
|
it "decorates each of the associations" do
|
293
|
-
Decorator.
|
294
|
-
Decorator.
|
292
|
+
expect(Decorator).to receive(:decorates_association).with(:friends, {})
|
293
|
+
expect(Decorator).to receive(:decorates_association).with(:enemies, {})
|
295
294
|
Decorator.decorates_associations :friends, :enemies
|
296
295
|
end
|
297
296
|
|
298
297
|
it "dispatches options" do
|
299
298
|
options = {with: Class.new, scope: :foo, context: {}}
|
300
299
|
|
301
|
-
Decorator.
|
302
|
-
Decorator.
|
300
|
+
expect(Decorator).to receive(:decorates_association).with(:friends, options)
|
301
|
+
expect(Decorator).to receive(:decorates_association).with(:enemies, options)
|
303
302
|
Decorator.decorates_associations :friends, :enemies, options
|
304
303
|
end
|
305
304
|
end
|
@@ -337,7 +336,6 @@ module Draper
|
|
337
336
|
|
338
337
|
expect(decorator.object).to be object
|
339
338
|
expect(decorator.model).to be object
|
340
|
-
expect(decorator.to_source).to be object
|
341
339
|
end
|
342
340
|
|
343
341
|
it "is aliased to #model" do
|
@@ -346,20 +344,6 @@ module Draper
|
|
346
344
|
|
347
345
|
expect(decorator.model).to be object
|
348
346
|
end
|
349
|
-
|
350
|
-
it "is aliased to #source" do
|
351
|
-
object = Model.new
|
352
|
-
decorator = Decorator.new(object)
|
353
|
-
|
354
|
-
expect(decorator.source).to be object
|
355
|
-
end
|
356
|
-
|
357
|
-
it "is aliased to #to_source" do
|
358
|
-
object = Model.new
|
359
|
-
decorator = Decorator.new(object)
|
360
|
-
|
361
|
-
expect(decorator.to_source).to be object
|
362
|
-
end
|
363
347
|
end
|
364
348
|
|
365
349
|
describe "aliasing object to object class name" do
|
@@ -480,15 +464,15 @@ module Draper
|
|
480
464
|
describe "#attributes" do
|
481
465
|
it "returns only the object's attributes that are implemented by the decorator" do
|
482
466
|
decorator = Decorator.new(double(attributes: {foo: "bar", baz: "qux"}))
|
483
|
-
decorator.
|
467
|
+
allow(decorator).to receive(:foo)
|
484
468
|
|
485
469
|
expect(decorator.attributes).to eq({foo: "bar"})
|
486
470
|
end
|
487
471
|
end
|
488
472
|
|
489
473
|
describe ".model_name" do
|
490
|
-
it "delegates to the
|
491
|
-
Decorator.
|
474
|
+
it "delegates to the object class" do
|
475
|
+
allow(Decorator).to receive(:object_class).and_return(double(model_name: :delegated))
|
492
476
|
|
493
477
|
expect(Decorator.model_name).to be :delegated
|
494
478
|
end
|
@@ -514,7 +498,7 @@ module Draper
|
|
514
498
|
decorator = Decorator.new(object)
|
515
499
|
other = double(object: Model.new)
|
516
500
|
|
517
|
-
object.
|
501
|
+
expect(object).to receive(:==).with(other).and_return(true)
|
518
502
|
expect(decorator == other).to be_truthy
|
519
503
|
end
|
520
504
|
|
@@ -523,7 +507,7 @@ module Draper
|
|
523
507
|
decorator = Decorator.new(object)
|
524
508
|
other = double(object: Model.new)
|
525
509
|
|
526
|
-
object.
|
510
|
+
expect(object).to receive(:==).with(other).and_return(false)
|
527
511
|
expect(decorator == other).to be_falsey
|
528
512
|
end
|
529
513
|
end
|
@@ -538,7 +522,7 @@ module Draper
|
|
538
522
|
|
539
523
|
it "is false when #== is false" do
|
540
524
|
decorator = Decorator.new(Model.new)
|
541
|
-
decorator.
|
525
|
+
allow(decorator).to receive(:==).with(:anything).and_return(false)
|
542
526
|
|
543
527
|
expect(decorator === :anything).to be_falsey
|
544
528
|
end
|
@@ -574,12 +558,12 @@ module Draper
|
|
574
558
|
protect_class Decorator
|
575
559
|
|
576
560
|
it "defaults the :to option to :object" do
|
577
|
-
Object.
|
561
|
+
expect(Object).to receive(:delegate).with(:foo, :bar, to: :object)
|
578
562
|
Decorator.delegate :foo, :bar
|
579
563
|
end
|
580
564
|
|
581
565
|
it "does not overwrite the :to option if supplied" do
|
582
|
-
Object.
|
566
|
+
expect(Object).to receive(:delegate).with(:foo, :bar, to: :baz)
|
583
567
|
Decorator.delegate :foo, :bar, to: :baz
|
584
568
|
end
|
585
569
|
end
|
@@ -596,17 +580,56 @@ module Draper
|
|
596
580
|
expect(decorator.hello_world).to be :delegated
|
597
581
|
end
|
598
582
|
|
599
|
-
it
|
600
|
-
|
583
|
+
it 'delegates `super` to parent class first' do
|
584
|
+
parent_decorator_class = Class.new(Decorator) do
|
585
|
+
def hello_world
|
586
|
+
"parent#hello_world"
|
587
|
+
end
|
588
|
+
end
|
601
589
|
|
602
|
-
|
603
|
-
|
604
|
-
|
590
|
+
child_decorator_class = Class.new(parent_decorator_class) do
|
591
|
+
def hello_world
|
592
|
+
super
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
decorator = child_decorator_class.new(double(hello_world: 'object#hello_world'))
|
597
|
+
expect(decorator.hello_world).to eq 'parent#hello_world'
|
598
|
+
end
|
599
|
+
|
600
|
+
it 'delegates `super` to object if method does not exist on parent class' do
|
601
|
+
decorator_class = Class.new(Decorator) do
|
602
|
+
def hello_world
|
603
|
+
super
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
decorator = decorator_class.new(double(hello_world: 'object#hello_world'))
|
608
|
+
expect(decorator.hello_world).to eq 'object#hello_world'
|
609
|
+
end
|
610
|
+
|
611
|
+
it 'raises `NoMethodError` when `super` is called on for method that does not exist' do
|
612
|
+
decorator_class = Class.new(Decorator) do
|
613
|
+
def hello_world
|
614
|
+
super
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
decorator = decorator_class.new(double)
|
619
|
+
expect{decorator.hello_world}.to raise_error NoMethodError
|
620
|
+
end
|
621
|
+
|
622
|
+
it "allows decorator to decorate different classes of objects" do
|
623
|
+
decorator_1 = Decorator.new(double)
|
624
|
+
decorator_2 = Decorator.new(double(hello_world: :delegated))
|
625
|
+
|
626
|
+
decorator_2.hello_world
|
627
|
+
expect(decorator_1.methods).not_to include :hello_world
|
605
628
|
end
|
606
629
|
|
607
630
|
it "passes blocks to delegated methods" do
|
608
631
|
object = Model.new
|
609
|
-
object.
|
632
|
+
allow(object).to receive(:hello_world) { |*args, &block| block.call }
|
610
633
|
decorator = Decorator.new(object)
|
611
634
|
|
612
635
|
expect(decorator.hello_world{:yielded}).to be :yielded
|
@@ -620,7 +643,7 @@ module Draper
|
|
620
643
|
|
621
644
|
it "delegates already-delegated methods" do
|
622
645
|
object = Class.new{ delegate :bar, to: :foo }.new
|
623
|
-
object.
|
646
|
+
allow(object).to receive_messages foo: double(bar: :delegated)
|
624
647
|
decorator = Decorator.new(object)
|
625
648
|
|
626
649
|
expect(decorator.bar).to be :delegated
|
@@ -643,23 +666,23 @@ module Draper
|
|
643
666
|
end
|
644
667
|
|
645
668
|
context ".method_missing" do
|
646
|
-
context "without
|
669
|
+
context "without an object class" do
|
647
670
|
it "raises a NoMethodError on missing methods" do
|
648
671
|
expect{Decorator.hello_world}.to raise_error NoMethodError
|
649
672
|
end
|
650
673
|
end
|
651
674
|
|
652
|
-
context "with
|
653
|
-
it "delegates methods that exist on the
|
675
|
+
context "with an object class" do
|
676
|
+
it "delegates methods that exist on the object class" do
|
654
677
|
object_class = Class.new
|
655
|
-
object_class.
|
656
|
-
Decorator.
|
678
|
+
allow(object_class).to receive_messages hello_world: :delegated
|
679
|
+
allow(Decorator).to receive_messages object_class: object_class
|
657
680
|
|
658
681
|
expect(Decorator.hello_world).to be :delegated
|
659
682
|
end
|
660
683
|
|
661
|
-
it "does not delegate methods that do not exist on the
|
662
|
-
Decorator.
|
684
|
+
it "does not delegate methods that do not exist on the object class" do
|
685
|
+
allow(Decorator).to receive_messages object_class: Class.new
|
663
686
|
|
664
687
|
expect{Decorator.hello_world}.to raise_error NoMethodError
|
665
688
|
end
|
@@ -698,7 +721,7 @@ module Draper
|
|
698
721
|
end
|
699
722
|
|
700
723
|
describe ".respond_to?" do
|
701
|
-
context "without a
|
724
|
+
context "without a object class" do
|
702
725
|
it "returns true for its own class methods" do
|
703
726
|
Decorator.class_eval{def self.hello_world; end}
|
704
727
|
|
@@ -710,16 +733,16 @@ module Draper
|
|
710
733
|
end
|
711
734
|
end
|
712
735
|
|
713
|
-
context "with a
|
736
|
+
context "with a object class" do
|
714
737
|
it "returns true for its own class methods" do
|
715
738
|
Decorator.class_eval{def self.hello_world; end}
|
716
|
-
Decorator.
|
739
|
+
allow(Decorator).to receive_messages object_class: Class.new
|
717
740
|
|
718
741
|
expect(Decorator).to respond_to :hello_world
|
719
742
|
end
|
720
743
|
|
721
|
-
it "returns true for the
|
722
|
-
Decorator.
|
744
|
+
it "returns true for the object's class methods" do
|
745
|
+
allow(Decorator).to receive_messages object_class: double(hello_world: :delegated)
|
723
746
|
|
724
747
|
expect(Decorator).to respond_to :hello_world
|
725
748
|
end
|
@@ -737,7 +760,7 @@ module Draper
|
|
737
760
|
|
738
761
|
describe ".respond_to_missing?" do
|
739
762
|
it "allows .method to be called on delegated class methods" do
|
740
|
-
Decorator.
|
763
|
+
allow(Decorator).to receive_messages object_class: double(hello_world: :delegated)
|
741
764
|
|
742
765
|
expect(Decorator.method(:hello_world)).not_to be_nil
|
743
766
|
end
|
@@ -745,7 +768,7 @@ module Draper
|
|
745
768
|
end
|
746
769
|
|
747
770
|
describe "class spoofing" do
|
748
|
-
it "pretends to be a kind of the
|
771
|
+
it "pretends to be a kind of the object class" do
|
749
772
|
decorator = Decorator.new(Model.new)
|
750
773
|
|
751
774
|
expect(decorator.kind_of?(Model)).to be_truthy
|
@@ -759,7 +782,7 @@ module Draper
|
|
759
782
|
expect(decorator.is_a?(Decorator)).to be_truthy
|
760
783
|
end
|
761
784
|
|
762
|
-
it "pretends to be an instance of the
|
785
|
+
it "pretends to be an instance of the object class" do
|
763
786
|
decorator = Decorator.new(Model.new)
|
764
787
|
|
765
788
|
expect(decorator.instance_of?(Model)).to be_truthy
|