halorgium-actionpack 3.0.pre

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 (154) hide show
  1. data/CHANGELOG +5179 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +409 -0
  4. data/lib/abstract_controller.rb +16 -0
  5. data/lib/abstract_controller/base.rb +158 -0
  6. data/lib/abstract_controller/callbacks.rb +113 -0
  7. data/lib/abstract_controller/exceptions.rb +12 -0
  8. data/lib/abstract_controller/helpers.rb +151 -0
  9. data/lib/abstract_controller/layouts.rb +250 -0
  10. data/lib/abstract_controller/localized_cache.rb +49 -0
  11. data/lib/abstract_controller/logger.rb +61 -0
  12. data/lib/abstract_controller/rendering_controller.rb +188 -0
  13. data/lib/action_controller.rb +72 -0
  14. data/lib/action_controller/base.rb +168 -0
  15. data/lib/action_controller/caching.rb +80 -0
  16. data/lib/action_controller/caching/actions.rb +163 -0
  17. data/lib/action_controller/caching/fragments.rb +116 -0
  18. data/lib/action_controller/caching/pages.rb +154 -0
  19. data/lib/action_controller/caching/sweeping.rb +97 -0
  20. data/lib/action_controller/deprecated.rb +4 -0
  21. data/lib/action_controller/deprecated/integration_test.rb +2 -0
  22. data/lib/action_controller/deprecated/performance_test.rb +1 -0
  23. data/lib/action_controller/dispatch/dispatcher.rb +57 -0
  24. data/lib/action_controller/metal.rb +129 -0
  25. data/lib/action_controller/metal/benchmarking.rb +73 -0
  26. data/lib/action_controller/metal/compatibility.rb +145 -0
  27. data/lib/action_controller/metal/conditional_get.rb +86 -0
  28. data/lib/action_controller/metal/configuration.rb +28 -0
  29. data/lib/action_controller/metal/cookies.rb +105 -0
  30. data/lib/action_controller/metal/exceptions.rb +55 -0
  31. data/lib/action_controller/metal/filter_parameter_logging.rb +77 -0
  32. data/lib/action_controller/metal/flash.rb +162 -0
  33. data/lib/action_controller/metal/head.rb +27 -0
  34. data/lib/action_controller/metal/helpers.rb +115 -0
  35. data/lib/action_controller/metal/hide_actions.rb +47 -0
  36. data/lib/action_controller/metal/http_authentication.rb +312 -0
  37. data/lib/action_controller/metal/layouts.rb +171 -0
  38. data/lib/action_controller/metal/mime_responds.rb +317 -0
  39. data/lib/action_controller/metal/rack_convenience.rb +27 -0
  40. data/lib/action_controller/metal/redirector.rb +22 -0
  41. data/lib/action_controller/metal/render_options.rb +103 -0
  42. data/lib/action_controller/metal/rendering_controller.rb +57 -0
  43. data/lib/action_controller/metal/request_forgery_protection.rb +108 -0
  44. data/lib/action_controller/metal/rescuable.rb +13 -0
  45. data/lib/action_controller/metal/responder.rb +200 -0
  46. data/lib/action_controller/metal/session.rb +15 -0
  47. data/lib/action_controller/metal/session_management.rb +45 -0
  48. data/lib/action_controller/metal/streaming.rb +188 -0
  49. data/lib/action_controller/metal/testing.rb +39 -0
  50. data/lib/action_controller/metal/url_for.rb +41 -0
  51. data/lib/action_controller/metal/verification.rb +130 -0
  52. data/lib/action_controller/middleware.rb +38 -0
  53. data/lib/action_controller/notifications.rb +10 -0
  54. data/lib/action_controller/polymorphic_routes.rb +183 -0
  55. data/lib/action_controller/record_identifier.rb +91 -0
  56. data/lib/action_controller/testing/process.rb +111 -0
  57. data/lib/action_controller/testing/test_case.rb +345 -0
  58. data/lib/action_controller/translation.rb +13 -0
  59. data/lib/action_controller/url_rewriter.rb +204 -0
  60. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  61. data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
  62. data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
  63. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +176 -0
  64. data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
  65. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
  66. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  67. data/lib/action_dispatch.rb +70 -0
  68. data/lib/action_dispatch/http/headers.rb +33 -0
  69. data/lib/action_dispatch/http/mime_type.rb +231 -0
  70. data/lib/action_dispatch/http/mime_types.rb +23 -0
  71. data/lib/action_dispatch/http/request.rb +539 -0
  72. data/lib/action_dispatch/http/response.rb +290 -0
  73. data/lib/action_dispatch/http/status_codes.rb +42 -0
  74. data/lib/action_dispatch/http/utils.rb +20 -0
  75. data/lib/action_dispatch/middleware/callbacks.rb +50 -0
  76. data/lib/action_dispatch/middleware/params_parser.rb +79 -0
  77. data/lib/action_dispatch/middleware/rescue.rb +26 -0
  78. data/lib/action_dispatch/middleware/session/abstract_store.rb +208 -0
  79. data/lib/action_dispatch/middleware/session/cookie_store.rb +235 -0
  80. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +47 -0
  81. data/lib/action_dispatch/middleware/show_exceptions.rb +143 -0
  82. data/lib/action_dispatch/middleware/stack.rb +116 -0
  83. data/lib/action_dispatch/middleware/static.rb +44 -0
  84. data/lib/action_dispatch/middleware/string_coercion.rb +29 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +24 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +26 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +10 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +29 -0
  89. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +2 -0
  90. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +10 -0
  91. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +21 -0
  92. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +2 -0
  93. data/lib/action_dispatch/routing.rb +381 -0
  94. data/lib/action_dispatch/routing/deprecated_mapper.rb +878 -0
  95. data/lib/action_dispatch/routing/mapper.rb +327 -0
  96. data/lib/action_dispatch/routing/route.rb +49 -0
  97. data/lib/action_dispatch/routing/route_set.rb +497 -0
  98. data/lib/action_dispatch/testing/assertions.rb +8 -0
  99. data/lib/action_dispatch/testing/assertions/dom.rb +35 -0
  100. data/lib/action_dispatch/testing/assertions/model.rb +19 -0
  101. data/lib/action_dispatch/testing/assertions/response.rb +145 -0
  102. data/lib/action_dispatch/testing/assertions/routing.rb +144 -0
  103. data/lib/action_dispatch/testing/assertions/selector.rb +639 -0
  104. data/lib/action_dispatch/testing/assertions/tag.rb +123 -0
  105. data/lib/action_dispatch/testing/integration.rb +504 -0
  106. data/lib/action_dispatch/testing/performance_test.rb +15 -0
  107. data/lib/action_dispatch/testing/test_request.rb +83 -0
  108. data/lib/action_dispatch/testing/test_response.rb +131 -0
  109. data/lib/action_pack.rb +24 -0
  110. data/lib/action_pack/version.rb +9 -0
  111. data/lib/action_view.rb +58 -0
  112. data/lib/action_view/base.rb +308 -0
  113. data/lib/action_view/context.rb +44 -0
  114. data/lib/action_view/erb/util.rb +48 -0
  115. data/lib/action_view/helpers.rb +62 -0
  116. data/lib/action_view/helpers/active_model_helper.rb +306 -0
  117. data/lib/action_view/helpers/ajax_helper.rb +68 -0
  118. data/lib/action_view/helpers/asset_tag_helper.rb +830 -0
  119. data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
  120. data/lib/action_view/helpers/cache_helper.rb +39 -0
  121. data/lib/action_view/helpers/capture_helper.rb +168 -0
  122. data/lib/action_view/helpers/date_helper.rb +988 -0
  123. data/lib/action_view/helpers/debug_helper.rb +38 -0
  124. data/lib/action_view/helpers/form_helper.rb +1102 -0
  125. data/lib/action_view/helpers/form_options_helper.rb +600 -0
  126. data/lib/action_view/helpers/form_tag_helper.rb +495 -0
  127. data/lib/action_view/helpers/javascript_helper.rb +208 -0
  128. data/lib/action_view/helpers/number_helper.rb +311 -0
  129. data/lib/action_view/helpers/prototype_helper.rb +1309 -0
  130. data/lib/action_view/helpers/raw_output_helper.rb +9 -0
  131. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  132. data/lib/action_view/helpers/record_tag_helper.rb +58 -0
  133. data/lib/action_view/helpers/sanitize_helper.rb +259 -0
  134. data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
  135. data/lib/action_view/helpers/tag_helper.rb +151 -0
  136. data/lib/action_view/helpers/text_helper.rb +594 -0
  137. data/lib/action_view/helpers/translation_helper.rb +39 -0
  138. data/lib/action_view/helpers/url_helper.rb +639 -0
  139. data/lib/action_view/locale/en.yml +117 -0
  140. data/lib/action_view/paths.rb +80 -0
  141. data/lib/action_view/render/partials.rb +342 -0
  142. data/lib/action_view/render/rendering.rb +134 -0
  143. data/lib/action_view/safe_buffer.rb +28 -0
  144. data/lib/action_view/template/error.rb +101 -0
  145. data/lib/action_view/template/handler.rb +36 -0
  146. data/lib/action_view/template/handlers.rb +52 -0
  147. data/lib/action_view/template/handlers/builder.rb +17 -0
  148. data/lib/action_view/template/handlers/erb.rb +53 -0
  149. data/lib/action_view/template/handlers/rjs.rb +18 -0
  150. data/lib/action_view/template/resolver.rb +165 -0
  151. data/lib/action_view/template/template.rb +131 -0
  152. data/lib/action_view/template/text.rb +38 -0
  153. data/lib/action_view/test_case.rb +163 -0
  154. 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