draper 3.0.0.pre1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +16 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +24 -0
  4. data/.rubocop.yml +11 -0
  5. data/.travis.yml +3 -2
  6. data/CHANGELOG.md +20 -0
  7. data/Guardfile +5 -5
  8. data/README.md +27 -5
  9. data/Rakefile +1 -2
  10. data/draper.gemspec +1 -0
  11. data/lib/draper.rb +7 -2
  12. data/lib/draper/automatic_delegation.rb +5 -3
  13. data/lib/draper/collection_decorator.rb +1 -11
  14. data/lib/draper/compatibility/api_only.rb +23 -0
  15. data/lib/draper/configuration.rb +15 -0
  16. data/lib/draper/decoratable.rb +2 -2
  17. data/lib/draper/decorator.rb +4 -12
  18. data/lib/draper/finders.rb +0 -0
  19. data/lib/draper/helper_proxy.rb +1 -8
  20. data/lib/draper/railtie.rb +12 -13
  21. data/lib/draper/tasks/test.rake +1 -1
  22. data/lib/draper/test/devise_helper.rb +1 -8
  23. data/lib/draper/test/minitest_integration.rb +0 -0
  24. data/lib/draper/test/rspec_integration.rb +0 -0
  25. data/lib/draper/undecorate.rb +8 -0
  26. data/lib/draper/version.rb +1 -1
  27. data/lib/draper/view_context.rb +3 -19
  28. data/lib/draper/view_context/build_strategy.rb +11 -2
  29. data/lib/generators/controller_override.rb +2 -2
  30. data/lib/generators/draper/install_generator.rb +14 -0
  31. data/lib/generators/draper/templates/application_decorator.rb +8 -0
  32. data/lib/generators/mini_test/decorator_generator.rb +1 -1
  33. data/lib/generators/rails/decorator_generator.rb +1 -8
  34. data/lib/generators/rspec/templates/decorator_spec.rb +1 -1
  35. data/spec/draper/collection_decorator_spec.rb +11 -26
  36. data/spec/draper/configuration_spec.rb +25 -0
  37. data/spec/draper/decoratable_spec.rb +28 -13
  38. data/spec/draper/decorated_association_spec.rb +9 -9
  39. data/spec/draper/decorates_assigned_spec.rb +6 -6
  40. data/spec/draper/decorator_spec.rb +104 -89
  41. data/spec/draper/draper_spec.rb +24 -0
  42. data/spec/draper/factory_spec.rb +24 -24
  43. data/spec/draper/finders_spec.rb +21 -21
  44. data/spec/draper/helper_proxy_spec.rb +2 -2
  45. data/spec/draper/lazy_helpers_spec.rb +2 -2
  46. data/spec/draper/undecorate_chain_spec.rb +20 -0
  47. data/spec/draper/view_context/build_strategy_spec.rb +26 -10
  48. data/spec/draper/view_context_spec.rb +49 -21
  49. data/spec/dummy/app/controllers/base_controller.rb +4 -0
  50. data/spec/dummy/app/controllers/posts_controller.rb +2 -2
  51. data/spec/dummy/app/decorators/post_decorator.rb +0 -0
  52. data/spec/dummy/config/boot.rb +1 -1
  53. data/spec/dummy/config/initializers/draper.rb +3 -0
  54. data/spec/dummy/db/schema.rb +4 -4
  55. data/spec/dummy/fast_spec/post_decorator_spec.rb +1 -1
  56. data/spec/dummy/lib/tasks/test.rake +1 -1
  57. data/spec/dummy/spec/decorators/devise_spec.rb +0 -9
  58. data/spec/dummy/spec/decorators/post_decorator_spec.rb +2 -2
  59. data/spec/dummy/test/decorators/minitest/devise_test.rb +0 -9
  60. data/spec/dummy/test/decorators/minitest/view_context_test.rb +3 -3
  61. data/spec/dummy/test/decorators/test_unit/devise_test.rb +0 -9
  62. data/spec/generators/controller/controller_generator_spec.rb +3 -3
  63. data/spec/generators/decorator/decorator_generator_spec.rb +11 -10
  64. data/spec/generators/install/install_generator_spec.rb +19 -0
  65. data/spec/spec_helper.rb +4 -3
  66. data/spec/support/shared_examples/view_helpers.rb +8 -8
  67. metadata +38 -7
  68. data/spec/dummy/app/controllers/application_controller.rb +0 -4
@@ -2,7 +2,7 @@ require 'rake/testtask'
2
2
  require 'rails/test_unit/railtie'
3
3
 
4
4
  namespace :test do
5
- Rake::TestTask.new(:decorators => "test:prepare") do |t|
5
+ Rake::TestTask.new(decorators: "test:prepare") do |t|
6
6
  t.libs << "test"
7
7
  t.pattern = "test/decorators/**/*_test.rb"
8
8
  end
@@ -1,14 +1,7 @@
1
1
  module Draper
2
2
  module DeviseHelper
3
3
  def sign_in(resource_or_scope, resource = nil)
4
- scope = begin
5
- Devise::Mapping.find_scope!(resource_or_scope)
6
- rescue RuntimeError => e
7
- # Draper 1.0 didn't require the mapping to exist
8
- ActiveSupport::Deprecation.warn("#{e.message}.\nUse `sign_in :user, mock_user` instead.", caller)
9
- :user
10
- end
11
-
4
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
12
5
  _stub_current_scope scope, resource || resource_or_scope
13
6
  end
14
7
 
File without changes
File without changes
@@ -6,4 +6,12 @@ module Draper
6
6
  object
7
7
  end
8
8
  end
9
+
10
+ def self.undecorate_chain(object)
11
+ if object.respond_to?(:decorated?) && object.decorated?
12
+ undecorate_chain(object.object)
13
+ else
14
+ object
15
+ end
16
+ end
9
17
  end
@@ -1,3 +1,3 @@
1
1
  module Draper
2
- VERSION = "3.0.0.pre1"
2
+ VERSION = '3.0.0'
3
3
  end
@@ -20,8 +20,10 @@ module Draper
20
20
  RequestStore.store[:current_controller]
21
21
  end
22
22
 
23
- # Sets the current controller.
23
+ # Sets the current controller. Clears view context when we are setting
24
+ # different controller.
24
25
  def self.controller=(controller)
26
+ clear! if RequestStore.store[:current_controller] != controller
25
27
  RequestStore.store[:current_controller] = controller
26
28
  end
27
29
 
@@ -82,23 +84,5 @@ module Draper
82
84
  def self.build_strategy
83
85
  @build_strategy ||= Draper::ViewContext::BuildStrategy.new(:full)
84
86
  end
85
-
86
- # @deprecated Use {controller} instead.
87
- def self.current_controller
88
- ActiveSupport::Deprecation.warn("Draper::ViewContext.current_controller is deprecated (use controller instead)", caller)
89
- self.controller || ApplicationController.new
90
- end
91
-
92
- # @deprecated Use {controller=} instead.
93
- def self.current_controller=(controller)
94
- ActiveSupport::Deprecation.warn("Draper::ViewContext.current_controller= is deprecated (use controller instead)", caller)
95
- self.controller = controller
96
- end
97
-
98
- # @deprecated Use {build} instead.
99
- def self.build_view_context
100
- ActiveSupport::Deprecation.warn("Draper::ViewContext.build_view_context is deprecated (use build instead)", caller)
101
- build
102
- end
103
87
  end
104
88
  end
@@ -37,10 +37,19 @@ module Draper
37
37
  attr_reader :block
38
38
 
39
39
  def controller
40
- (Draper::ViewContext.controller || ApplicationController.new).tap do |controller|
41
- controller.request ||= ActionController::TestRequest.create
40
+ Draper::ViewContext.controller ||= Draper.default_controller.new
41
+ Draper::ViewContext.controller.tap do |controller|
42
+ controller.request ||= new_test_request controller if defined?(ActionController::TestRequest)
42
43
  end
43
44
  end
45
+
46
+ def new_test_request(controller)
47
+ is_above_rails_5_1 ? ActionController::TestRequest.create(controller) : ActionController::TestRequest.create
48
+ end
49
+
50
+ def is_above_rails_5_1
51
+ ActionController::TestRequest.method(:create).parameters.first == [:req, :controller_class]
52
+ end
44
53
  end
45
54
 
46
55
  end
@@ -5,13 +5,13 @@ require "rails/generators/rails/scaffold_controller/scaffold_controller_generato
5
5
  module Rails
6
6
  module Generators
7
7
  class ControllerGenerator
8
- hook_for :decorator, default: true do |generator|
8
+ hook_for :decorator, type: :boolean, default: true do |generator|
9
9
  invoke generator, [name.singularize]
10
10
  end
11
11
  end
12
12
 
13
13
  class ScaffoldControllerGenerator
14
- hook_for :decorator, default: true
14
+ hook_for :decorator, type: :boolean, default: true
15
15
  end
16
16
  end
17
17
  end
@@ -0,0 +1,14 @@
1
+ module Draper
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path('../templates', __FILE__)
5
+
6
+ desc 'Creates an ApplicationDecorator, if none exists.'
7
+
8
+ def create_application_decorator
9
+ file = 'application_decorator.rb'
10
+ copy_file file, "app/decorators/#{file}"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ class ApplicationDecorator < Draper::Decorator
2
+ # Define methods for all decorated objects.
3
+ # Helpers are accessed through `helpers` (aka `h`). For example:
4
+ #
5
+ # def percent_amount
6
+ # h.number_to_percentage object.amount, precision: 2
7
+ # end
8
+ end
@@ -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
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,19 +109,25 @@ 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
@@ -130,17 +136,17 @@ module Draper
130
136
  describe ".decorate" do
131
137
  it "calls #decorate_collection on .decorator_class" do
132
138
  scoped = [Product.new]
133
- Product.stub all: scoped
139
+ allow(Product).to receive(:all).and_return(scoped)
134
140
 
135
- 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)
136
142
  expect(Product.decorate).to be :decorated_collection
137
143
  end
138
144
 
139
145
  it "accepts options" do
140
146
  options = {with: ProductDecorator, context: {some: "context"}}
141
- Product.stub all: []
147
+ allow(Product).to receive(:all).and_return([])
142
148
 
143
- Product.decorator_class.should_receive(:decorate_collection).with([], options)
149
+ expect(Product.decorator_class).to receive(:decorate_collection).with([], options)
144
150
  Product.decorate(options)
145
151
  end
146
152
  end
@@ -175,13 +181,22 @@ module Draper
175
181
 
176
182
  context "for ActiveModel classes" do
177
183
  it "infers the decorator from the model name" do
178
- Namespaced::Product.stub(:model_name).and_return("Namespaced::Other")
184
+ allow(Namespaced::Product).to receive(:model_name).and_return("Namespaced::Other")
179
185
 
180
186
  expect(Namespaced::Product.decorator_class).to be Namespaced::OtherDecorator
181
187
  end
182
188
  end
183
189
  end
184
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
+
185
200
  context "when the decorator can't be inferred" do
186
201
  it "throws an UninferrableDecoratorError" do
187
202
  expect{Model.decorator_class}.to raise_error UninferrableDecoratorError
@@ -190,7 +205,7 @@ module Draper
190
205
 
191
206
  context "when an unrelated NameError is thrown" do
192
207
  it "re-raises that error" do
193
- String.any_instance.stub(:constantize) { Draper::Base }
208
+ allow_any_instance_of(String).to receive(:constantize) { Draper::Base }
194
209
  expect{Product.decorator_class}.to raise_error NameError, /Draper::Base/
195
210
  end
196
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