actionpack 0.9.0 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (37) hide show
  1. data/CHANGELOG +88 -0
  2. data/README +2 -2
  3. data/RUNNING_UNIT_TESTS +12 -1
  4. data/install.rb +7 -3
  5. data/lib/action_controller.rb +2 -0
  6. data/lib/action_controller/assertions/action_pack_assertions.rb +39 -6
  7. data/lib/action_controller/base.rb +82 -49
  8. data/lib/action_controller/cgi_process.rb +3 -2
  9. data/lib/action_controller/helpers.rb +89 -0
  10. data/lib/action_controller/layout.rb +9 -3
  11. data/lib/action_controller/request.rb +7 -2
  12. data/lib/action_controller/response.rb +2 -2
  13. data/lib/action_controller/support/class_inheritable_attributes.rb +1 -1
  14. data/lib/action_controller/support/inflector.rb +8 -0
  15. data/lib/action_controller/test_process.rb +3 -2
  16. data/lib/action_controller/url_rewriter.rb +27 -11
  17. data/lib/action_view/base.rb +11 -0
  18. data/lib/action_view/helpers/active_record_helper.rb +5 -5
  19. data/lib/action_view/helpers/date_helper.rb +8 -1
  20. data/lib/action_view/helpers/form_helper.rb +12 -6
  21. data/lib/action_view/helpers/form_options_helper.rb +44 -1
  22. data/lib/action_view/helpers/text_helper.rb +13 -31
  23. data/lib/action_view/helpers/url_helper.rb +11 -5
  24. data/lib/action_view/partials.rb +5 -2
  25. data/rakefile +12 -3
  26. data/test/controller/action_pack_assertions_test.rb +30 -2
  27. data/test/controller/active_record_assertions_test.rb +2 -1
  28. data/test/controller/helper_test.rb +110 -0
  29. data/test/controller/send_file_test.rb +68 -0
  30. data/test/controller/url_test.rb +81 -35
  31. data/test/fixtures/helpers/abc_helper.rb +5 -0
  32. data/test/template/active_record_helper_test.rb +3 -3
  33. data/test/template/date_helper_test.rb +1 -0
  34. data/test/template/form_helper_test.rb +13 -6
  35. data/test/template/form_options_helper_test.rb +1 -1
  36. data/test/template/url_helper_test.rb +18 -4
  37. metadata +7 -2
data/CHANGELOG CHANGED
@@ -1,3 +1,91 @@
1
+ *0.9.5* (28)
2
+
3
+ * Added helper_method to designate that a given private or protected method you should available as a helper in the view. [bitsweat]
4
+
5
+ * Fixed assert_rendered_file so it actually verifies if that was the rendered file [htonl]
6
+
7
+ * Added the option for sharing partial spacer templates just like partials themselves [radsaq]
8
+
9
+ * Fixed that Russia was named twice in country_select [alexey]
10
+
11
+ * Fixed request_origin to use remote_ip instead of remote_addr [bitsweat]
12
+
13
+ * Fixed link_to breakage when nil was passed for html_options [alexey]
14
+
15
+ * Fixed redirect_to on a virtual server setup with apache with a port other than the default where it would forget the port number [seanohalpin]
16
+
17
+ * Fixed that auto-loading webrick on Windows would cause file uploads to fail [bitsweat]
18
+
19
+ * Fixed issues with sending files on WEBrick by setting the proper binmode [bitsweat]
20
+
21
+ * Added send_data as an alternative to send_file when the stream is not read off the filesystem but from a database or generated live [bitsweat]
22
+
23
+ * Added a new way to include helpers that doesn't require the include hack and can go without the explicit require. [bitsweat]
24
+
25
+ Before:
26
+
27
+ module WeblogHelper
28
+ def self.append_features(controller) #:nodoc:
29
+ controller.ancestors.include?(ActionController::Base) ? controller.add_template_helper(self) : super
30
+ end
31
+ end
32
+
33
+ require 'weblog_helper'
34
+ class WeblogController < ActionController::Base
35
+ include WeblogHelper
36
+ end
37
+
38
+ After:
39
+
40
+ module WeblogHelper
41
+ end
42
+
43
+ class WeblogController < ActionController::Base
44
+ helper :weblog
45
+ end
46
+
47
+ * Added a default content-type of "text/xml" to .rxml renders [Ryan Platte]
48
+
49
+ * Fixed that when /controller/index was requested by the browser, url_for would generates wrong URLs [Ryan Platte]
50
+
51
+ * Fixed a bug that would share cookies between users when using FastCGI and mod_ruby [The Robot Co-op]
52
+
53
+ * Added an optional third hash parameter to the process method in functional tests that takes the session data to be used [alexey]
54
+
55
+ * Added UrlHelper#mail_to to make it easier to create mailto: style ahrefs
56
+
57
+ * Added better error messages for layouts declared with the .rhtml extension (which they shouldn't) [geech]
58
+
59
+ * Added another case to DateHelper#distance_in_minutes to return "less than a minute" instead of "0 minutes" and "1 minute" instead of "1 minutes"
60
+
61
+ * Added a hidden field to checkboxes generated with FormHelper#check_box that will make sure that the unchecked value (usually 0)
62
+ is sent even if the checkbox is not checked. This relieves the controller from doing custom checking if the the checkbox wasn't
63
+ checked. BEWARE: This might conflict with your run-on-the-mill work-around code. [Tobias Luetke]
64
+
65
+ * Fixed error_message_on to just use the first if more than one error had been added [marcel]
66
+
67
+ * Fixed that URL rewriting with /controller/ was working but /controller was not and that you couldn't use :id on index [geech]
68
+
69
+ * Fixed a bug with link_to where the :confirm option wouldn't be picked up if the link was a straight url instead of an option hash
70
+
71
+ * Changed scaffolding of forms to use <label> tags instead of <b> to please W3C [evl]
72
+
73
+ * Added DateHelper#distance_of_time_in_words_to_now(from_time) that works like distance_of_time_in_words,
74
+ but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
75
+
76
+ * Added assert_flash_equal(expected, key, message), assert_session_equal(expected, key, message),
77
+ assert_assigned_equal(expected, key, message) to test the contents of flash, session, and template assigns.
78
+
79
+ * Improved the failure report on assert_success when the action triggered a redirection [alexey].
80
+
81
+ * Added "markdown" to accompany "textilize" as a TextHelper method for converting text to HTML using the Markdown syntax.
82
+ BlueCloth must be installed in order for this method to become available.
83
+
84
+ * Made sure that an active session exists before we attempt to delete it [Samuel]
85
+
86
+ * Changed link_to with Javascript confirmation to use onclick instead of onClick for XHTML validity [Scott Barron]
87
+
88
+
1
89
  *0.9.0 (43)*
2
90
 
3
91
  * Added support for Builder-based templates for files with the .rxml extension. These new templates are an alternative to ERb that
data/README CHANGED
@@ -95,7 +95,7 @@ A short rundown of the major features:
95
95
 
96
96
  class WeblogController < ActionController::Base
97
97
  before_filter :authenticate, :cache, :audit
98
- after_filter proc{|c| c.response.body = GZip::compress(c.response.body)}
98
+ after_filter { |c| c.response.body = GZip::compress(c.response.body) }
99
99
  after_filter LocalizeFilter
100
100
 
101
101
  def list
@@ -174,7 +174,7 @@ A short rundown of the major features:
174
174
  class LoginControllerTest < Test::Unit::TestCase
175
175
  def setup
176
176
  @controller = LoginController.new
177
- @request = ActionController::TestRequest.new
177
+ @request = ActionController::TestRequest.new
178
178
  @response = ActionController::TestResponse.new
179
179
  end
180
180
 
@@ -11,4 +11,15 @@ Rake can be found at http://rake.rubyforge.org
11
11
  If you only want to run a single test suite, or don't want to bother with Rake,
12
12
  you can do so with something like:
13
13
 
14
- ruby controller/base_test.rb
14
+ ruby controller/base_test.rb
15
+
16
+ == Dependency on ActiveRecord and database setup
17
+
18
+ Test cases in test/controller/active_record_assertions.rb depend on having
19
+ activerecord installed and configured in a particular way. See comment in the
20
+ test file itself for details. If ActiveRecord is not in
21
+ actionpack/../activerecord directory, these tests are skipped. If activerecord
22
+ is installed, but not configured as expected, the tests will fail.
23
+
24
+ Other tests are runnable from a fresh copy of actionpack without any configuration.
25
+
data/install.rb CHANGED
@@ -18,9 +18,10 @@ unless $sitedir
18
18
  end
19
19
  end
20
20
 
21
- makedirs = %w{ action_controller/cgi_ext action_controller/session
22
- action_controller/support action_controller/templates
23
- action_controller/templates/rescues action_controller/templates/scaffolds
21
+ makedirs = %w{ action_controller/assertions action_controller/cgi_ext
22
+ action_controller/session action_controller/support
23
+ action_controller/templates action_controller/templates/rescues
24
+ action_controller/templates/scaffolds
24
25
  action_view/helpers action_view/vendor action_view/vendor/builder
25
26
  }
26
27
 
@@ -33,6 +34,8 @@ makedirs.each {|f| File::makedirs(File.join($sitedir, *f.split(/\//)))}
33
34
  # files to install in library path
34
35
  files = %w-
35
36
  action_controller.rb
37
+ action_controller/assertions/action_pack_assertions.rb
38
+ action_controller/assertions/active_record_assertions.rb
36
39
  action_controller/base.rb
37
40
  action_controller/benchmarking.rb
38
41
  action_controller/cgi_ext/cgi_ext.rb
@@ -40,6 +43,7 @@ files = %w-
40
43
  action_controller/cgi_process.rb
41
44
  action_controller/filters.rb
42
45
  action_controller/flash.rb
46
+ action_controller/helpers.rb
43
47
  action_controller/layout.rb
44
48
  action_controller/request.rb
45
49
  action_controller/rescue.rb
@@ -32,12 +32,14 @@ require 'action_controller/filters'
32
32
  require 'action_controller/layout'
33
33
  require 'action_controller/flash'
34
34
  require 'action_controller/scaffolding'
35
+ require 'action_controller/helpers'
35
36
  require 'action_controller/cgi_process'
36
37
 
37
38
  ActionController::Base.class_eval do
38
39
  include ActionController::Filters
39
40
  include ActionController::Layout
40
41
  include ActionController::Flash
42
+ include ActionController::Helpers
41
43
  include ActionController::Benchmarking
42
44
  include ActionController::Rescue
43
45
  include ActionController::Scaffolding
@@ -11,19 +11,30 @@ module Test #:nodoc:
11
11
  # ensure that the web request has been serviced correctly
12
12
  def assert_success(message=nil)
13
13
  response = acquire_assertion_target
14
- msg = build_message(message, "unsuccessful request (response code = <?>)", response.response_code)
15
- assert_block(msg) { response.success? }
14
+ if response.success?
15
+ # to count the assertion
16
+ assert_block("") { true }
17
+ else
18
+ if response.redirect?
19
+ msg = build_message(message, "Response unexpectedly redirect to <?>", response.redirect_url)
20
+ else
21
+ msg = build_message(message, "unsuccessful request (response code = <?>)",
22
+ response.response_code)
23
+ end
24
+ assert_block(msg) { false }
25
+ end
16
26
  end
17
27
 
18
28
  # ensure the request was rendered with the appropriate template file
19
29
  def assert_rendered_file(expected=nil, message=nil)
20
30
  response = acquire_assertion_target
21
- msg = build_message(message, "expecting <?> but rendering with <?>", expected, response.rendered_file)
31
+ rendered = expected ? response.rendered_file(!expected.include?('/')) : response.rendered_file
32
+ msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
22
33
  assert_block(msg) do
23
34
  if expected.nil?
24
35
  response.rendered_with_file?
25
36
  else
26
- expected != response.rendered_file(expected.include?('/'))
37
+ expected == rendered
27
38
  end
28
39
  end
29
40
  end
@@ -44,6 +55,12 @@ module Test #:nodoc:
44
55
  assert_block(msg) { !response.has_session_object?(key) }
45
56
  end
46
57
 
58
+ def assert_session_equal(expected = nil, key = nil, message = nil)
59
+ response = acquire_assertion_target
60
+ msg = build_message(message, "<?> expected in session['?'] but was <?>", expected, key, response.session[key])
61
+ assert_block(msg) { expected == response.session[key] }
62
+ end
63
+
47
64
  # -- flash assertions ---------------------------------------------------
48
65
 
49
66
  # ensure that the flash has an object with the specified name
@@ -88,6 +105,12 @@ module Test #:nodoc:
88
105
  assert_block(msg) { response.has_flash_with_contents? }
89
106
  end
90
107
 
108
+ def assert_flash_equal(expected = nil, key = nil, message = nil)
109
+ response = acquire_assertion_target
110
+ msg = build_message(message, "<?> expected in flash['?'] but was <?>", expected, key, response.flash[key])
111
+ assert_block(msg) { expected == response.flash[key] }
112
+ end
113
+
91
114
  # -- redirection assertions ---------------------------------------------
92
115
 
93
116
  # ensure we have be redirected
@@ -103,8 +126,11 @@ module Test #:nodoc:
103
126
 
104
127
  msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>)", response.redirected_to)
105
128
  assert_block(msg) do
106
- response.redirected_to == options ||
129
+ if options.is_a?(Symbol)
130
+ response.redirected_to == options
131
+ else
107
132
  options.keys.all? { |k| options[k] == response.redirected_to[k] }
133
+ end
108
134
  end
109
135
  end
110
136
 
@@ -140,6 +166,13 @@ module Test #:nodoc:
140
166
  assert_block(msg) { !response.has_template_object?(key) }
141
167
  end
142
168
 
169
+ # ensures that the object assigned to the template on +key+ is equal to +expected+ object.
170
+ def assert_assigned_equal(expected = nil, key = nil, message = nil)
171
+ response = acquire_assertion_target
172
+ msg = build_message(message, "<?> expected in assigns['?'] but was <?>", expected, key, response.template.assigns[key.to_s])
173
+ assert_block(msg) { expected == response.template.assigns[key.to_s] }
174
+ end
175
+
143
176
  # Asserts that the template returns the +expected+ string or array based on the XPath +expression+.
144
177
  # This will only work if the template rendered a valid XML document.
145
178
  def assert_template_xpath_match(expression=nil, expected=nil, message=nil)
@@ -163,4 +196,4 @@ module Test #:nodoc:
163
196
 
164
197
  end # Assertions
165
198
  end # Unit
166
- end # Test
199
+ end # Test
@@ -174,6 +174,13 @@ module ActionController #:nodoc:
174
174
 
175
175
  DEFAULT_RENDER_STATUS_CODE = "200 OK"
176
176
 
177
+ DEFAULT_SEND_FILE_OPTIONS = {
178
+ :type => 'application/octet_stream',
179
+ :disposition => 'attachment',
180
+ :stream => true, :buffer_size => 4096
181
+ }
182
+
183
+
177
184
  # Determines whether the view has access to controller internals @request, @response, @session, and @template.
178
185
  # By default, it does.
179
186
  @@view_controller_internals = true
@@ -234,13 +241,6 @@ module ActionController #:nodoc:
234
241
  def process(request, response) #:nodoc:
235
242
  new.process(request, response)
236
243
  end
237
-
238
- # Makes all the (instance) methods in the helper module available to templates rendered through this controller.
239
- # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
240
- # available to the templates.
241
- def add_template_helper(helper_module)
242
- template_class.class_eval "include #{helper_module}"
243
- end
244
244
  end
245
245
 
246
246
  public
@@ -285,12 +285,13 @@ module ActionController #:nodoc:
285
285
  # "/library/books/ISBN/0743536703/show?temporary=1"
286
286
  # * <tt>:anchor</tt> - specifies the anchor name to be appended to the path. Called with "x14" would give
287
287
  # "/library/books/ISBN/0743536703/show#x14"
288
+ # * <tt>:only_path</tt> - if true, returns the absolute URL (omitting the protocol, host name, and port).
288
289
  #
289
290
  # Naturally, you can combine multiple options in a single redirect. Examples:
290
291
  #
291
292
  # redirect_to(:controller_prefix => "/shop", :controller => "settings")
292
293
  # redirect_to(:action => "edit", :id => 3425)
293
- # redirect_to(:action => "edit", :path_params => { "type" => "XTC"}, :params => { "temp" => 1})
294
+ # redirect_to(:action => "edit", :path_params => { "type" => "XTC" }, :params => { "temp" => 1})
294
295
  # redirect_to(:action => "publish", :action_prefix => "/published", :anchor => "x14")
295
296
  #
296
297
  # Instead of passing an options hash, you can also pass a method reference in the form of a symbol. Consider this example:
@@ -380,16 +381,18 @@ module ActionController #:nodoc:
380
381
  # it feasible to send even large files.
381
382
  #
382
383
  # Be careful to sanitize the path parameter if it coming from a web
383
- # page. send_file(@params['path'] allows a malicious user to
384
+ # page. send_file(@params['path']) allows a malicious user to
384
385
  # download any file on your server.
385
386
  #
386
387
  # Options:
387
- # * <tt>:filename</tt> - specifies the filename the browser will see.
388
+ # * <tt>:filename</tt> - suggests a filename for the browser to use.
388
389
  # Defaults to File.basename(path).
389
390
  # * <tt>:type</tt> - specifies an HTTP content type.
390
391
  # Defaults to 'application/octet-stream'.
391
392
  # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
392
393
  # Valid values are 'inline' and 'attachment' (default).
394
+ # * <tt>:streaming</tt> - whether to send the file to the user agent as it is read (true)
395
+ # or to read the entire file before sending (false). Defaults to true.
393
396
  # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
394
397
  # Defaults to 4096.
395
398
  #
@@ -416,46 +419,57 @@ module ActionController #:nodoc:
416
419
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
417
420
  # for the Cache-Control header spec.
418
421
  def send_file(path, options = {})
419
- options = {
420
- :filename => File.basename(path),
421
- :type => 'application/octet_stream',
422
- :disposition => 'attachment',
423
- :buffer_size => 4096
424
- }.merge(options)
425
-
426
- # Internet Explorer cache workaround.
427
- if @request.env['HTTP_USER_AGENT'] =~ /msie/i
428
- @headers['Pragma'] = ''
429
- @headers['Cache-Control'] = ''
430
-
431
- # HTTP 1.1 headers require strict validation with server before
432
- # releasing a cached response to client.
433
- else
434
- @headers['Pragma'] = 'no-cache'
435
- @headers['Cache-Control'] = 'no-cache, must-revalidate'
436
- end
437
-
438
- # HTTP 1.0 headers for cache expiry.
439
- @headers['Last-Modified'] = CGI.rfc1123_date(File.mtime(path))
440
- @headers['Expires'] = CGI.rfc1123_date(Time.now)
441
-
442
- # HTTP Content headers.
443
- @headers['Content-Type'] = options[:type]
444
- @headers['Content-Disposition'] = "#{options[:disposition]}; filename=\"#{options[:filename]}\""
445
- @headers['Content-Length'] = File.size(path)
446
- @headers['Content-Transfer-Encoding'] = 'binary'
447
-
448
- logger.info("Sending file #{path}") unless logger.nil?
449
-
450
- render_text do
451
- File.open(path, 'rb') do |file|
452
- while buf = file.read(options[:buffer_size])
453
- print buf
422
+ raise MissingFile unless File.file?(path) and File.readable?(path)
423
+
424
+ options[:length] ||= File.size(path)
425
+ options[:filename] ||= File.basename(path)
426
+ send_file_headers! options
427
+
428
+ if options[:stream]
429
+ render_text do
430
+ logger.info "Streaming file #{path}" unless logger.nil?
431
+ len = options[:buffer_size] || 4096
432
+ File.open(path, 'rb') do |file|
433
+ begin
434
+ while true
435
+ $stdout.syswrite file.sysread(len)
436
+ end
437
+ rescue EOFError
438
+ end
454
439
  end
455
440
  end
441
+ else
442
+ logger.info "Sending file #{path}" unless logger.nil?
443
+ File.open(path, 'rb') { |file| render_text file.read }
456
444
  end
457
445
  end
458
-
446
+
447
+ # Send binary data to the user as a file download. May set content type, apparent file name,
448
+ # and specify whether to show data inline or download as an attachment.
449
+ #
450
+ # Options:
451
+ # * <tt>:filename</tt> - Suggests a filename for the browser to use.
452
+ # * <tt>:type</tt> - specifies an HTTP content type.
453
+ # Defaults to 'application/octet-stream'.
454
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
455
+ # Valid values are 'inline' and 'attachment' (default).
456
+ #
457
+ # Generic data download:
458
+ # send_data buffer
459
+ #
460
+ # Download a dynamically-generated tarball:
461
+ # send_data generate_tgz('dir'), :filename => 'dir.tgz'
462
+ #
463
+ # Display an image Active Record in the browser:
464
+ # send_data image.data, :type => image.content_type, :disposition => 'inline'
465
+ #
466
+ # See +send_file+ for more information on HTTP Content-* headers and caching.
467
+ def send_data(data, options = {})
468
+ logger.info "Sending data #{options[:filename]}" unless logger.nil?
469
+ send_file_headers! options.merge(:length => data.size)
470
+ render_text data
471
+ end
472
+
459
473
  def rewrite_options(options)
460
474
  if defaults = default_url_options(options)
461
475
  defaults.merge(options)
@@ -602,7 +616,7 @@ module ActionController #:nodoc:
602
616
  end
603
617
 
604
618
  def request_origin
605
- "#{@request.remote_addr} at #{Time.now.to_s}"
619
+ "#{@request.remote_ip} at #{Time.now.to_s}"
606
620
  end
607
621
 
608
622
  def close_session
@@ -614,13 +628,32 @@ module ActionController #:nodoc:
614
628
  end
615
629
 
616
630
  def template_public?(template_name = "#{controller_name}/#{action_name}")
617
- @template.file_public?(template_name)
631
+ @template.file_public?(template_name)
618
632
  end
619
633
 
620
634
  def assert_existance_of_template_file(template_name)
621
635
  unless template_exists?(template_name) || ignore_missing_templates
622
- raise(MissingTemplate, "Couldn't find #{template_name}")
636
+ full_template_path = @template.send(:full_template_path, template_name, 'rhtml')
637
+ template_type = (template_name =~ /layouts/i) ? 'layout' : 'template'
638
+ raise(MissingTemplate, "Missing #{template_type} #{full_template_path}")
623
639
  end
624
640
  end
641
+
642
+ def send_file_headers!(options)
643
+ options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
644
+ [:length, :type, :disposition].each do |arg|
645
+ raise ArgumentError, ":#{arg} option required" if options[arg].nil?
646
+ end
647
+
648
+ disposition = options[:disposition] || 'attachment'
649
+ disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
650
+
651
+ @headers.update(
652
+ 'Content-Length' => options[:length],
653
+ 'Content-Type' => options[:type],
654
+ 'Content-Disposition' => disposition,
655
+ 'Content-Transfer-Encoding' => 'binary'
656
+ );
657
+ end
625
658
  end
626
659
  end