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
@@ -0,0 +1,154 @@
1
+ require 'spec_helper'
2
+
3
+ module Draper
4
+ describe ViewContext do
5
+ describe "#view_context" do
6
+ let(:base) { Class.new { def view_context; :controller_view_context; end } }
7
+ let(:controller) { Class.new(base) { include ViewContext } }
8
+
9
+ it "saves the superclass's view context" do
10
+ ViewContext.should_receive(:current=).with(:controller_view_context)
11
+ controller.new.view_context
12
+ end
13
+
14
+ it "returns the superclass's view context" do
15
+ expect(controller.new.view_context).to be :controller_view_context
16
+ end
17
+ end
18
+
19
+ describe ".controller" do
20
+ it "returns the stored controller from RequestStore" do
21
+ RequestStore.stub store: {current_controller: :stored_controller}
22
+
23
+ expect(ViewContext.controller).to be :stored_controller
24
+ end
25
+ end
26
+
27
+ describe ".controller=" do
28
+ it "stores a controller in RequestStore" do
29
+ store = {}
30
+ RequestStore.stub store: store
31
+
32
+ ViewContext.controller = :stored_controller
33
+ expect(store[:current_controller]).to be :stored_controller
34
+ end
35
+ end
36
+
37
+ describe ".current" do
38
+ it "returns the stored view context from RequestStore" do
39
+ RequestStore.stub store: {current_view_context: :stored_view_context}
40
+
41
+ expect(ViewContext.current).to be :stored_view_context
42
+ end
43
+
44
+ context "when no view context is stored" do
45
+ it "builds a view context" do
46
+ RequestStore.stub store: {}
47
+ ViewContext.stub build_strategy: ->{ :new_view_context }
48
+ HelperProxy.stub(:new).with(:new_view_context).and_return(:new_helper_proxy)
49
+
50
+ expect(ViewContext.current).to be :new_helper_proxy
51
+ end
52
+
53
+ it "stores the built view context" do
54
+ store = {}
55
+ RequestStore.stub store: store
56
+ ViewContext.stub build_strategy: ->{ :new_view_context }
57
+ HelperProxy.stub(:new).with(:new_view_context).and_return(:new_helper_proxy)
58
+
59
+ ViewContext.current
60
+ expect(store[:current_view_context]).to be :new_helper_proxy
61
+ end
62
+ end
63
+ end
64
+
65
+ describe ".current=" do
66
+ it "stores a helper proxy for the view context in RequestStore" do
67
+ store = {}
68
+ RequestStore.stub store: store
69
+ HelperProxy.stub(:new).with(:stored_view_context).and_return(:stored_helper_proxy)
70
+
71
+ ViewContext.current = :stored_view_context
72
+ expect(store[:current_view_context]).to be :stored_helper_proxy
73
+ end
74
+ end
75
+
76
+ describe ".clear!" do
77
+ it "clears the stored controller and view controller" do
78
+ store = {current_controller: :stored_controller, current_view_context: :stored_view_context}
79
+ RequestStore.stub store: store
80
+
81
+ ViewContext.clear!
82
+ expect(store).not_to have_key :current_controller
83
+ expect(store).not_to have_key :current_view_context
84
+ end
85
+ end
86
+
87
+ describe ".build" do
88
+ it "returns a new view context using the build strategy" do
89
+ ViewContext.stub build_strategy: ->{ :new_view_context }
90
+
91
+ expect(ViewContext.build).to be :new_view_context
92
+ end
93
+ end
94
+
95
+ describe ".build!" do
96
+ it "returns a helper proxy for the new view context" do
97
+ ViewContext.stub build_strategy: ->{ :new_view_context }
98
+ HelperProxy.stub(:new).with(:new_view_context).and_return(:new_helper_proxy)
99
+
100
+ expect(ViewContext.build!).to be :new_helper_proxy
101
+ end
102
+
103
+ it "stores the helper proxy" do
104
+ store = {}
105
+ RequestStore.stub store: store
106
+ ViewContext.stub build_strategy: ->{ :new_view_context }
107
+ HelperProxy.stub(:new).with(:new_view_context).and_return(:new_helper_proxy)
108
+
109
+ ViewContext.build!
110
+ expect(store[:current_view_context]).to be :new_helper_proxy
111
+ end
112
+ end
113
+
114
+ describe ".build_strategy" do
115
+ it "defaults to full" do
116
+ expect(ViewContext.build_strategy).to be_a ViewContext::BuildStrategy::Full
117
+ end
118
+
119
+ it "memoizes" do
120
+ expect(ViewContext.build_strategy).to be ViewContext.build_strategy
121
+ end
122
+ end
123
+
124
+ describe ".test_strategy" do
125
+ protect_module ViewContext
126
+
127
+ context "with :fast" do
128
+ it "creates a fast strategy" do
129
+ ViewContext.test_strategy :fast
130
+ expect(ViewContext.build_strategy).to be_a ViewContext::BuildStrategy::Fast
131
+ end
132
+
133
+ it "passes a block to the strategy" do
134
+ ViewContext::BuildStrategy::Fast.stub(:new).and_return{|&block| block.call}
135
+
136
+ expect(ViewContext.test_strategy(:fast){:passed}).to be :passed
137
+ end
138
+ end
139
+
140
+ context "with :full" do
141
+ it "creates a full strategy" do
142
+ ViewContext.test_strategy :full
143
+ expect(ViewContext.build_strategy).to be_a ViewContext::BuildStrategy::Full
144
+ end
145
+
146
+ it "passes a block to the strategy" do
147
+ ViewContext::BuildStrategy::Full.stub(:new).and_return{|&block| block.call}
148
+
149
+ expect(ViewContext.test_strategy(:full){:passed}).to be :passed
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -1,41 +1,8 @@
1
1
  require 'spec_helper'
2
+ require 'support/shared_examples/view_helpers'
2
3
 
3
- describe Draper::ViewHelpers do
4
- subject(:view_helpers_class) { Class.new { include Draper::ViewHelpers } }
5
-
6
- let(:view_helpers) { view_helpers_class.new }
7
- let(:helper_proxy) { Draper::HelperProxy.new }
8
- let(:view_context) { Object.new }
9
-
10
- before { view_helpers.helpers.stub(:view_context).and_return(view_context) }
11
-
12
- describe "#helpers" do
13
- it "returns a HelperProxy" do
14
- view_helpers.helpers.should be_a Draper::HelperProxy
15
- end
16
-
17
- it "is aliased to #h" do
18
- view_helpers.h.should be view_helpers_class.helpers
19
- end
20
- end
21
-
22
- it "delegates #localize to #helpers" do
23
- view_context.should_receive(:localize).with(Date.new)
24
- view_helpers.localize(Date.new)
25
- end
26
-
27
- it "aliases #l to #localize" do
28
- view_context.should_receive(:localize).with(Date.new)
29
- view_helpers.l(Date.new)
30
- end
31
-
32
- describe ".helpers" do
33
- it "returns a HelperProxy" do
34
- view_helpers_class.helpers.should be_a Draper::HelperProxy
35
- end
36
-
37
- it "is aliased to #h" do
38
- view_helpers_class.h.should be view_helpers_class.helpers
39
- end
4
+ module Draper
5
+ describe ViewHelpers do
6
+ it_behaves_like "view helpers", Class.new{include ViewHelpers}.new
40
7
  end
41
8
  end
@@ -8,4 +8,11 @@ class PostsController < ApplicationController
8
8
  email = PostMailer.decorated_email(post).deliver
9
9
  render text: email.body
10
10
  end
11
+
12
+ private
13
+
14
+ def goodnight_moon
15
+ "Goodnight, moon!"
16
+ end
17
+ helper_method :goodnight_moon
11
18
  end
@@ -1,9 +1,13 @@
1
1
  class PostDecorator < Draper::Decorator
2
+ # don't delegate_all here because it helps to identify things we
3
+ # have to delegate for ActiveModel compatibility
4
+
5
+ # need to delegate attribute methods for AM::Serialization
2
6
  # need to delegate id and new_record? for AR::Base#== (Rails 3.0 only)
3
- delegate :id, :new_record?
7
+ delegate :id, :created_at, :new_record?
4
8
 
5
9
  def posted_date
6
- if source.created_at.to_date == DateTime.now.utc.to_date
10
+ if created_at.to_date == DateTime.now.utc.to_date
7
11
  "Today"
8
12
  else
9
13
  "Not Today"
@@ -29,4 +33,24 @@ class PostDecorator < Draper::Decorator
29
33
  def link
30
34
  h.link_to id.to_s, self
31
35
  end
36
+
37
+ def truncated
38
+ h.truncate("Once upon a time in a world far far away", length: 17, separator: ' ')
39
+ end
40
+
41
+ def html_escaped
42
+ h.html_escape("<script>danger</script>")
43
+ end
44
+
45
+ def hello_world
46
+ h.hello_world
47
+ end
48
+
49
+ def goodnight_moon
50
+ h.goodnight_moon
51
+ end
52
+
53
+ def updated_at
54
+ :overridden
55
+ end
32
56
  end
@@ -1,2 +1,5 @@
1
1
  module ApplicationHelper
2
+ def hello_world
3
+ "Hello, world!"
4
+ end
2
5
  end
@@ -2,8 +2,18 @@ class PostMailer < ApplicationMailer
2
2
  default from: "from@example.com"
3
3
  layout "application"
4
4
 
5
+ # Mailers don't import app/helpers automatically
6
+ helper :application
7
+
5
8
  def decorated_email(post)
6
9
  @post = post.decorate
7
10
  mail to: "to@example.com", subject: "A decorated post"
8
11
  end
12
+
13
+ private
14
+
15
+ def goodnight_moon
16
+ "Goodnight, moon!"
17
+ end
18
+ helper_method :goodnight_moon
9
19
  end
@@ -0,0 +1,5 @@
1
+ if defined?(Devise)
2
+ class Admin
3
+ extend Devise::Models
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ if defined?(Mongoid)
2
+ class MongoidPost
3
+ include Mongoid::Document
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ if defined?(Devise)
2
+ class User
3
+ extend Devise::Models
4
+ end
5
+ end
@@ -2,9 +2,24 @@
2
2
  <dt>Environment:</dt>
3
3
  <dd id="environment"><%= Rails.env %></dd>
4
4
 
5
+ <dt>Draper view context controller:</dt>
6
+ <dd id="controller"><%= Draper::ViewContext.current.controller.class %></dd>
7
+
5
8
  <dt>Posted:</dt>
6
9
  <dd id="posted_date"><%= post.posted_date %></dd>
7
10
 
11
+ <dt>Built-in helpers:</dt>
12
+ <dd id="truncated"><%= post.truncated %></dd>
13
+
14
+ <dt>Built-in private helpers:</dt>
15
+ <dd id="html_escaped"><%= post.html_escaped %></dd>
16
+
17
+ <dt>Helpers from app/helpers:</dt>
18
+ <dd id="hello_world"><%= post.hello_world %></dd>
19
+
20
+ <dt>Helpers from the controller:</dt>
21
+ <dd id="goodnight_moon"><%= post.goodnight_moon %></dd>
22
+
8
23
  <dt>Path with model:</dt>
9
24
  <dd id="path_with_model"><%= post.path_with_model %></dd>
10
25
 
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'
@@ -1,9 +1,14 @@
1
1
  require File.expand_path('../boot', __FILE__)
2
2
 
3
- require 'rails/all'
3
+ def attempt_require(file)
4
+ require file
5
+ rescue LoadError
6
+ end
4
7
 
5
- Bundler.require
6
- require "draper"
8
+ require 'rails/all'
9
+ require 'draper'
10
+ attempt_require 'mongoid'
11
+ attempt_require 'devise'
7
12
 
8
13
  module Dummy
9
14
  class Application < Rails::Application
@@ -62,3 +67,4 @@ module Dummy
62
67
  end
63
68
  end
64
69
 
70
+ ActiveRecord::Migration.verbose = false
@@ -1,10 +1,5 @@
1
1
  require 'rubygems'
2
- gemfile = File.expand_path('../../../../Gemfile', __FILE__)
3
2
 
4
- if File.exist?(gemfile)
5
- ENV['BUNDLE_GEMFILE'] = gemfile
6
- require 'bundler'
7
- Bundler.setup
8
- end
3
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
9
4
 
10
- $:.unshift File.expand_path('../../../../lib', __FILE__)
5
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
@@ -6,9 +6,6 @@ Dummy::Application.configure do
6
6
  # since you don't have to restart the web server when you make code changes.
7
7
  config.cache_classes = false
8
8
 
9
- # Log error messages when you accidentally call methods on nil.
10
- config.whiny_nils = true
11
-
12
9
  # Show full error reports and disable caching
13
10
  config.consider_all_requests_local = true
14
11
  config.action_controller.perform_caching = false
@@ -19,6 +16,8 @@ Dummy::Application.configure do
19
16
  # Only use best-standards-support built into browsers
20
17
  config.action_dispatch.best_standards_support = :builtin
21
18
 
19
+ config.eager_load = false
20
+
22
21
  # Raise exception on mass assignment protection for Active Record models
23
22
  # config.active_record.mass_assignment_sanitizer = :strict
24
23
 
@@ -11,6 +11,8 @@ Dummy::Application.configure do
11
11
  # Disable Rails's static asset server (Apache or nginx will already do this)
12
12
  config.serve_static_assets = false
13
13
 
14
+ config.eager_load = true
15
+
14
16
  # Defaults to nil and saved in location specified by config.assets.prefix
15
17
  # config.assets.manifest = YOUR_PATH
16
18
 
@@ -9,10 +9,7 @@ Dummy::Application.configure do
9
9
 
10
10
  # Configure static asset server for tests with Cache-Control for performance
11
11
  # config.serve_static_assets = true
12
- config.static_cache_control = "public, max-age=3600"
13
-
14
- # Log error messages when you accidentally call methods on nil
15
- config.whiny_nils = true
12
+ # config.static_cache_control = "public, max-age=3600"
16
13
 
17
14
  # Show full error reports and disable caching
18
15
  config.consider_all_requests_local = true
@@ -29,4 +26,6 @@ Dummy::Application.configure do
29
26
 
30
27
  # Print deprecation notices to the stderr
31
28
  config.active_support.deprecation = :stderr
29
+
30
+ config.eager_load = false
32
31
  end
@@ -4,4 +4,5 @@
4
4
  # If you change this key, all old signed cookies will become invalid!
5
5
  # Make sure the secret is at least 30 characters and all random,
6
6
  # no regular words or you'll be exposed to dictionary attacks.
7
+ Dummy::Application.config.secret_key_base = 'c2e3474d3816f60bf6dd0f3b983e7283c7ff5373e11a96935340b544a31964dbe5ee077136165ee2975e0005f5e80207c0059e6d5589699031242ba5a06dcb87'
7
8
  Dummy::Application.config.secret_token = 'c2e3474d3816f60bf6dd0f3b983e7283c7ff5373e11a96935340b544a31964dbe5ee077136165ee2975e0005f5e80207c0059e6d5589699031242ba5a06dcb87'
@@ -0,0 +1,80 @@
1
+ development:
2
+ # Configure available database sessions. (required)
3
+ sessions:
4
+ # Defines the default session. (required)
5
+ default:
6
+ # Defines the name of the default database that Mongoid can connect to.
7
+ # (required).
8
+ database: dummy_development
9
+ # Provides the hosts the default session can connect to. Must be an array
10
+ # of host:port pairs. (required)
11
+ hosts:
12
+ - localhost:27017
13
+ options:
14
+ # Change whether the session persists in safe mode by default.
15
+ # (default: false)
16
+ # safe: false
17
+
18
+ # Change the default consistency model to :eventual or :strong.
19
+ # :eventual will send reads to secondaries, :strong sends everything
20
+ # to master. (default: :eventual)
21
+ # consistency: :eventual
22
+
23
+ # How many times Moped should attempt to retry an operation after
24
+ # failure. (default: 30)
25
+ # max_retries: 30
26
+
27
+ # The time in seconds that Moped should wait before retrying an
28
+ # operation on failure. (default: 1)
29
+ # retry_interval: 1
30
+ # Configure Mongoid specific options. (optional)
31
+ options:
32
+ # Configuration for whether or not to allow access to fields that do
33
+ # not have a field definition on the model. (default: true)
34
+ # allow_dynamic_fields: true
35
+
36
+ # Enable the identity map, needed for eager loading. (default: false)
37
+ # identity_map_enabled: false
38
+
39
+ # Includes the root model name in json serialization. (default: false)
40
+ # include_root_in_json: false
41
+
42
+ # Include the _type field in serializaion. (default: false)
43
+ # include_type_for_serialization: false
44
+
45
+ # Preload all models in development, needed when models use
46
+ # inheritance. (default: false)
47
+ # preload_models: false
48
+
49
+ # Protect id and type from mass assignment. (default: true)
50
+ # protect_sensitive_fields: true
51
+
52
+ # Raise an error when performing a #find and the document is not found.
53
+ # (default: true)
54
+ # raise_not_found_error: true
55
+
56
+ # Raise an error when defining a scope with the same name as an
57
+ # existing method. (default: false)
58
+ # scope_overwrite_exception: false
59
+
60
+ # Skip the database version check, used when connecting to a db without
61
+ # admin access. (default: false)
62
+ # skip_version_check: false
63
+
64
+ # User Active Support's time zone in conversions. (default: true)
65
+ # use_activesupport_time_zone: true
66
+
67
+ # Ensure all times are UTC in the app side. (default: false)
68
+ # use_utc: false
69
+ test:
70
+ sessions:
71
+ default:
72
+ database: dummy_test
73
+ hosts:
74
+ - localhost:27017
75
+ options:
76
+ consistency: :strong
77
+ # In the test environment we lower the retries and retry interval to
78
+ # low amounts for fast failures.
79
+ max_retries: 1
80
+ retry_interval: 0