draper 3.0.1 → 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 (47) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +55 -0
  3. data/CHANGELOG.md +41 -0
  4. data/Gemfile +11 -2
  5. data/README.md +35 -6
  6. data/bin/bundle +114 -0
  7. data/bin/rake +29 -0
  8. data/draper.gemspec +9 -8
  9. data/lib/draper/automatic_delegation.rb +4 -2
  10. data/lib/draper/collection_decorator.rb +4 -2
  11. data/lib/draper/configuration.rb +8 -0
  12. data/lib/draper/decoratable.rb +0 -1
  13. data/lib/draper/decorated_association.rb +0 -2
  14. data/lib/draper/decorator.rb +8 -6
  15. data/lib/draper/delegation.rb +1 -1
  16. data/lib/draper/finders.rb +0 -1
  17. data/lib/draper/helper_proxy.rb +2 -2
  18. data/lib/draper/lazy_helpers.rb +1 -3
  19. data/lib/draper/query_methods/load_strategy.rb +21 -0
  20. data/lib/draper/query_methods.rb +23 -0
  21. data/lib/draper/test_case.rb +3 -3
  22. data/lib/draper/undecorate.rb +1 -1
  23. data/lib/draper/version.rb +1 -1
  24. data/lib/draper/view_context/build_strategy.rb +1 -3
  25. data/lib/draper/view_helpers.rb +5 -5
  26. data/lib/draper.rb +3 -0
  27. data/spec/draper/collection_decorator_spec.rb +5 -6
  28. data/spec/draper/configuration_spec.rb +32 -8
  29. data/spec/draper/decoratable_spec.rb +1 -3
  30. data/spec/draper/decorated_association_spec.rb +0 -2
  31. data/spec/draper/decorator_spec.rb +33 -1
  32. data/spec/draper/draper_spec.rb +1 -0
  33. data/spec/draper/factory_spec.rb +0 -4
  34. data/spec/draper/query_methods/load_strategy_spec.rb +26 -0
  35. data/spec/draper/query_methods_spec.rb +70 -0
  36. data/spec/dummy/app/assets/config/manifest.js +3 -0
  37. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  38. data/spec/dummy/config/environments/development.rb +2 -0
  39. data/spec/dummy/config/environments/production.rb +2 -0
  40. data/spec/dummy/config/environments/test.rb +2 -0
  41. data/spec/dummy/config/storage.yml +7 -0
  42. data/spec/generators/controller/controller_generator_spec.rb +1 -0
  43. data/spec/generators/decorator/decorator_generator_spec.rb +1 -1
  44. data/spec/integration/integration_spec.rb +1 -0
  45. data/spec/spec_helper.rb +7 -0
  46. metadata +61 -20
  47. data/.travis.yml +0 -16
@@ -3,9 +3,9 @@ module Draper
3
3
 
4
4
  class TestCase < ::ActiveSupport::TestCase
5
5
  module ViewContextTeardown
6
- def teardown
7
- super
6
+ def before_setup
8
7
  Draper::ViewContext.clear!
8
+ super
9
9
  end
10
10
  end
11
11
 
@@ -21,7 +21,7 @@ module Draper
21
21
  end
22
22
 
23
23
  include Draper::ViewHelpers::ClassMethods
24
- alias_method :helper, :helpers
24
+ alias :helper :helpers
25
25
  end
26
26
 
27
27
  include Behavior
@@ -6,7 +6,7 @@ module Draper
6
6
  object
7
7
  end
8
8
  end
9
-
9
+
10
10
  def self.undecorate_chain(object)
11
11
  if object.respond_to?(:decorated?) && object.decorated?
12
12
  undecorate_chain(object.object)
@@ -1,3 +1,3 @@
1
1
  module Draper
2
- VERSION = '3.0.1'
2
+ VERSION = '4.0.2'
3
3
  end
@@ -2,7 +2,6 @@ module Draper
2
2
  module ViewContext
3
3
  # @private
4
4
  module BuildStrategy
5
-
6
5
  def self.new(name, &block)
7
6
  const_get(name.to_s.camelize).new(&block)
8
7
  end
@@ -13,7 +12,7 @@ module Draper
13
12
  end
14
13
 
15
14
  def call
16
- view_context_class.new
15
+ view_context_class.respond_to?(:empty) ? view_context_class.empty : view_context_class.new
17
16
  end
18
17
 
19
18
  private
@@ -51,7 +50,6 @@ module Draper
51
50
  ActionController::TestRequest.method(:create).parameters.first == [:req, :controller_class]
52
51
  end
53
52
  end
54
-
55
53
  end
56
54
  end
57
55
  end
@@ -5,7 +5,6 @@ module Draper
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  module ClassMethods
8
-
9
8
  # Access the helpers proxy to call built-in and user-defined
10
9
  # Rails helpers from a class context.
11
10
  #
@@ -13,8 +12,8 @@ module Draper
13
12
  def helpers
14
13
  Draper::ViewContext.current
15
14
  end
16
- alias_method :h, :helpers
17
15
 
16
+ alias :h :helpers
18
17
  end
19
18
 
20
19
  # Access the helpers proxy to call built-in and user-defined
@@ -24,14 +23,15 @@ module Draper
24
23
  def helpers
25
24
  Draper::ViewContext.current
26
25
  end
27
- alias_method :h, :helpers
26
+
27
+ alias :h :helpers
28
28
 
29
29
  # Alias for `helpers.localize`, since localize is something that's used
30
30
  # quite often. Further aliased to `l` for convenience.
31
- def localize(*args)
31
+ ruby2_keywords def localize(*args)
32
32
  helpers.localize(*args)
33
33
  end
34
- alias_method :l, :localize
35
34
 
35
+ alias :l :localize
36
36
  end
37
37
  end
data/lib/draper.rb CHANGED
@@ -8,6 +8,8 @@ require 'active_support/core_ext/hash/keys'
8
8
  require 'active_support/core_ext/hash/reverse_merge'
9
9
  require 'active_support/core_ext/name_error'
10
10
 
11
+ require 'ruby2_keywords'
12
+
11
13
  require 'draper/version'
12
14
  require 'draper/configuration'
13
15
  require 'draper/view_helpers'
@@ -23,6 +25,7 @@ require 'draper/factory'
23
25
  require 'draper/decorated_association'
24
26
  require 'draper/helper_support'
25
27
  require 'draper/view_context'
28
+ require 'draper/query_methods'
26
29
  require 'draper/collection_decorator'
27
30
  require 'draper/undecorate'
28
31
  require 'draper/decorates_assigned'
@@ -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"}
@@ -232,6 +231,5 @@ module Draper
232
231
  end
233
232
  end
234
233
  end
235
-
236
234
  end
237
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
@@ -439,7 +439,7 @@ module Draper
439
439
  it "returns a detailed description of the decorator" do
440
440
  decorator = ProductDecorator.new(double)
441
441
 
442
- expect(decorator.inspect).to match /#<ProductDecorator:0x\h+ .+>/
442
+ expect(decorator.inspect).to match(/#<ProductDecorator:0x\h+ .+>/)
443
443
  end
444
444
 
445
445
  it "includes the object" do
@@ -664,6 +664,38 @@ module Draper
664
664
  expect{decorator.hello_world}.to raise_error NoMethodError
665
665
  expect(decorator.methods).not_to include :hello_world
666
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
667
699
  end
668
700
 
669
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
@@ -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
@@ -30,4 +30,6 @@ Dummy::Application.configure do
30
30
  config.eager_load = false
31
31
 
32
32
  config.active_job.queue_adapter = :test
33
+
34
+ config.active_storage.service = :test
33
35
  end
@@ -0,0 +1,7 @@
1
+ test:
2
+ service: Disk
3
+ root: <%= Rails.root.join("tmp/storage") %>
4
+
5
+ local:
6
+ service: Disk
7
+ root: <%= Rails.root.join("storage") %>
@@ -3,6 +3,7 @@ require 'dummy/config/environment'
3
3
  require 'ammeter/init'
4
4
  require 'generators/controller_override'
5
5
  require 'generators/rails/decorator_generator'
6
+ SimpleCov.command_name 'test:generator'
6
7
 
7
8
  describe Rails::Generators::ControllerGenerator do
8
9
  destination File.expand_path("../tmp", __FILE__)
@@ -40,7 +40,7 @@ describe Rails::Generators::DecoratorGenerator do
40
40
 
41
41
  context "with an ApplicationDecorator" do
42
42
  before do
43
- allow_any_instance_of(Object).to receive(:require)
43
+ allow_any_instance_of(Object).to receive(:require).and_call_original
44
44
  allow_any_instance_of(Object).to receive(:require).with("application_decorator").and_return(
45
45
  stub_const "ApplicationDecorator", Class.new
46
46
  )
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'support/dummy_app'
3
3
  require 'support/matchers/have_text'
4
+ SimpleCov.command_name 'test:integration'
4
5
 
5
6
  app = DummyApp.new(ENV["RAILS_ENV"])
6
7
  spec_types = {
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,10 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter 'spec'
4
+ add_group 'Draper', 'lib/draper'
5
+ add_group 'Generators', 'lib/generators'
6
+ end
7
+
1
8
  require 'bundler/setup'
2
9
  require 'draper'
3
10
  require 'action_controller'