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
@@ -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