draper 3.0.1 → 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
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'