draper 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/CHANGELOG.md +3 -0
  2. data/CONTRIBUTING.md +5 -1
  3. data/Gemfile +23 -9
  4. data/README.md +144 -52
  5. data/Rakefile +1 -0
  6. data/draper.gemspec +1 -1
  7. data/lib/draper.rb +9 -6
  8. data/lib/draper/decoratable.rb +3 -7
  9. data/lib/draper/decoratable/equality.rb +14 -0
  10. data/lib/draper/decorator.rb +4 -7
  11. data/lib/draper/helper_proxy.rb +22 -3
  12. data/lib/draper/test/devise_helper.rb +18 -22
  13. data/lib/draper/test/rspec_integration.rb +4 -0
  14. data/lib/draper/test_case.rb +20 -0
  15. data/lib/draper/version.rb +1 -1
  16. data/lib/draper/view_context.rb +75 -13
  17. data/lib/draper/view_context/build_strategy.rb +48 -0
  18. data/lib/draper/view_helpers.rb +2 -2
  19. data/spec/draper/collection_decorator_spec.rb +169 -196
  20. data/spec/draper/decoratable/equality_spec.rb +10 -0
  21. data/spec/draper/decoratable_spec.rb +107 -132
  22. data/spec/draper/decorated_association_spec.rb +99 -96
  23. data/spec/draper/decorator_spec.rb +408 -434
  24. data/spec/draper/finders_spec.rb +160 -126
  25. data/spec/draper/helper_proxy_spec.rb +38 -8
  26. data/spec/draper/view_context/build_strategy_spec.rb +116 -0
  27. data/spec/draper/view_context_spec.rb +154 -0
  28. data/spec/draper/view_helpers_spec.rb +4 -37
  29. data/spec/dummy/app/controllers/posts_controller.rb +7 -0
  30. data/spec/dummy/app/decorators/post_decorator.rb +26 -2
  31. data/spec/dummy/app/helpers/application_helper.rb +3 -0
  32. data/spec/dummy/app/mailers/post_mailer.rb +10 -0
  33. data/spec/dummy/app/models/admin.rb +5 -0
  34. data/spec/dummy/app/models/mongoid_post.rb +5 -0
  35. data/spec/dummy/app/models/user.rb +5 -0
  36. data/spec/dummy/app/views/posts/_post.html.erb +15 -0
  37. data/spec/dummy/bin/rails +4 -0
  38. data/spec/dummy/config/application.rb +9 -3
  39. data/spec/dummy/config/boot.rb +2 -7
  40. data/spec/dummy/config/environments/development.rb +2 -3
  41. data/spec/dummy/config/environments/production.rb +2 -0
  42. data/spec/dummy/config/environments/test.rb +3 -4
  43. data/spec/dummy/config/initializers/secret_token.rb +1 -0
  44. data/spec/dummy/config/mongoid.yml +80 -0
  45. data/spec/dummy/config/routes.rb +2 -0
  46. data/spec/dummy/fast_spec/post_decorator_spec.rb +38 -0
  47. data/spec/dummy/lib/tasks/test.rake +11 -5
  48. data/spec/dummy/spec/decorators/devise_spec.rb +64 -0
  49. data/spec/dummy/spec/decorators/helpers_spec.rb +21 -0
  50. data/spec/dummy/spec/decorators/post_decorator_spec.rb +26 -6
  51. data/spec/dummy/spec/decorators/spec_type_spec.rb +7 -0
  52. data/spec/dummy/spec/decorators/view_context_spec.rb +22 -0
  53. data/spec/dummy/spec/mailers/post_mailer_spec.rb +10 -6
  54. data/spec/dummy/spec/models/mongoid_post_spec.rb +10 -0
  55. data/spec/dummy/spec/models/post_spec.rb +5 -5
  56. data/spec/dummy/spec/spec_helper.rb +1 -0
  57. data/spec/dummy/test/decorators/minitest/devise_test.rb +64 -0
  58. data/spec/dummy/test/decorators/minitest/helpers_test.rb +21 -0
  59. data/spec/dummy/{mini_test/mini_test_integration_test.rb → test/decorators/minitest/spec_type_test.rb} +9 -3
  60. data/spec/dummy/test/decorators/minitest/view_context_test.rb +24 -0
  61. data/spec/dummy/test/decorators/test_unit/devise_test.rb +64 -0
  62. data/spec/dummy/test/decorators/test_unit/helpers_test.rb +21 -0
  63. data/spec/dummy/test/decorators/test_unit/view_context_test.rb +24 -0
  64. data/spec/dummy/test/minitest_helper.rb +4 -0
  65. data/spec/dummy/test/test_helper.rb +3 -0
  66. data/spec/generators/decorator/decorator_generator_spec.rb +1 -0
  67. data/spec/integration/integration_spec.rb +31 -6
  68. data/spec/spec_helper.rb +32 -25
  69. data/spec/support/shared_examples/decoratable_equality.rb +40 -0
  70. data/spec/support/shared_examples/view_helpers.rb +39 -0
  71. metadata +56 -44
  72. data/spec/dummy/README.rdoc +0 -261
  73. data/spec/dummy/spec/decorators/rspec_integration_spec.rb +0 -19
  74. data/spec/support/action_controller.rb +0 -12
  75. data/spec/support/active_model.rb +0 -7
  76. data/spec/support/active_record.rb +0 -9
  77. data/spec/support/decorators/decorator_with_application_helper.rb +0 -25
  78. data/spec/support/decorators/namespaced_product_decorator.rb +0 -5
  79. data/spec/support/decorators/non_active_model_product_decorator.rb +0 -2
  80. data/spec/support/decorators/product_decorator.rb +0 -23
  81. data/spec/support/decorators/products_decorator.rb +0 -10
  82. data/spec/support/decorators/some_thing_decorator.rb +0 -2
  83. data/spec/support/decorators/specific_product_decorator.rb +0 -2
  84. data/spec/support/decorators/widget_decorator.rb +0 -2
  85. data/spec/support/models/namespaced_product.rb +0 -49
  86. data/spec/support/models/non_active_model_product.rb +0 -3
  87. data/spec/support/models/product.rb +0 -95
  88. data/spec/support/models/some_thing.rb +0 -5
  89. data/spec/support/models/uninferrable_decorator_model.rb +0 -3
  90. data/spec/support/models/widget.rb +0 -2
data/Rakefile CHANGED
@@ -60,6 +60,7 @@ end
60
60
  namespace "db" do
61
61
  desc "Set up databases for integration testing"
62
62
  task "setup" do
63
+ puts "Setting up databases"
63
64
  run_in_dummy_app "rm -f db/*.sqlite3"
64
65
  run_in_dummy_app "RAILS_ENV=development rake db:schema:load db:seed"
65
66
  run_in_dummy_app "RAILS_ENV=production rake db:schema:load db:seed"
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
9
9
  s.email = ["jeff@casimircreative.com", "steve@steveklabnik.com"]
10
10
  s.homepage = "http://github.com/drapergem/draper"
11
11
  s.summary = "View Models for Rails"
12
- s.description = "Draper adds a nicely-separated object-oriented layer of presentation logic to your Rails apps."
12
+ s.description = "Draper adds an object-oriented layer of presentation logic to your Rails apps."
13
13
  s.rubyforge_project = "draper"
14
14
  s.files = `git ls-files`.split("\n")
15
15
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -1,4 +1,8 @@
1
1
  require 'action_view'
2
+ require 'active_model/serialization'
3
+ require 'active_support/inflector'
4
+ require 'active_support/core_ext/hash/keys'
5
+ require 'active_support/core_ext/hash/reverse_merge'
2
6
 
3
7
  require 'draper/version'
4
8
  require 'draper/view_helpers'
@@ -15,17 +19,16 @@ require 'draper/view_context'
15
19
  require 'draper/collection_decorator'
16
20
  require 'draper/railtie' if defined?(Rails)
17
21
 
18
- require 'active_support/core_ext/hash/keys'
19
-
20
22
  module Draper
21
23
  def self.setup_action_controller(base)
22
24
  base.class_eval do
23
25
  include Draper::ViewContext
24
26
  extend Draper::HelperSupport
25
- before_filter ->(controller) {
26
- Draper::ViewContext.current = nil
27
- Draper::ViewContext.current_controller = controller
28
- }
27
+
28
+ before_filter do |controller|
29
+ Draper::ViewContext.clear!
30
+ Draper::ViewContext.controller = controller
31
+ end
29
32
  end
30
33
  end
31
34
 
@@ -1,3 +1,5 @@
1
+ require 'draper/decoratable/equality'
2
+
1
3
  module Draper
2
4
  # Provides shortcuts to decorate objects directly, so you can do
3
5
  # `@product.decorate` instead of `ProductDecorator.new(@product)`.
@@ -7,6 +9,7 @@ module Draper
7
9
  # plain old Ruby objects, you can include it manually.
8
10
  module Decoratable
9
11
  extend ActiveSupport::Concern
12
+ include Draper::Decoratable::Equality
10
13
 
11
14
  # Decorates the object using the inferred {#decorator_class}.
12
15
  # @param [Hash] options
@@ -40,13 +43,6 @@ module Draper
40
43
  false
41
44
  end
42
45
 
43
- # Compares with possibly-decorated objects.
44
- #
45
- # @return [Boolean]
46
- def ==(other)
47
- super || (other.respond_to?(:source) && self == other.source)
48
- end
49
-
50
46
  module ClassMethods
51
47
 
52
48
  # Decorates a collection of objects. Used at the end of a scope chain.
@@ -0,0 +1,14 @@
1
+ module Draper
2
+ module Decoratable
3
+ module Equality
4
+ # Compares self with a possibly-decorated object.
5
+ #
6
+ # @return [Boolean]
7
+ def ==(other)
8
+ super ||
9
+ other.respond_to?(:decorated?) && other.decorated? &&
10
+ other.respond_to?(:source) && self == other.source
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,10 +1,8 @@
1
- require 'active_support/core_ext/array/extract_options'
2
-
3
1
  module Draper
4
2
  class Decorator
5
3
  include Draper::ViewHelpers
6
4
  extend Draper::Delegation
7
- include ActiveModel::Serialization if defined?(ActiveModel::Serialization)
5
+ include ActiveModel::Serialization
8
6
 
9
7
  # @return the object being decorated.
10
8
  attr_reader :source
@@ -27,7 +25,6 @@ module Draper
27
25
  # methods.
28
26
  def initialize(source, options = {})
29
27
  options.assert_valid_keys(:context)
30
- source.to_a if source.respond_to?(:to_a) # forces evaluation of a lazy query from AR
31
28
  @source = source
32
29
  @context = options.fetch(:context, {})
33
30
  handle_multiple_decoration(options) if source.instance_of?(self.class)
@@ -159,11 +156,11 @@ module Draper
159
156
  true
160
157
  end
161
158
 
162
- # Delegated to the source object.
159
+ # Compares the source with a possibly-decorated object.
163
160
  #
164
161
  # @return [Boolean]
165
162
  def ==(other)
166
- source == (other.respond_to?(:source) ? other.source : other)
163
+ source.extend(Draper::Decoratable::Equality) == other
167
164
  end
168
165
 
169
166
  # Checks if `self.kind_of?(klass)` or `source.kind_of?(klass)`
@@ -191,7 +188,7 @@ module Draper
191
188
  end
192
189
 
193
190
  # ActiveModel compatibility
194
- delegate :to_param, :to_partial_path
191
+ delegate :attributes, :to_param, :to_partial_path
195
192
 
196
193
  # ActiveModel compatibility
197
194
  singleton_class.delegate :model_name, to: :source_class
@@ -3,15 +3,34 @@ module Draper
3
3
  # defined in your application.
4
4
  class HelperProxy
5
5
 
6
+ # @overload initialize(view_context)
7
+ def initialize(view_context = nil)
8
+ view_context ||= current_view_context # backwards compatibility
9
+
10
+ @view_context = view_context
11
+ end
12
+
6
13
  # Sends helper methods to the view context.
7
14
  def method_missing(method, *args, &block)
8
- view_context.send(method, *args, &block)
15
+ self.class.define_proxy method
16
+ send(method, *args, &block)
9
17
  end
10
18
 
19
+ protected
20
+
21
+ attr_reader :view_context
22
+
11
23
  private
12
24
 
13
- def view_context
14
- Draper::ViewContext.current
25
+ def self.define_proxy(name)
26
+ define_method name do |*args, &block|
27
+ view_context.send(name, *args, &block)
28
+ end
29
+ end
30
+
31
+ def current_view_context
32
+ ActiveSupport::Deprecation.warn("wrong number of arguments (0 for 1) passed to Draper::HelperProxy.new", caller[1..-1])
33
+ Draper::ViewContext.current.view_context
15
34
  end
16
35
  end
17
36
  end
@@ -1,33 +1,29 @@
1
1
  module Draper
2
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
3
+ def sign_in(resource_or_scope, resource = nil)
4
+ scope = begin
5
+ Devise::Mapping.find_scope!(resource_or_scope)
6
+ rescue RuntimeError => e
7
+ # Draper 1.0 didn't require the mapping to exist
8
+ ActiveSupport::Deprecation.warn("#{e.message}.\nUse `sign_in :user, mock_user` instead.", caller)
9
+ :user
10
+ end
10
11
 
11
- def request
12
- @request ||= ::ActionDispatch::TestRequest.new
12
+ _stub_current_scope scope, resource || resource_or_scope
13
13
  end
14
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
15
+ def sign_out(resource_or_scope)
16
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
17
+ _stub_current_scope scope, nil
21
18
  end
22
19
 
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
20
+ private
21
+
22
+ def _stub_current_scope(scope, resource)
23
+ Draper::ViewContext.current.controller.singleton_class.class_eval do
24
+ define_method "current_#{scope}" do
25
+ resource
29
26
  end
30
- request.env['warden'] = Warden::Proxy.new(request.env, manager)
31
27
  end
32
28
  end
33
29
  end
@@ -8,5 +8,9 @@ module Draper
8
8
 
9
9
  RSpec.configure do |config|
10
10
  config.include DecoratorExampleGroup, example_group: {file_path: %r{spec/decorators}}, type: :decorator
11
+
12
+ [:decorator, :controller, :mailer].each do |type|
13
+ config.after(:each, type: type) { Draper::ViewContext.clear! }
14
+ end
11
15
  end
12
16
  end
@@ -13,6 +13,13 @@ module Draper
13
13
  end
14
14
 
15
15
  class TestCase < active_support_test_case
16
+ module ViewContextTeardown
17
+ def teardown
18
+ super
19
+ Draper::ViewContext.clear!
20
+ end
21
+ end
22
+
16
23
  module Behavior
17
24
  if defined?(::Devise)
18
25
  require 'draper/test/devise_helper'
@@ -29,5 +36,18 @@ module Draper
29
36
  end
30
37
 
31
38
  include Behavior
39
+ include ViewContextTeardown
40
+ end
41
+ end
42
+
43
+ if defined?(ActionController::TestCase)
44
+ class ActionController::TestCase
45
+ include Draper::TestCase::ViewContextTeardown
46
+ end
47
+ end
48
+
49
+ if defined?(ActionMailer::TestCase)
50
+ class ActionMailer::TestCase
51
+ include Draper::TestCase::ViewContextTeardown
32
52
  end
33
53
  end
@@ -1,3 +1,3 @@
1
1
  module Draper
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -1,37 +1,99 @@
1
+ require 'draper/view_context/build_strategy'
1
2
  require 'request_store'
2
3
 
3
4
  module Draper
4
5
  module ViewContext
6
+ # Hooks into a controller or mailer to save the view context in {current}.
5
7
  def view_context
6
8
  super.tap do |context|
7
9
  Draper::ViewContext.current = context
8
10
  end
9
11
  end
10
12
 
11
- def self.current_controller
12
- RequestStore.store[:current_controller] || ApplicationController.new
13
+ # Returns the current controller.
14
+ def self.controller
15
+ RequestStore.store[:current_controller]
13
16
  end
14
17
 
15
- def self.current_controller=(controller)
18
+ # Sets the current controller.
19
+ def self.controller=(controller)
16
20
  RequestStore.store[:current_controller] = controller
17
21
  end
18
22
 
23
+ # Returns the current view context, or builds one if none is saved.
24
+ #
25
+ # @return [HelperProxy]
19
26
  def self.current
20
- RequestStore.store[:current_view_context] ||= build_view_context
27
+ RequestStore.store.fetch(:current_view_context) { build! }
28
+ end
29
+
30
+ # Sets the current view context.
31
+ def self.current=(view_context)
32
+ RequestStore.store[:current_view_context] = Draper::HelperProxy.new(view_context)
33
+ end
34
+
35
+ # Clears the saved controller and view context.
36
+ def self.clear!
37
+ RequestStore.store.delete :current_controller
38
+ RequestStore.store.delete :current_view_context
39
+ end
40
+
41
+ # Builds a new view context for usage in tests. See {test_strategy} for
42
+ # details of how the view context is built.
43
+ def self.build
44
+ build_strategy.call
45
+ end
46
+
47
+ # Builds a new view context and sets it as the current view context.
48
+ #
49
+ # @return [HelperProxy]
50
+ def self.build!
51
+ # send because we want to return the HelperProxy returned from #current=
52
+ send :current=, build
21
53
  end
22
54
 
23
- def self.current=(context)
24
- RequestStore.store[:current_view_context] = context
55
+ # Configures the strategy used to build view contexts in tests, which
56
+ # defaults to `:full` if `test_strategy` has not been called. Evaluates
57
+ # the block, if given, in the context of the view context's class.
58
+ #
59
+ # @example Pass a block to add helper methods to the view context:
60
+ # Draper::ViewContext.test_strategy :fast do
61
+ # include ApplicationHelper
62
+ # end
63
+ #
64
+ # @param [:full, :fast] name
65
+ # the strategy to use:
66
+ #
67
+ # `:full` - build a fully-working view context. Your Rails environment
68
+ # must be loaded, including your `ApplicationController`.
69
+ #
70
+ # `:fast` - build a minimal view context in tests, with no dependencies
71
+ # on other components of your application.
72
+ def self.test_strategy(name, &block)
73
+ @build_strategy = Draper::ViewContext::BuildStrategy.new(name, &block)
25
74
  end
26
75
 
76
+ # @private
77
+ def self.build_strategy
78
+ @build_strategy ||= Draper::ViewContext::BuildStrategy.new(:full)
79
+ end
80
+
81
+ # @deprecated Use {controller} instead.
82
+ def self.current_controller
83
+ ActiveSupport::Deprecation.warn("Draper::ViewContext.current_controller is deprecated (use controller instead)", caller)
84
+ self.controller || ApplicationController.new
85
+ end
86
+
87
+ # @deprecated Use {controller=} instead.
88
+ def self.current_controller=(controller)
89
+ ActiveSupport::Deprecation.warn("Draper::ViewContext.current_controller= is deprecated (use controller instead)", caller)
90
+ self.controller = controller
91
+ end
92
+
93
+ # @deprecated Use {build} instead.
27
94
  def self.build_view_context
28
- current_controller.view_context.tap do |context|
29
- if defined?(ActionController::TestRequest)
30
- context.controller.request ||= ActionController::TestRequest.new
31
- context.request ||= context.controller.request
32
- context.params ||= {}
33
- end
34
- end
95
+ ActiveSupport::Deprecation.warn("Draper::ViewContext.build_view_context is deprecated (use build instead)", caller)
96
+ build
35
97
  end
36
98
  end
37
99
  end
@@ -0,0 +1,48 @@
1
+ module Draper
2
+ module ViewContext
3
+ # @private
4
+ module BuildStrategy
5
+
6
+ def self.new(name, &block)
7
+ const_get(name.to_s.camelize).new(&block)
8
+ end
9
+
10
+ class Fast
11
+ def initialize(&block)
12
+ @view_context_class = Class.new(ActionView::Base, &block)
13
+ end
14
+
15
+ def call
16
+ view_context_class.new
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :view_context_class
22
+ end
23
+
24
+ class Full
25
+ def initialize(&block)
26
+ @block = block
27
+ end
28
+
29
+ def call
30
+ controller.view_context.tap do |context|
31
+ context.singleton_class.class_eval(&block) if block
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :block
38
+
39
+ def controller
40
+ (Draper::ViewContext.controller || ApplicationController.new).tap do |controller|
41
+ controller.request ||= ActionController::TestRequest.new if defined?(ActionController::TestRequest)
42
+ end
43
+ end
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -11,7 +11,7 @@ module Draper
11
11
  #
12
12
  # @return [HelperProxy] the helpers proxy
13
13
  def helpers
14
- @helpers ||= Draper::HelperProxy.new
14
+ Draper::ViewContext.current
15
15
  end
16
16
  alias_method :h, :helpers
17
17
 
@@ -22,7 +22,7 @@ module Draper
22
22
  #
23
23
  # @return [HelperProxy] the helpers proxy
24
24
  def helpers
25
- self.class.helpers
25
+ Draper::ViewContext.current
26
26
  end
27
27
  alias_method :h, :helpers
28
28