draper 3.0.0 → 4.0.2

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