draper 1.0.0.beta6 → 1.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 (59) hide show
  1. data/.travis.yml +6 -0
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +20 -0
  4. data/Gemfile +11 -0
  5. data/README.md +14 -17
  6. data/Rakefile +5 -3
  7. data/draper.gemspec +2 -2
  8. data/lib/draper.rb +2 -1
  9. data/lib/draper/automatic_delegation.rb +50 -0
  10. data/lib/draper/collection_decorator.rb +26 -7
  11. data/lib/draper/decoratable.rb +71 -32
  12. data/lib/draper/decorated_association.rb +11 -7
  13. data/lib/draper/decorator.rb +114 -148
  14. data/lib/draper/delegation.rb +13 -0
  15. data/lib/draper/finders.rb +9 -6
  16. data/lib/draper/helper_proxy.rb +4 -3
  17. data/lib/draper/lazy_helpers.rb +10 -6
  18. data/lib/draper/railtie.rb +5 -4
  19. data/lib/draper/tasks/test.rake +22 -0
  20. data/lib/draper/test/devise_helper.rb +34 -0
  21. data/lib/draper/test/minitest_integration.rb +2 -3
  22. data/lib/draper/test/rspec_integration.rb +4 -59
  23. data/lib/draper/test_case.rb +33 -0
  24. data/lib/draper/version.rb +1 -1
  25. data/lib/draper/view_helpers.rb +4 -3
  26. data/lib/generators/decorator/templates/decorator.rb +7 -25
  27. data/lib/generators/mini_test/decorator_generator.rb +20 -0
  28. data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
  29. data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
  30. data/lib/generators/test_unit/templates/decorator_test.rb +1 -1
  31. data/spec/draper/collection_decorator_spec.rb +25 -3
  32. data/spec/draper/decorated_association_spec.rb +18 -7
  33. data/spec/draper/decorator_spec.rb +125 -165
  34. data/spec/draper/finders_spec.rb +0 -13
  35. data/spec/dummy/app/controllers/localized_urls.rb +1 -1
  36. data/spec/dummy/app/controllers/posts_controller.rb +3 -9
  37. data/spec/dummy/app/decorators/post_decorator.rb +4 -1
  38. data/spec/dummy/config/application.rb +3 -3
  39. data/spec/dummy/config/environments/development.rb +4 -4
  40. data/spec/dummy/config/environments/test.rb +2 -2
  41. data/spec/dummy/lib/tasks/test.rake +10 -0
  42. data/spec/dummy/mini_test/mini_test_integration_test.rb +46 -0
  43. data/spec/dummy/spec/decorators/post_decorator_spec.rb +2 -2
  44. data/spec/dummy/spec/decorators/rspec_integration_spec.rb +19 -0
  45. data/spec/dummy/spec/mailers/post_mailer_spec.rb +2 -2
  46. data/spec/dummy/spec/spec_helper.rb +0 -1
  47. data/spec/generators/decorator/decorator_generator_spec.rb +43 -2
  48. data/spec/integration/integration_spec.rb +2 -2
  49. data/spec/spec_helper.rb +17 -21
  50. data/spec/support/active_record.rb +0 -13
  51. data/spec/support/dummy_app.rb +4 -3
  52. metadata +26 -23
  53. data/lib/draper/security.rb +0 -48
  54. data/lib/draper/tasks/tu.rake +0 -5
  55. data/lib/draper/test/test_unit_integration.rb +0 -18
  56. data/spec/draper/security_spec.rb +0 -158
  57. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
  58. data/spec/dummy/lib/tasks/spec.rake +0 -5
  59. data/spec/minitest-rails/spec_type_spec.rb +0 -63
@@ -1,8 +1,9 @@
1
1
  module Draper
2
+ # Provides access to helper methods - both Rails built-in helpers, and those
3
+ # defined in your application.
2
4
  class HelperProxy
3
- # Some helpers are private, for example html_escape... as a workaround
4
- # we are wrapping the helpers in a delegator that passes the methods
5
- # along through a send, which will ignore private/public distinctions
5
+
6
+ # Sends helper methods to the view context.
6
7
  def method_missing(method, *args, &block)
7
8
  view_context.send(method, *args, &block)
8
9
  end
@@ -1,11 +1,15 @@
1
1
  module Draper
2
+ # Include this module in your decorators to get direct access to the helpers
3
+ # so that you can stop typing `h.` everywhere, at the cost of mixing in a
4
+ # bazillion methods.
2
5
  module LazyHelpers
3
- def method_missing(method_name, *args, &block)
4
- begin
5
- helpers.send method_name, *args, &block
6
- rescue NoMethodError
7
- super
8
- end
6
+
7
+ # Sends missing methods to the {HelperProxy}.
8
+ def method_missing(method, *args, &block)
9
+ helpers.send(method, *args, &block)
10
+ rescue NoMethodError
11
+ super
9
12
  end
13
+
10
14
  end
11
15
  end
@@ -17,10 +17,11 @@ module Draper
17
17
  config.after_initialize do |app|
18
18
  app.config.paths.add 'app/decorators', eager_load: true
19
19
 
20
- # Test Support
21
- require 'draper/test/rspec_integration' if defined?(RSpec) and RSpec.respond_to?(:configure)
22
- require 'draper/test/minitest_integration' if defined?(MiniTest::Rails)
23
- require 'draper/test/test_unit_integration'
20
+ unless Rails.env.production?
21
+ require 'draper/test_case'
22
+ require 'draper/test/rspec_integration' if defined?(RSpec) and RSpec.respond_to?(:configure)
23
+ require 'draper/test/minitest_integration' if defined?(MiniTest::Rails)
24
+ end
24
25
  end
25
26
 
26
27
  initializer "draper.setup_action_controller" do |app|
@@ -0,0 +1,22 @@
1
+ require 'rake/testtask'
2
+
3
+ test_task = if Rails.version.to_f < 3.2
4
+ require 'rails/test_unit/railtie'
5
+ Rake::TestTask
6
+ else
7
+ require 'rails/test_unit/sub_test_task'
8
+ Rails::SubTestTask
9
+ end
10
+
11
+ namespace :test do
12
+ test_task.new(:decorators => "test:prepare") do |t|
13
+ t.libs << "test"
14
+ t.pattern = "test/decorators/**/*_test.rb"
15
+ end
16
+ end
17
+
18
+ if Rake::Task.task_defined?('test:run')
19
+ Rake::Task['test:run'].enhance do
20
+ Rake::Task['test:decorators'].invoke
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ module Draper
2
+ module DeviseHelper
3
+ def sign_in(user)
4
+ warden.stub :authenticate! => user
5
+ controller.stub :current_user => user
6
+ user
7
+ end
8
+
9
+ private
10
+
11
+ def request
12
+ @request ||= ::ActionDispatch::TestRequest.new
13
+ end
14
+
15
+ def controller
16
+ return @controller if @controller
17
+ @controller = ApplicationController.new
18
+ @controller.request = request
19
+ ::Draper::ViewContext.current = @controller.view_context
20
+ @controller
21
+ end
22
+
23
+ # taken from Devise's helper but uses the request method instead of @request
24
+ # and we don't really need the rest of their helper
25
+ def warden
26
+ @warden ||= begin
27
+ manager = Warden::Manager.new(nil) do |config|
28
+ config.merge! Devise.warden_config
29
+ end
30
+ request.env['warden'] = Warden::Proxy.new(request.env, manager)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,7 +1,6 @@
1
- class MiniTest::Rails::ActiveSupport::TestCase
2
- # Use AS::TestCase for the base class when describing a decorator
1
+ class Draper::TestCase
3
2
  register_spec_type(self) do |desc|
4
- desc < Draper::Decorator if desc.is_a?(Class)
3
+ desc < Draper::Decorator || desc < Draper::CollectionDecorator if desc.is_a?(Class)
5
4
  end
6
5
  register_spec_type(/Decorator( ?Test)?\z/i, self)
7
6
  end
@@ -1,67 +1,12 @@
1
1
  module Draper
2
2
  module DecoratorExampleGroup
3
+ include Draper::TestCase::Behavior
3
4
  extend ActiveSupport::Concern
4
- included { metadata[:type] = :decorator }
5
- end
6
-
7
- module DeviseHelper
8
- def sign_in(user)
9
- warden.stub :authenticate! => user
10
- controller.stub :current_user => user
11
- user
12
- end
13
-
14
- private
15
5
 
16
- def request
17
- @request ||= ::ActionDispatch::TestRequest.new
18
- end
19
-
20
- def controller
21
- return @controller if @controller
22
- @controller = ApplicationController.new
23
- @controller.request = request
24
- ::Draper::ViewContext.current = @controller.view_context
25
- @controller
26
- end
27
-
28
- # taken from Devise's helper but uses the request method instead of @request
29
- # and we don't really need the rest of their helper
30
- def warden
31
- @warden ||= begin
32
- manager = Warden::Manager.new(nil) do |config|
33
- config.merge! Devise.warden_config
34
- end
35
- request.env['warden'] = Warden::Proxy.new(request.env, manager)
36
- end
37
- end
38
- end
39
- end
40
-
41
- RSpec.configure do |config|
42
- # Automatically tag specs in specs/decorators as type: :decorator
43
- config.include Draper::DecoratorExampleGroup, :type => :decorator, :example_group => {
44
- :file_path => /spec[\\\/]decorators/
45
- }
46
-
47
- if defined?(Devise)
48
- config.include Draper::DeviseHelper, :type => :decorator
6
+ included { metadata[:type] = :decorator }
49
7
  end
50
- end
51
8
 
52
- module Draper
53
- module RSpec
54
- class Railtie < Rails::Railtie
55
- config.after_initialize do |app|
56
- if defined?(Capybara)
57
- require 'capybara/rspec/matchers'
58
-
59
- ::RSpec.configure do |config|
60
- config.include Capybara::RSpecMatchers, :type => :decorator
61
- end
62
- end
63
- end
64
- end
9
+ RSpec.configure do |config|
10
+ config.include DecoratorExampleGroup, example_group: {file_path: %r{spec/decorators}}, type: :decorator
65
11
  end
66
12
  end
67
-
@@ -0,0 +1,33 @@
1
+ module Draper
2
+ begin
3
+ require 'minitest/rails'
4
+ rescue LoadError
5
+ end
6
+
7
+ active_support_test_case = begin
8
+ require 'minitest/rails/active_support' # minitest-rails < 0.5
9
+ ::MiniTest::Rails::ActiveSupport::TestCase
10
+ rescue LoadError
11
+ require 'active_support/test_case'
12
+ ::ActiveSupport::TestCase
13
+ end
14
+
15
+ class TestCase < active_support_test_case
16
+ module Behavior
17
+ if defined?(::Devise)
18
+ require 'draper/test/devise_helper'
19
+ include Draper::DeviseHelper
20
+ end
21
+
22
+ if defined?(::Capybara) && (defined?(::RSpec) || defined?(::MiniTest::Matchers))
23
+ require 'capybara/rspec/matchers'
24
+ include ::Capybara::RSpecMatchers
25
+ end
26
+
27
+ include Draper::ViewHelpers::ClassMethods
28
+ alias_method :helper, :helpers
29
+ end
30
+
31
+ include Behavior
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module Draper
2
- VERSION = "1.0.0.beta6"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -1,4 +1,6 @@
1
1
  module Draper
2
+ # Provides the {#helpers} method used in {Decorator} and {CollectionDecorator}
3
+ # to call the Rails helpers.
2
4
  module ViewHelpers
3
5
  extend ActiveSupport::Concern
4
6
 
@@ -24,9 +26,8 @@ module Draper
24
26
  end
25
27
  alias_method :h, :helpers
26
28
 
27
- # Localize is something that's used quite often. Even though
28
- # it's available through helpers, that's annoying. Aliased
29
- # to `l` for convenience.
29
+ # Alias for `helpers.localize`, since localize is something that's used
30
+ # quite often. Further aliased to `l` for convenience.
30
31
  def localize(*args)
31
32
  helpers.localize(*args)
32
33
  end
@@ -4,34 +4,16 @@ class <%= class_name %>Decorator < <%= parent_class_name %>
4
4
  <%- else -%>
5
5
  class <%= class_name %>
6
6
  <%- end -%>
7
+ delegate_all
7
8
 
8
- # Accessing Helpers
9
- # You can access any helper via a proxy
10
- #
11
- # Normal Usage: helpers.number_to_currency(2)
12
- # Abbreviated : h.number_to_currency(2)
13
- #
14
- # Or, optionally enable "lazy helpers" by including this module:
15
- # include Draper::LazyHelpers
16
- # Then use the helpers with no proxy:
17
- # number_to_currency(2)
18
-
19
- # Defining an Interface
20
- # Control access to the wrapped subject's methods using one of the following:
21
- #
22
- # To allow only the listed methods (whitelist):
23
- # allows :method1, :method2
24
- #
25
- # To allow everything except the listed methods (blacklist):
26
- # denies :method1, :method2
27
-
28
- # Presentation Methods
29
- # Define your own instance methods, even overriding accessors
30
- # generated by ActiveRecord:
9
+ # Define presentation-specific methods here. Helpers are accessed through
10
+ # `helpers` (aka `h`). You can override attributes, for example:
31
11
  #
32
12
  # def created_at
33
- # h.content_tag :span, attributes["created_at"].strftime("%a %m/%d/%y"),
34
- # :class => 'timestamp'
13
+ # helpers.content_tag :span, class: 'time' do
14
+ # source.created_at.strftime("%a %m/%d/%y")
15
+ # end
35
16
  # end
17
+
36
18
  end
37
19
  <% end -%>
@@ -0,0 +1,20 @@
1
+ require 'generators/mini_test'
2
+
3
+ module MiniTest
4
+ module Generators
5
+ class DecoratorGenerator < Base
6
+ def self.source_root
7
+ File.expand_path('../templates', __FILE__)
8
+ end
9
+
10
+ class_option :spec, :type => :boolean, :default => false, :desc => "Use MiniTest::Spec DSL"
11
+
12
+ check_class_collision suffix: "DecoratorTest"
13
+
14
+ def create_test_file
15
+ template_type = options[:spec] ? "spec" : "test"
16
+ template "decorator_#{template_type}.rb", File.join("test/decorators", class_path, "#{singular_name}_decorator_test.rb")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ require 'minitest_helper'
2
+
3
+ describe <%= class_name %>Decorator do
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'minitest_helper'
2
+
3
+ class <%= class_name %>DecoratorTest < Draper::TestCase
4
+ end
@@ -1,4 +1,4 @@
1
1
  require 'test_helper'
2
2
 
3
- class <%= class_name %>DecoratorTest < ActiveSupport::TestCase
3
+ class <%= class_name %>DecoratorTest < Draper::TestCase
4
4
  end
@@ -76,7 +76,7 @@ describe Draper::CollectionDecorator do
76
76
  end
77
77
 
78
78
  it "raises error on invalid options" do
79
- expect { Draper::CollectionDecorator.new(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
79
+ expect { Draper::CollectionDecorator.new(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, /Unknown key/)
80
80
  end
81
81
  end
82
82
  end
@@ -127,6 +127,20 @@ describe Draper::CollectionDecorator do
127
127
  end
128
128
  end
129
129
 
130
+ describe ".delegate" do
131
+ subject { Class.new(Draper::CollectionDecorator) }
132
+
133
+ it "defaults the :to option to :source" do
134
+ Draper::CollectionDecorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :source)
135
+ subject.delegate :foo, :bar
136
+ end
137
+
138
+ it "does not overwrite the :to option if supplied" do
139
+ Draper::CollectionDecorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :baz)
140
+ subject.delegate :foo, :bar, to: :baz
141
+ end
142
+ end
143
+
130
144
  describe "#find" do
131
145
  context "with a block" do
132
146
  it "decorates Enumerable#find" do
@@ -248,7 +262,7 @@ describe Draper::CollectionDecorator do
248
262
  let(:options) { {with: ProductDecorator} }
249
263
 
250
264
  it "returns a string representation of the CollectionDecorator" do
251
- subject.to_s.should == '#<CollectionDecorator of ProductDecorator for ["a", "b", "c"]>'
265
+ subject.to_s.should == '#<Draper::CollectionDecorator of ProductDecorator for ["a", "b", "c"]>'
252
266
  end
253
267
  end
254
268
 
@@ -256,7 +270,15 @@ describe Draper::CollectionDecorator do
256
270
  let(:options) { {} }
257
271
 
258
272
  it "returns a string representation of the CollectionDecorator" do
259
- subject.to_s.should == '#<CollectionDecorator of inferred decorators for ["a", "b", "c"]>'
273
+ subject.to_s.should == '#<Draper::CollectionDecorator of inferred decorators for ["a", "b", "c"]>'
274
+ end
275
+ end
276
+
277
+ context "for a custom subclass" do
278
+ subject { ProductsDecorator.new(source) }
279
+
280
+ it "uses the custom class name" do
281
+ subject.to_s.should =~ /ProductsDecorator/
260
282
  end
261
283
  end
262
284
  end
@@ -15,7 +15,7 @@ describe Draper::DecoratedAssociation do
15
15
  end
16
16
 
17
17
  it "raises error on invalid options" do
18
- expect { Draper::DecoratedAssociation.new(owner, :association, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
18
+ expect { Draper::DecoratedAssociation.new(owner, :association, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, /Unknown key/)
19
19
  end
20
20
  end
21
21
  end
@@ -31,10 +31,10 @@ describe Draper::DecoratedAssociation do
31
31
 
32
32
  context "for a singular association" do
33
33
  let(:associated) { Product.new }
34
+ let(:decorator) { SpecificProductDecorator }
34
35
 
35
36
  context "when :with option was given" do
36
37
  let(:options) { {with: decorator} }
37
- let(:decorator) { SpecificProductDecorator }
38
38
 
39
39
  it "uses the specified decorator" do
40
40
  decorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated)
@@ -44,7 +44,8 @@ describe Draper::DecoratedAssociation do
44
44
 
45
45
  context "when :with option was not given" do
46
46
  it "infers the decorator" do
47
- associated.should_receive(:decorate).with(expected_options).and_return(:decorated)
47
+ associated.stub(:decorator_class).and_return(decorator)
48
+ decorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated)
48
49
  decorated_association.call.should be :decorated
49
50
  end
50
51
  end
@@ -52,10 +53,10 @@ describe Draper::DecoratedAssociation do
52
53
 
53
54
  context "for a collection association" do
54
55
  let(:associated) { [Product.new, Widget.new] }
56
+ let(:collection_decorator) { ProductsDecorator }
55
57
 
56
58
  context "when :with option is a collection decorator" do
57
59
  let(:options) { {with: collection_decorator} }
58
- let(:collection_decorator) { ProductsDecorator }
59
60
 
60
61
  it "uses the specified decorator" do
61
62
  collection_decorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated_collection)
@@ -74,9 +75,19 @@ describe Draper::DecoratedAssociation do
74
75
  end
75
76
 
76
77
  context "when :with option was not given" do
77
- it "uses a CollectionDecorator of inferred decorators" do
78
- Draper::CollectionDecorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated_collection)
79
- decorated_association.call.should be :decorated_collection
78
+ context "when the collection is decoratable" do
79
+ it "infers the decorator" do
80
+ associated.stub(:decorator_class).and_return(collection_decorator)
81
+ collection_decorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated_collection)
82
+ decorated_association.call.should be :decorated_collection
83
+ end
84
+ end
85
+
86
+ context "when the collection is not decoratable" do
87
+ it "uses a CollectionDecorator of inferred decorators" do
88
+ Draper::CollectionDecorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated_collection)
89
+ decorated_association.call.should be :decorated_collection
90
+ end
80
91
  end
81
92
  end
82
93
  end