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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +16 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +24 -0
  4. data/.gitignore +3 -1
  5. data/.rubocop.yml +11 -0
  6. data/.travis.yml +3 -7
  7. data/CHANGELOG.md +26 -0
  8. data/Gemfile +4 -5
  9. data/Guardfile +5 -5
  10. data/README.md +61 -11
  11. data/Rakefile +1 -1
  12. data/draper.gemspec +12 -10
  13. data/lib/draper.rb +8 -3
  14. data/lib/draper/automatic_delegation.rb +5 -3
  15. data/lib/draper/collection_decorator.rb +1 -11
  16. data/lib/draper/compatibility/api_only.rb +23 -0
  17. data/lib/draper/configuration.rb +15 -0
  18. data/lib/draper/decoratable.rb +3 -4
  19. data/lib/draper/decorator.rb +4 -24
  20. data/lib/draper/finders.rb +0 -0
  21. data/lib/draper/helper_proxy.rb +1 -8
  22. data/lib/draper/railtie.rb +12 -21
  23. data/lib/draper/tasks/test.rake +2 -15
  24. data/lib/draper/test/devise_helper.rb +1 -8
  25. data/lib/draper/test/minitest_integration.rb +0 -0
  26. data/lib/draper/test/rspec_integration.rb +1 -5
  27. data/lib/draper/test_case.rb +4 -8
  28. data/lib/draper/undecorate.rb +8 -0
  29. data/lib/draper/version.rb +1 -1
  30. data/lib/draper/view_context.rb +3 -19
  31. data/lib/draper/view_context/build_strategy.rb +11 -2
  32. data/lib/generators/controller_override.rb +2 -2
  33. data/lib/generators/draper/install_generator.rb +14 -0
  34. data/lib/generators/draper/templates/application_decorator.rb +8 -0
  35. data/lib/generators/mini_test/decorator_generator.rb +1 -1
  36. data/lib/generators/rails/decorator_generator.rb +1 -8
  37. data/lib/generators/rspec/templates/decorator_spec.rb +2 -2
  38. data/spec/draper/collection_decorator_spec.rb +11 -26
  39. data/spec/draper/configuration_spec.rb +25 -0
  40. data/spec/draper/decoratable_spec.rb +29 -16
  41. data/spec/draper/decorated_association_spec.rb +9 -9
  42. data/spec/draper/decorates_assigned_spec.rb +6 -6
  43. data/spec/draper/decorator_spec.rb +112 -89
  44. data/spec/draper/draper_spec.rb +24 -0
  45. data/spec/draper/factory_spec.rb +26 -26
  46. data/spec/draper/finders_spec.rb +21 -21
  47. data/spec/draper/helper_proxy_spec.rb +3 -3
  48. data/spec/draper/lazy_helpers_spec.rb +2 -2
  49. data/spec/draper/undecorate_chain_spec.rb +20 -0
  50. data/spec/draper/view_context/build_strategy_spec.rb +26 -10
  51. data/spec/draper/view_context_spec.rb +49 -21
  52. data/spec/dummy/app/controllers/base_controller.rb +4 -0
  53. data/spec/dummy/app/controllers/posts_controller.rb +2 -2
  54. data/spec/dummy/app/decorators/post_decorator.rb +0 -0
  55. data/spec/dummy/app/views/posts/_post.html.erb +8 -6
  56. data/spec/dummy/config/boot.rb +1 -1
  57. data/spec/dummy/config/initializers/draper.rb +3 -0
  58. data/spec/dummy/config/mongoid.yml +104 -41
  59. data/spec/dummy/db/schema.rb +4 -4
  60. data/spec/dummy/fast_spec/post_decorator_spec.rb +1 -1
  61. data/spec/dummy/lib/tasks/test.rake +1 -1
  62. data/spec/dummy/spec/decorators/active_model_serializers_spec.rb +4 -8
  63. data/spec/dummy/spec/decorators/devise_spec.rb +0 -9
  64. data/spec/dummy/spec/decorators/post_decorator_spec.rb +2 -4
  65. data/spec/dummy/spec/mailers/post_mailer_spec.rb +0 -8
  66. data/spec/dummy/spec/shared_examples/decoratable.rb +0 -2
  67. data/spec/dummy/test/decorators/minitest/devise_test.rb +0 -9
  68. data/spec/dummy/test/decorators/minitest/view_context_test.rb +3 -3
  69. data/spec/dummy/test/decorators/test_unit/devise_test.rb +0 -9
  70. data/spec/dummy/test/decorators/test_unit/view_context_test.rb +1 -1
  71. data/spec/generators/controller/controller_generator_spec.rb +3 -3
  72. data/spec/generators/decorator/decorator_generator_spec.rb +14 -12
  73. data/spec/generators/install/install_generator_spec.rb +19 -0
  74. data/spec/integration/integration_spec.rb +11 -8
  75. data/spec/performance/benchmark.rb +1 -1
  76. data/spec/spec_helper.rb +4 -4
  77. data/spec/support/matchers/have_text.rb +2 -2
  78. data/spec/support/shared_examples/view_helpers.rb +8 -8
  79. metadata +71 -29
  80. data/gemfiles/4.0.gemfile +0 -3
  81. data/gemfiles/4.1.gemfile +0 -3
  82. data/gemfiles/4.2.gemfile +0 -3
  83. 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, :type => :boolean, :default => false, :desc => "Use MiniTest::Spec DSL"
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
- class DecoratorGenerator < NamedBase
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
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ require 'rails_helper'
2
2
 
3
- describe <%= class_name %>Decorator do
3
+ RSpec.describe <%= class_name %>Decorator do
4
4
  end
@@ -63,7 +63,7 @@ module Draper
63
63
  it "does not trigger decoration" do
64
64
  decorator = CollectionDecorator.new([])
65
65
 
66
- decorator.should_not_receive(:decorated_collection)
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.should_receive(:delegate).with(:foo, :bar, to: :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.should_receive(:delegate).with(:foo, :bar, to: :baz)
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
- context "with a block" do
125
- it "decorates Enumerable#find" do
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
- object.should_receive(:find).and_return(found)
140
- ActiveSupport::Deprecation.silence do
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.should_receive(:to_ary).and_return(:delegated)
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.decorated_collection.should_receive(:[]).with(42).and_return(:delegated)
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.should_receive(:kind_of?).with(Array).and_return("true")
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.stub(:kind_of?).with(::Draper::CollectionDecorator).and_return(false)
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.stub decorator_class: OtherDecorator
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.should_receive(:decorator_class).and_return(:some_decorator)
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.should_receive(:==).and_return(true)
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.should_receive(:==).and_return(false)
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 = double(object: Product.new)
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 = double(object: Class.new(Product).new)
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 = double(object: Model.new)
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.stub scoping_method => scoped
139
+ allow(Product).to receive(:all).and_return(scoped)
136
140
 
137
- Product.decorator_class.should_receive(:decorate_collection).with(scoped, with: nil).and_return(:decorated_collection)
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.stub scoping_method => []
147
+ allow(Product).to receive(:all).and_return([])
144
148
 
145
- Product.decorator_class.should_receive(:decorate_collection).with([], options)
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.stub(:model_name).and_return("Other")
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.stub(:model_name).and_return("Namespaced::Other")
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.any_instance.stub(:constantize) { Draper::Base }
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.should_receive(:new).with(options)
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.should_receive(:new).with(with: nil, context: anything())
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.should_receive(:new) do |options|
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.stub new: 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.should_receive(:decorate).with(associated, context_args: owner_context).and_return(decorated)
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.stub new: 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.should_receive(:decorate).once.and_return(decorated)
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.stub new: 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.should_receive(:decorate).with(scoped, anything()).and_return(decorated)
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.should_receive(:new).once
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.should_receive(:new).with(options)
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.stub new: 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.should_receive(:decorate).with(object, context_args: controller).and_return(:decorated)
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.stub new: 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.should_receive(:decorate).once
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.any_instance.stub(:warn) { |instance, message| warning_message = message }
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.any_instance.stub(:warn)
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.stub(:new) }
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.should_receive(:new).with(object, with: Decorator)
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.should_receive(:new).with([], options)
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.should_receive(:new).with(object, with: ProductDecorator)
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.should_receive(:new).with([], options)
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 UninferrableSourceError for a so-named 'Decorator'" do
185
- expect{Decorator.object_class}.to raise_error UninferrableSourceError
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 UninferrableSourceError for anonymous decorators" do
189
- expect{Class.new(Decorator).object_class}.to raise_error UninferrableSourceError
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 UninferrableSourceError for a decorator without a model" do
193
- skip
194
- expect{OtherDecorator.object_class}.to raise_error UninferrableSourceError
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 UninferrableSourceError for other naming conventions" do
198
- expect{ProductPresenter.object_class}.to raise_error UninferrableSourceError
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 source for '<Model>Decorator'" do
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 sources" do
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.any_instance.stub(:constantize) { SomethingThatDoesntExist }
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.stub(:object_class).and_return(Model)
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.stub(:object_class).and_raise(UninferrableSourceError.new(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 "is aliased to .source_class?" do
236
- Decorator.stub(:object_class).and_return(Model)
232
+ it 'infers collection decorator based on name' do
233
+ expect(ProductDecorator.collection_decorator_class).to be ProductsDecorator
234
+ end
237
235
 
238
- expect(Decorator.source_class?).to be_truthy
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.stub(:new).and_return(->{}) }
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.should_receive(:new).with(decorator, :children, options).and_return(->{})
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.should_receive(:new).once.and_return(->{})
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.stub(:new).and_return(decorated_association)
280
+ allow(DecoratedAssociation).to receive(:new).and_return(decorated_association)
282
281
 
283
- decorated_association.should_receive(:call).and_return(:decorated)
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.should_receive(:decorates_association).with(:friends, {})
294
- Decorator.should_receive(:decorates_association).with(:enemies, {})
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.should_receive(:decorates_association).with(:friends, options)
302
- Decorator.should_receive(:decorates_association).with(:enemies, options)
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.stub(:foo)
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 source class" do
491
- Decorator.stub object_class: double(model_name: :delegated)
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.should_receive(:==).with(other).and_return(true)
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.should_receive(:==).with(other).and_return(false)
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.stub(:==).with(:anything).and_return(false)
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.should_receive(:delegate).with(:foo, :bar, to: :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.should_receive(:delegate).with(:foo, :bar, to: :baz)
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 "adds delegated methods to the decorator when they are used" do
600
- decorator = Decorator.new(double(hello_world: :delegated))
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
- expect(decorator.methods).not_to include :hello_world
603
- decorator.hello_world
604
- expect(decorator.methods).to include :hello_world
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.stub(:hello_world) { |*args, &block| block.call }
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.stub foo: double(bar: :delegated)
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 a source class" do
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 a source class" do
653
- it "delegates methods that exist on the source class" do
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.stub hello_world: :delegated
656
- Decorator.stub object_class: object_class
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 source class" do
662
- Decorator.stub object_class: Class.new
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 source class" do
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 source class" do
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.stub object_class: Class.new
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 source's class methods" do
722
- Decorator.stub object_class: double(hello_world: :delegated)
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.stub object_class: double(hello_world: :delegated)
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 source class" do
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 source class" do
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