draper 1.0.0 → 1.1.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 (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