draper 1.0.0.beta6 → 1.0.0

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