halorgium-actionpack 3.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5179 -0
- data/MIT-LICENSE +21 -0
- data/README +409 -0
- data/lib/abstract_controller.rb +16 -0
- data/lib/abstract_controller/base.rb +158 -0
- data/lib/abstract_controller/callbacks.rb +113 -0
- data/lib/abstract_controller/exceptions.rb +12 -0
- data/lib/abstract_controller/helpers.rb +151 -0
- data/lib/abstract_controller/layouts.rb +250 -0
- data/lib/abstract_controller/localized_cache.rb +49 -0
- data/lib/abstract_controller/logger.rb +61 -0
- data/lib/abstract_controller/rendering_controller.rb +188 -0
- data/lib/action_controller.rb +72 -0
- data/lib/action_controller/base.rb +168 -0
- data/lib/action_controller/caching.rb +80 -0
- data/lib/action_controller/caching/actions.rb +163 -0
- data/lib/action_controller/caching/fragments.rb +116 -0
- data/lib/action_controller/caching/pages.rb +154 -0
- data/lib/action_controller/caching/sweeping.rb +97 -0
- data/lib/action_controller/deprecated.rb +4 -0
- data/lib/action_controller/deprecated/integration_test.rb +2 -0
- data/lib/action_controller/deprecated/performance_test.rb +1 -0
- data/lib/action_controller/dispatch/dispatcher.rb +57 -0
- data/lib/action_controller/metal.rb +129 -0
- data/lib/action_controller/metal/benchmarking.rb +73 -0
- data/lib/action_controller/metal/compatibility.rb +145 -0
- data/lib/action_controller/metal/conditional_get.rb +86 -0
- data/lib/action_controller/metal/configuration.rb +28 -0
- data/lib/action_controller/metal/cookies.rb +105 -0
- data/lib/action_controller/metal/exceptions.rb +55 -0
- data/lib/action_controller/metal/filter_parameter_logging.rb +77 -0
- data/lib/action_controller/metal/flash.rb +162 -0
- data/lib/action_controller/metal/head.rb +27 -0
- data/lib/action_controller/metal/helpers.rb +115 -0
- data/lib/action_controller/metal/hide_actions.rb +47 -0
- data/lib/action_controller/metal/http_authentication.rb +312 -0
- data/lib/action_controller/metal/layouts.rb +171 -0
- data/lib/action_controller/metal/mime_responds.rb +317 -0
- data/lib/action_controller/metal/rack_convenience.rb +27 -0
- data/lib/action_controller/metal/redirector.rb +22 -0
- data/lib/action_controller/metal/render_options.rb +103 -0
- data/lib/action_controller/metal/rendering_controller.rb +57 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +108 -0
- data/lib/action_controller/metal/rescuable.rb +13 -0
- data/lib/action_controller/metal/responder.rb +200 -0
- data/lib/action_controller/metal/session.rb +15 -0
- data/lib/action_controller/metal/session_management.rb +45 -0
- data/lib/action_controller/metal/streaming.rb +188 -0
- data/lib/action_controller/metal/testing.rb +39 -0
- data/lib/action_controller/metal/url_for.rb +41 -0
- data/lib/action_controller/metal/verification.rb +130 -0
- data/lib/action_controller/middleware.rb +38 -0
- data/lib/action_controller/notifications.rb +10 -0
- data/lib/action_controller/polymorphic_routes.rb +183 -0
- data/lib/action_controller/record_identifier.rb +91 -0
- data/lib/action_controller/testing/process.rb +111 -0
- data/lib/action_controller/testing/test_case.rb +345 -0
- data/lib/action_controller/translation.rb +13 -0
- data/lib/action_controller/url_rewriter.rb +204 -0
- data/lib/action_controller/vendor/html-scanner.rb +16 -0
- data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
- data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +176 -0
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
- data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
- data/lib/action_dispatch.rb +70 -0
- data/lib/action_dispatch/http/headers.rb +33 -0
- data/lib/action_dispatch/http/mime_type.rb +231 -0
- data/lib/action_dispatch/http/mime_types.rb +23 -0
- data/lib/action_dispatch/http/request.rb +539 -0
- data/lib/action_dispatch/http/response.rb +290 -0
- data/lib/action_dispatch/http/status_codes.rb +42 -0
- data/lib/action_dispatch/http/utils.rb +20 -0
- data/lib/action_dispatch/middleware/callbacks.rb +50 -0
- data/lib/action_dispatch/middleware/params_parser.rb +79 -0
- data/lib/action_dispatch/middleware/rescue.rb +26 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +208 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +235 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +47 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +143 -0
- data/lib/action_dispatch/middleware/stack.rb +116 -0
- data/lib/action_dispatch/middleware/static.rb +44 -0
- data/lib/action_dispatch/middleware/string_coercion.rb +29 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +26 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +10 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +29 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +2 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +10 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +21 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +2 -0
- data/lib/action_dispatch/routing.rb +381 -0
- data/lib/action_dispatch/routing/deprecated_mapper.rb +878 -0
- data/lib/action_dispatch/routing/mapper.rb +327 -0
- data/lib/action_dispatch/routing/route.rb +49 -0
- data/lib/action_dispatch/routing/route_set.rb +497 -0
- data/lib/action_dispatch/testing/assertions.rb +8 -0
- data/lib/action_dispatch/testing/assertions/dom.rb +35 -0
- data/lib/action_dispatch/testing/assertions/model.rb +19 -0
- data/lib/action_dispatch/testing/assertions/response.rb +145 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +144 -0
- data/lib/action_dispatch/testing/assertions/selector.rb +639 -0
- data/lib/action_dispatch/testing/assertions/tag.rb +123 -0
- data/lib/action_dispatch/testing/integration.rb +504 -0
- data/lib/action_dispatch/testing/performance_test.rb +15 -0
- data/lib/action_dispatch/testing/test_request.rb +83 -0
- data/lib/action_dispatch/testing/test_response.rb +131 -0
- data/lib/action_pack.rb +24 -0
- data/lib/action_pack/version.rb +9 -0
- data/lib/action_view.rb +58 -0
- data/lib/action_view/base.rb +308 -0
- data/lib/action_view/context.rb +44 -0
- data/lib/action_view/erb/util.rb +48 -0
- data/lib/action_view/helpers.rb +62 -0
- data/lib/action_view/helpers/active_model_helper.rb +306 -0
- data/lib/action_view/helpers/ajax_helper.rb +68 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +830 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
- data/lib/action_view/helpers/cache_helper.rb +39 -0
- data/lib/action_view/helpers/capture_helper.rb +168 -0
- data/lib/action_view/helpers/date_helper.rb +988 -0
- data/lib/action_view/helpers/debug_helper.rb +38 -0
- data/lib/action_view/helpers/form_helper.rb +1102 -0
- data/lib/action_view/helpers/form_options_helper.rb +600 -0
- data/lib/action_view/helpers/form_tag_helper.rb +495 -0
- data/lib/action_view/helpers/javascript_helper.rb +208 -0
- data/lib/action_view/helpers/number_helper.rb +311 -0
- data/lib/action_view/helpers/prototype_helper.rb +1309 -0
- data/lib/action_view/helpers/raw_output_helper.rb +9 -0
- data/lib/action_view/helpers/record_identification_helper.rb +20 -0
- data/lib/action_view/helpers/record_tag_helper.rb +58 -0
- data/lib/action_view/helpers/sanitize_helper.rb +259 -0
- data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
- data/lib/action_view/helpers/tag_helper.rb +151 -0
- data/lib/action_view/helpers/text_helper.rb +594 -0
- data/lib/action_view/helpers/translation_helper.rb +39 -0
- data/lib/action_view/helpers/url_helper.rb +639 -0
- data/lib/action_view/locale/en.yml +117 -0
- data/lib/action_view/paths.rb +80 -0
- data/lib/action_view/render/partials.rb +342 -0
- data/lib/action_view/render/rendering.rb +134 -0
- data/lib/action_view/safe_buffer.rb +28 -0
- data/lib/action_view/template/error.rb +101 -0
- data/lib/action_view/template/handler.rb +36 -0
- data/lib/action_view/template/handlers.rb +52 -0
- data/lib/action_view/template/handlers/builder.rb +17 -0
- data/lib/action_view/template/handlers/erb.rb +53 -0
- data/lib/action_view/template/handlers/rjs.rb +18 -0
- data/lib/action_view/template/resolver.rb +165 -0
- data/lib/action_view/template/template.rb +131 -0
- data/lib/action_view/template/text.rb +38 -0
- data/lib/action_view/test_case.rb +163 -0
- metadata +236 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
module ActionController
|
2
|
+
class Middleware < Metal
|
3
|
+
class ActionMiddleware
|
4
|
+
def initialize(controller, app)
|
5
|
+
@controller, @app = controller, app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
@controller.build(@app).dispatch(:index, env)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
alias build new
|
15
|
+
|
16
|
+
def new(app)
|
17
|
+
ActionMiddleware.new(self, app)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_internal :app
|
22
|
+
|
23
|
+
def process(action)
|
24
|
+
response = super
|
25
|
+
self.status, self.headers, self.response_body = response if response.is_a?(Array)
|
26
|
+
response
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(app)
|
30
|
+
super()
|
31
|
+
@_app = app
|
32
|
+
end
|
33
|
+
|
34
|
+
def index
|
35
|
+
call(env)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'active_support/notifications'
|
2
|
+
|
3
|
+
ActiveSupport::Notifications.subscribe(/(read|write|cache|expire|exist)_(fragment|page)\??/) do |*args|
|
4
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
5
|
+
|
6
|
+
if logger = ActionController::Base.logger
|
7
|
+
human_name = event.name.to_s.humanize
|
8
|
+
logger.info("#{human_name} (%.1fms)" % event.duration)
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
module ActionController
|
2
|
+
# Polymorphic URL helpers are methods for smart resolution to a named route call when
|
3
|
+
# given an Active Record model instance. They are to be used in combination with
|
4
|
+
# ActionController::Resources.
|
5
|
+
#
|
6
|
+
# These methods are useful when you want to generate correct URL or path to a RESTful
|
7
|
+
# resource without having to know the exact type of the record in question.
|
8
|
+
#
|
9
|
+
# Nested resources and/or namespaces are also supported, as illustrated in the example:
|
10
|
+
#
|
11
|
+
# polymorphic_url([:admin, @article, @comment])
|
12
|
+
#
|
13
|
+
# results in:
|
14
|
+
#
|
15
|
+
# admin_article_comment_url(@article, @comment)
|
16
|
+
#
|
17
|
+
# == Usage within the framework
|
18
|
+
#
|
19
|
+
# Polymorphic URL helpers are used in a number of places throughout the Rails framework:
|
20
|
+
#
|
21
|
+
# * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
|
22
|
+
# <tt>url_for(@article)</tt>;
|
23
|
+
# * ActionView::Helpers::FormHelper uses <tt>polymorphic_path</tt>, so you can write
|
24
|
+
# <tt>form_for(@article)</tt> without having to specify <tt>:url</tt> parameter for the form
|
25
|
+
# action;
|
26
|
+
# * <tt>redirect_to</tt> (which, in fact, uses <tt>url_for</tt>) so you can write
|
27
|
+
# <tt>redirect_to(post)</tt> in your controllers;
|
28
|
+
# * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
|
29
|
+
# for feed entries.
|
30
|
+
#
|
31
|
+
# == Prefixed polymorphic helpers
|
32
|
+
#
|
33
|
+
# In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
|
34
|
+
# number of prefixed helpers are available as a shorthand to <tt>:action => "..."</tt>
|
35
|
+
# in options. Those are:
|
36
|
+
#
|
37
|
+
# * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
|
38
|
+
# * <tt>new_polymorphic_url</tt>, <tt>new_polymorphic_path</tt>
|
39
|
+
#
|
40
|
+
# Example usage:
|
41
|
+
#
|
42
|
+
# edit_polymorphic_path(@post) # => "/posts/1/edit"
|
43
|
+
# polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
|
44
|
+
module PolymorphicRoutes
|
45
|
+
# Constructs a call to a named RESTful route for the given record and returns the
|
46
|
+
# resulting URL string. For example:
|
47
|
+
#
|
48
|
+
# # calls post_url(post)
|
49
|
+
# polymorphic_url(post) # => "http://example.com/posts/1"
|
50
|
+
# polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
|
51
|
+
# polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
|
52
|
+
# polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
|
53
|
+
# polymorphic_url(Comment) # => "http://example.com/comments"
|
54
|
+
#
|
55
|
+
# ==== Options
|
56
|
+
#
|
57
|
+
# * <tt>:action</tt> - Specifies the action prefix for the named route:
|
58
|
+
# <tt>:new</tt> or <tt>:edit</tt>. Default is no prefix.
|
59
|
+
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
|
60
|
+
# Default is <tt>:url</tt>.
|
61
|
+
#
|
62
|
+
# ==== Examples
|
63
|
+
#
|
64
|
+
# # an Article record
|
65
|
+
# polymorphic_url(record) # same as article_url(record)
|
66
|
+
#
|
67
|
+
# # a Comment record
|
68
|
+
# polymorphic_url(record) # same as comment_url(record)
|
69
|
+
#
|
70
|
+
# # it recognizes new records and maps to the collection
|
71
|
+
# record = Comment.new
|
72
|
+
# polymorphic_url(record) # same as comments_url()
|
73
|
+
#
|
74
|
+
# # the class of a record will also map to the collection
|
75
|
+
# polymorphic_url(Comment) # same as comments_url()
|
76
|
+
#
|
77
|
+
def polymorphic_url(record_or_hash_or_array, options = {})
|
78
|
+
if record_or_hash_or_array.kind_of?(Array)
|
79
|
+
record_or_hash_or_array = record_or_hash_or_array.compact
|
80
|
+
record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
|
81
|
+
end
|
82
|
+
|
83
|
+
record = extract_record(record_or_hash_or_array)
|
84
|
+
record = record.to_model if record.respond_to?(:to_model)
|
85
|
+
|
86
|
+
args = case record_or_hash_or_array
|
87
|
+
when Hash; [ record_or_hash_or_array ]
|
88
|
+
when Array; record_or_hash_or_array.dup
|
89
|
+
else [ record_or_hash_or_array ]
|
90
|
+
end
|
91
|
+
|
92
|
+
inflection = if options[:action].to_s == "new"
|
93
|
+
args.pop
|
94
|
+
:singular
|
95
|
+
elsif (record.respond_to?(:new_record?) && record.new_record?) ||
|
96
|
+
(record.respond_to?(:destroyed?) && record.destroyed?)
|
97
|
+
args.pop
|
98
|
+
:plural
|
99
|
+
elsif record.is_a?(Class)
|
100
|
+
args.pop
|
101
|
+
:plural
|
102
|
+
else
|
103
|
+
:singular
|
104
|
+
end
|
105
|
+
|
106
|
+
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
|
107
|
+
named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
|
108
|
+
|
109
|
+
url_options = options.except(:action, :routing_type)
|
110
|
+
unless url_options.empty?
|
111
|
+
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
|
112
|
+
end
|
113
|
+
|
114
|
+
__send__(named_route, *args)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns the path component of a URL for the given record. It uses
|
118
|
+
# <tt>polymorphic_url</tt> with <tt>:routing_type => :path</tt>.
|
119
|
+
def polymorphic_path(record_or_hash_or_array, options = {})
|
120
|
+
polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
|
121
|
+
end
|
122
|
+
|
123
|
+
%w(edit new).each do |action|
|
124
|
+
module_eval <<-EOT, __FILE__, __LINE__
|
125
|
+
def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
|
126
|
+
polymorphic_url( # polymorphic_url(
|
127
|
+
record_or_hash, # record_or_hash,
|
128
|
+
options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
|
129
|
+
end # end
|
130
|
+
#
|
131
|
+
def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
|
132
|
+
polymorphic_url( # polymorphic_url(
|
133
|
+
record_or_hash, # record_or_hash,
|
134
|
+
options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
|
135
|
+
end # end
|
136
|
+
EOT
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
def action_prefix(options)
|
141
|
+
options[:action] ? "#{options[:action]}_" : ''
|
142
|
+
end
|
143
|
+
|
144
|
+
def routing_type(options)
|
145
|
+
options[:routing_type] || :url
|
146
|
+
end
|
147
|
+
|
148
|
+
def build_named_route_call(records, inflection, options = {})
|
149
|
+
unless records.is_a?(Array)
|
150
|
+
record = extract_record(records)
|
151
|
+
route = ''
|
152
|
+
else
|
153
|
+
record = records.pop
|
154
|
+
route = records.inject("") do |string, parent|
|
155
|
+
if parent.is_a?(Symbol) || parent.is_a?(String)
|
156
|
+
string << "#{parent}_"
|
157
|
+
else
|
158
|
+
string << RecordIdentifier.__send__("plural_class_name", parent).singularize
|
159
|
+
string << "_"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
if record.is_a?(Symbol) || record.is_a?(String)
|
165
|
+
route << "#{record}_"
|
166
|
+
else
|
167
|
+
route << RecordIdentifier.__send__("plural_class_name", record)
|
168
|
+
route = route.singularize if inflection == :singular
|
169
|
+
route << "_"
|
170
|
+
end
|
171
|
+
|
172
|
+
action_prefix(options) + route + routing_type(options).to_s
|
173
|
+
end
|
174
|
+
|
175
|
+
def extract_record(record_or_hash_or_array)
|
176
|
+
case record_or_hash_or_array
|
177
|
+
when Array; record_or_hash_or_array.last
|
178
|
+
when Hash; record_or_hash_or_array[:id]
|
179
|
+
else record_or_hash_or_array
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'active_support/core_ext/module'
|
2
|
+
|
3
|
+
module ActionController
|
4
|
+
# The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
|
5
|
+
# Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate
|
6
|
+
# the view actions to a higher logical level. Example:
|
7
|
+
#
|
8
|
+
# # routes
|
9
|
+
# map.resources :posts
|
10
|
+
#
|
11
|
+
# # view
|
12
|
+
# <% div_for(post) do %> <div id="post_45" class="post">
|
13
|
+
# <%= post.body %> What a wonderful world!
|
14
|
+
# <% end %> </div>
|
15
|
+
#
|
16
|
+
# # controller
|
17
|
+
# def destroy
|
18
|
+
# post = Post.find(params[:id])
|
19
|
+
# post.destroy
|
20
|
+
#
|
21
|
+
# respond_to do |format|
|
22
|
+
# format.html { redirect_to(post) } # Calls polymorphic_url(post) which in turn calls post_url(post)
|
23
|
+
# format.js do
|
24
|
+
# # Calls: new Effect.fade('post_45');
|
25
|
+
# render(:update) { |page| page[post].visual_effect(:fade) }
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
|
31
|
+
# that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
|
32
|
+
# convention and allows you to write less code if you follow it.
|
33
|
+
module RecordIdentifier
|
34
|
+
extend self
|
35
|
+
|
36
|
+
JOIN = '_'.freeze
|
37
|
+
NEW = 'new'.freeze
|
38
|
+
|
39
|
+
# The DOM class convention is to use the singular form of an object or class. Examples:
|
40
|
+
#
|
41
|
+
# dom_class(post) # => "post"
|
42
|
+
# dom_class(Person) # => "person"
|
43
|
+
#
|
44
|
+
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_class:
|
45
|
+
#
|
46
|
+
# dom_class(post, :edit) # => "edit_post"
|
47
|
+
# dom_class(Person, :edit) # => "edit_person"
|
48
|
+
def dom_class(record_or_class, prefix = nil)
|
49
|
+
singular = singular_class_name(record_or_class)
|
50
|
+
prefix ? "#{prefix}#{JOIN}#{singular}" : singular
|
51
|
+
end
|
52
|
+
|
53
|
+
# The DOM id convention is to use the singular form of an object or class with the id following an underscore.
|
54
|
+
# If no id is found, prefix with "new_" instead. Examples:
|
55
|
+
#
|
56
|
+
# dom_id(Post.find(45)) # => "post_45"
|
57
|
+
# dom_id(Post.new) # => "new_post"
|
58
|
+
#
|
59
|
+
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
|
60
|
+
#
|
61
|
+
# dom_id(Post.find(45), :edit) # => "edit_post_45"
|
62
|
+
def dom_id(record, prefix = nil)
|
63
|
+
if record_id = record.id
|
64
|
+
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
|
65
|
+
else
|
66
|
+
dom_class(record, prefix || NEW)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the plural class name of a record or class. Examples:
|
71
|
+
#
|
72
|
+
# plural_class_name(post) # => "posts"
|
73
|
+
# plural_class_name(Highrise::Person) # => "highrise_people"
|
74
|
+
def plural_class_name(record_or_class)
|
75
|
+
model_name_from_record_or_class(record_or_class).plural
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the singular class name of a record or class. Examples:
|
79
|
+
#
|
80
|
+
# singular_class_name(post) # => "post"
|
81
|
+
# singular_class_name(Highrise::Person) # => "highrise_person"
|
82
|
+
def singular_class_name(record_or_class)
|
83
|
+
model_name_from_record_or_class(record_or_class).singular
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def model_name_from_record_or_class(record_or_class)
|
88
|
+
(record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'active_support/core_ext/object/conversions'
|
2
|
+
require "rack/test"
|
3
|
+
|
4
|
+
module ActionController #:nodoc:
|
5
|
+
# Essentially generates a modified Tempfile object similar to the object
|
6
|
+
# you'd get from the standard library CGI module in a multipart
|
7
|
+
# request. This means you can use an ActionController::TestUploadedFile
|
8
|
+
# object in the params of a test request in order to simulate
|
9
|
+
# a file upload.
|
10
|
+
#
|
11
|
+
# Usage example, within a functional test:
|
12
|
+
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(ActionController::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
|
13
|
+
#
|
14
|
+
# Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows):
|
15
|
+
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(ActionController::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary)
|
16
|
+
TestUploadedFile = Rack::Test::UploadedFile
|
17
|
+
|
18
|
+
module TestProcess
|
19
|
+
def assigns(key = nil)
|
20
|
+
assigns = {}
|
21
|
+
@controller.instance_variable_names.each do |ivar|
|
22
|
+
next if ActionController::Base.protected_instance_variables.include?(ivar)
|
23
|
+
assigns[ivar[1..-1]] = @controller.instance_variable_get(ivar)
|
24
|
+
end
|
25
|
+
|
26
|
+
key.nil? ? assigns : assigns[key.to_s]
|
27
|
+
end
|
28
|
+
|
29
|
+
def session
|
30
|
+
@request.session
|
31
|
+
end
|
32
|
+
|
33
|
+
def flash
|
34
|
+
@request.flash
|
35
|
+
end
|
36
|
+
|
37
|
+
def cookies
|
38
|
+
@request.cookies.merge(@response.cookies)
|
39
|
+
end
|
40
|
+
|
41
|
+
def redirect_to_url
|
42
|
+
@response.redirect_url
|
43
|
+
end
|
44
|
+
|
45
|
+
def html_document
|
46
|
+
xml = @response.content_type =~ /xml$/
|
47
|
+
@html_document ||= HTML::Document.new(@response.body, false, xml)
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_tag(conditions)
|
51
|
+
html_document.find(conditions)
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_all_tag(conditions)
|
55
|
+
html_document.find_all(conditions)
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing(selector, *args, &block)
|
59
|
+
if @controller && ActionController::Routing::Routes.named_routes.helpers.include?(selector)
|
60
|
+
@controller.send(selector, *args, &block)
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Shortcut for <tt>ActionController::TestUploadedFile.new(ActionController::TestCase.fixture_path + path, type)</tt>:
|
67
|
+
#
|
68
|
+
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
|
69
|
+
#
|
70
|
+
# To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
|
71
|
+
# This will not affect other platforms:
|
72
|
+
#
|
73
|
+
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
|
74
|
+
def fixture_file_upload(path, mime_type = nil, binary = false)
|
75
|
+
fixture_path = ActionController::TestCase.send(:fixture_path) if ActionController::TestCase.respond_to?(:fixture_path)
|
76
|
+
ActionController::TestUploadedFile.new("#{fixture_path}#{path}", mime_type, binary)
|
77
|
+
end
|
78
|
+
|
79
|
+
# A helper to make it easier to test different route configurations.
|
80
|
+
# This method temporarily replaces ActionController::Routing::Routes
|
81
|
+
# with a new RouteSet instance.
|
82
|
+
#
|
83
|
+
# The new instance is yielded to the passed block. Typically the block
|
84
|
+
# will create some routes using <tt>map.draw { map.connect ... }</tt>:
|
85
|
+
#
|
86
|
+
# with_routing do |set|
|
87
|
+
# set.draw do |map|
|
88
|
+
# map.connect ':controller/:action/:id'
|
89
|
+
# assert_equal(
|
90
|
+
# ['/content/10/show', {}],
|
91
|
+
# map.generate(:controller => 'content', :id => 10, :action => 'show')
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
def with_routing
|
97
|
+
real_routes = ActionController::Routing::Routes
|
98
|
+
ActionController::Routing.module_eval { remove_const :Routes }
|
99
|
+
|
100
|
+
temporary_routes = ActionController::Routing::RouteSet.new
|
101
|
+
ActionController::Routing.module_eval { const_set :Routes, temporary_routes }
|
102
|
+
|
103
|
+
yield temporary_routes
|
104
|
+
ensure
|
105
|
+
if ActionController::Routing.const_defined? :Routes
|
106
|
+
ActionController::Routing.module_eval { remove_const :Routes }
|
107
|
+
end
|
108
|
+
ActionController::Routing.const_set(:Routes, real_routes) if real_routes
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,345 @@
|
|
1
|
+
require 'active_support/test_case'
|
2
|
+
require 'rack/session/abstract/id'
|
3
|
+
|
4
|
+
module ActionController
|
5
|
+
class TestRequest < ActionDispatch::TestRequest #:nodoc:
|
6
|
+
def initialize(env = {})
|
7
|
+
super
|
8
|
+
|
9
|
+
self.session = TestSession.new
|
10
|
+
self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16))
|
11
|
+
end
|
12
|
+
|
13
|
+
class Result < ::Array #:nodoc:
|
14
|
+
def to_s() join '/' end
|
15
|
+
def self.new_escaped(strings)
|
16
|
+
new strings.collect {|str| URI.unescape str}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def assign_parameters(controller_path, action, parameters = {})
|
21
|
+
parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
|
22
|
+
extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
|
23
|
+
non_path_parameters = get? ? query_parameters : request_parameters
|
24
|
+
parameters.each do |key, value|
|
25
|
+
if value.is_a? Fixnum
|
26
|
+
value = value.to_s
|
27
|
+
elsif value.is_a? Array
|
28
|
+
value = Result.new(value)
|
29
|
+
end
|
30
|
+
|
31
|
+
if extra_keys.include?(key.to_sym)
|
32
|
+
non_path_parameters[key] = value
|
33
|
+
else
|
34
|
+
path_parameters[key.to_s] = value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
params = self.request_parameters.dup
|
39
|
+
|
40
|
+
%w(controller action only_path).each do |k|
|
41
|
+
params.delete(k)
|
42
|
+
params.delete(k.to_sym)
|
43
|
+
end
|
44
|
+
|
45
|
+
data = params.to_query
|
46
|
+
@env['CONTENT_LENGTH'] = data.length.to_s
|
47
|
+
@env['rack.input'] = StringIO.new(data)
|
48
|
+
end
|
49
|
+
|
50
|
+
def recycle!
|
51
|
+
@formats = nil
|
52
|
+
@env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
|
53
|
+
@env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
|
54
|
+
@env['action_dispatch.request.query_parameters'] = {}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class TestResponse < ActionDispatch::TestResponse
|
59
|
+
def recycle!
|
60
|
+
@status = 200
|
61
|
+
@header = {}
|
62
|
+
@writer = lambda { |x| @body << x }
|
63
|
+
@block = nil
|
64
|
+
@length = 0
|
65
|
+
@body = []
|
66
|
+
@charset = nil
|
67
|
+
@content_type = nil
|
68
|
+
|
69
|
+
@request = @template = nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc:
|
74
|
+
DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS
|
75
|
+
|
76
|
+
def initialize(session = {})
|
77
|
+
replace(session.stringify_keys)
|
78
|
+
@loaded = true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Superclass for ActionController functional tests. Functional tests allow you to
|
83
|
+
# test a single controller action per test method. This should not be confused with
|
84
|
+
# integration tests (see ActionController::IntegrationTest), which are more like
|
85
|
+
# "stories" that can involve multiple controllers and mutliple actions (i.e. multiple
|
86
|
+
# different HTTP requests).
|
87
|
+
#
|
88
|
+
# == Basic example
|
89
|
+
#
|
90
|
+
# Functional tests are written as follows:
|
91
|
+
# 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
|
92
|
+
# an HTTP request.
|
93
|
+
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
|
94
|
+
# the controller's HTTP response, the database contents, etc.
|
95
|
+
#
|
96
|
+
# For example:
|
97
|
+
#
|
98
|
+
# class BooksControllerTest < ActionController::TestCase
|
99
|
+
# def test_create
|
100
|
+
# # Simulate a POST response with the given HTTP parameters.
|
101
|
+
# post(:create, :book => { :title => "Love Hina" })
|
102
|
+
#
|
103
|
+
# # Assert that the controller tried to redirect us to
|
104
|
+
# # the created book's URI.
|
105
|
+
# assert_response :found
|
106
|
+
#
|
107
|
+
# # Assert that the controller really put the book in the database.
|
108
|
+
# assert_not_nil Book.find_by_title("Love Hina")
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# == Special instance variables
|
113
|
+
#
|
114
|
+
# ActionController::TestCase will also automatically provide the following instance
|
115
|
+
# variables for use in the tests:
|
116
|
+
#
|
117
|
+
# <b>@controller</b>::
|
118
|
+
# The controller instance that will be tested.
|
119
|
+
# <b>@request</b>::
|
120
|
+
# An ActionController::TestRequest, representing the current HTTP
|
121
|
+
# request. You can modify this object before sending the HTTP request. For example,
|
122
|
+
# you might want to set some session properties before sending a GET request.
|
123
|
+
# <b>@response</b>::
|
124
|
+
# An ActionController::TestResponse object, representing the response
|
125
|
+
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
|
126
|
+
# after calling +post+. If the various assert methods are not sufficient, then you
|
127
|
+
# may use this object to inspect the HTTP response in detail.
|
128
|
+
#
|
129
|
+
# (Earlier versions of Rails required each functional test to subclass
|
130
|
+
# Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
|
131
|
+
#
|
132
|
+
# == Controller is automatically inferred
|
133
|
+
#
|
134
|
+
# ActionController::TestCase will automatically infer the controller under test
|
135
|
+
# from the test class name. If the controller cannot be inferred from the test
|
136
|
+
# class name, you can explicitly set it with +tests+.
|
137
|
+
#
|
138
|
+
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
|
139
|
+
# tests WidgetController
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# == Testing controller internals
|
143
|
+
#
|
144
|
+
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
|
145
|
+
# can be used against. These collections are:
|
146
|
+
#
|
147
|
+
# * assigns: Instance variables assigned in the action that are available for the view.
|
148
|
+
# * session: Objects being saved in the session.
|
149
|
+
# * flash: The flash objects currently in the session.
|
150
|
+
# * cookies: Cookies being sent to the user on this request.
|
151
|
+
#
|
152
|
+
# These collections can be used just like any other hash:
|
153
|
+
#
|
154
|
+
# assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
|
155
|
+
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
|
156
|
+
# assert flash.empty? # makes sure that there's nothing in the flash
|
157
|
+
#
|
158
|
+
# For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
|
159
|
+
# appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
|
160
|
+
# So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
|
161
|
+
#
|
162
|
+
# On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
|
163
|
+
#
|
164
|
+
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
|
165
|
+
# action call which can then be asserted against.
|
166
|
+
#
|
167
|
+
# == Manipulating the request collections
|
168
|
+
#
|
169
|
+
# The collections described above link to the response, so you can test if what the actions were expected to do happened. But
|
170
|
+
# sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
|
171
|
+
# and cookies, though. For sessions, you just do:
|
172
|
+
#
|
173
|
+
# @request.session[:key] = "value"
|
174
|
+
# @request.cookies["key"] = "value"
|
175
|
+
#
|
176
|
+
# == Testing named routes
|
177
|
+
#
|
178
|
+
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
|
179
|
+
# Example:
|
180
|
+
#
|
181
|
+
# assert_redirected_to page_url(:title => 'foo')
|
182
|
+
class TestCase < ActiveSupport::TestCase
|
183
|
+
include TestProcess
|
184
|
+
|
185
|
+
# Executes a request simulating GET HTTP method and set/volley the response
|
186
|
+
def get(action, parameters = nil, session = nil, flash = nil)
|
187
|
+
process(action, parameters, session, flash, "GET")
|
188
|
+
end
|
189
|
+
|
190
|
+
# Executes a request simulating POST HTTP method and set/volley the response
|
191
|
+
def post(action, parameters = nil, session = nil, flash = nil)
|
192
|
+
process(action, parameters, session, flash, "POST")
|
193
|
+
end
|
194
|
+
|
195
|
+
# Executes a request simulating PUT HTTP method and set/volley the response
|
196
|
+
def put(action, parameters = nil, session = nil, flash = nil)
|
197
|
+
process(action, parameters, session, flash, "PUT")
|
198
|
+
end
|
199
|
+
|
200
|
+
# Executes a request simulating DELETE HTTP method and set/volley the response
|
201
|
+
def delete(action, parameters = nil, session = nil, flash = nil)
|
202
|
+
process(action, parameters, session, flash, "DELETE")
|
203
|
+
end
|
204
|
+
|
205
|
+
# Executes a request simulating HEAD HTTP method and set/volley the response
|
206
|
+
def head(action, parameters = nil, session = nil, flash = nil)
|
207
|
+
process(action, parameters, session, flash, "HEAD")
|
208
|
+
end
|
209
|
+
|
210
|
+
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
|
211
|
+
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
|
212
|
+
@request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
|
213
|
+
returning __send__(request_method, action, parameters, session, flash) do
|
214
|
+
@request.env.delete 'HTTP_X_REQUESTED_WITH'
|
215
|
+
@request.env.delete 'HTTP_ACCEPT'
|
216
|
+
end
|
217
|
+
end
|
218
|
+
alias xhr :xml_http_request
|
219
|
+
|
220
|
+
def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
|
221
|
+
# Sanity check for required instance variables so we can give an
|
222
|
+
# understandable error message.
|
223
|
+
%w(@controller @request @response).each do |iv_name|
|
224
|
+
if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
|
225
|
+
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
@request.recycle!
|
230
|
+
@response.recycle!
|
231
|
+
@controller.response_body = nil
|
232
|
+
@controller.formats = nil
|
233
|
+
@controller.params = nil
|
234
|
+
|
235
|
+
@html_document = nil
|
236
|
+
@request.env['REQUEST_METHOD'] = http_method
|
237
|
+
|
238
|
+
parameters ||= {}
|
239
|
+
@request.assign_parameters(@controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)
|
240
|
+
|
241
|
+
@request.session = ActionController::TestSession.new(session) unless session.nil?
|
242
|
+
@request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
|
243
|
+
|
244
|
+
@controller.request = @request
|
245
|
+
@controller.params.merge!(parameters)
|
246
|
+
build_request_uri(action, parameters)
|
247
|
+
Base.class_eval { include Testing }
|
248
|
+
@controller.process_with_new_base_test(@request, @response)
|
249
|
+
@response
|
250
|
+
end
|
251
|
+
|
252
|
+
include ActionDispatch::Assertions
|
253
|
+
|
254
|
+
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
|
255
|
+
# (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
|
256
|
+
# rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
|
257
|
+
# than 0.0.0.0.
|
258
|
+
#
|
259
|
+
# The exception is stored in the exception accessor for further inspection.
|
260
|
+
module RaiseActionExceptions
|
261
|
+
def self.included(base)
|
262
|
+
base.class_eval do
|
263
|
+
attr_accessor :exception
|
264
|
+
protected :exception, :exception=
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
protected
|
269
|
+
def rescue_action_without_handler(e)
|
270
|
+
self.exception = e
|
271
|
+
|
272
|
+
if request.remote_addr == "0.0.0.0"
|
273
|
+
raise(e)
|
274
|
+
else
|
275
|
+
super(e)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
setup :setup_controller_request_and_response
|
281
|
+
|
282
|
+
@@controller_class = nil
|
283
|
+
|
284
|
+
class << self
|
285
|
+
# Sets the controller class name. Useful if the name can't be inferred from test class.
|
286
|
+
# Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>.
|
287
|
+
def tests(controller_class)
|
288
|
+
self.controller_class = controller_class
|
289
|
+
end
|
290
|
+
|
291
|
+
def controller_class=(new_class)
|
292
|
+
prepare_controller_class(new_class) if new_class
|
293
|
+
write_inheritable_attribute(:controller_class, new_class)
|
294
|
+
end
|
295
|
+
|
296
|
+
def controller_class
|
297
|
+
if current_controller_class = read_inheritable_attribute(:controller_class)
|
298
|
+
current_controller_class
|
299
|
+
else
|
300
|
+
self.controller_class = determine_default_controller_class(name)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def determine_default_controller_class(name)
|
305
|
+
name.sub(/Test$/, '').constantize
|
306
|
+
rescue NameError
|
307
|
+
nil
|
308
|
+
end
|
309
|
+
|
310
|
+
def prepare_controller_class(new_class)
|
311
|
+
new_class.send :include, RaiseActionExceptions
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def setup_controller_request_and_response
|
316
|
+
@request = TestRequest.new
|
317
|
+
@response = TestResponse.new
|
318
|
+
|
319
|
+
if klass = self.class.controller_class
|
320
|
+
@controller ||= klass.new rescue nil
|
321
|
+
end
|
322
|
+
|
323
|
+
if @controller
|
324
|
+
@controller.request = @request
|
325
|
+
@controller.params = {}
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
# Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
|
330
|
+
def rescue_action_in_public!
|
331
|
+
@request.remote_addr = '208.77.188.166' # example.com
|
332
|
+
end
|
333
|
+
|
334
|
+
private
|
335
|
+
def build_request_uri(action, parameters)
|
336
|
+
unless @request.env['REQUEST_URI']
|
337
|
+
options = @controller.__send__(:rewrite_options, parameters)
|
338
|
+
options.update(:only_path => true, :action => action)
|
339
|
+
|
340
|
+
url = ActionController::UrlRewriter.new(@request, parameters)
|
341
|
+
@request.request_uri = url.rewrite(options)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|