Permalink
Browse files

Merge branch 'master' into prepend-content-for

* master: (45 commits)
  use Hash#delete with a default block
  refactor options_for_select
  refactor grouped_options_for_select
  Fix a failing test
  Use the right format when a partial is missing.
  Merge pull request #5101 from ckdake/ckdake_actionview_handler_reset
  search private / protected methods in trunk ruby
  removed commented line. 3434 tests, 10531 assertions, 0 failures, 0 errors, 31 skips
  Restored ability to identify ID and Sequence from tables relying on a nonmatching sequence default value for PK.
  AM::Errors: allow :full_messages parameter for #as_json
  Remove fixture files with Windows incompatible filenames
  Integration tests support the OPTIONS http method
  Update activerecord/CHANGELOG.md
  fix some typos [ci skip]
  Documenting the :inverse_of option for associations
  add selected and disabled option to grouped select
  assigns(:foo) should not convert @foo's keys to strings if it happens to be a hash
  Fix actionpack readme weblog example
  Fix AbstractController::Base#hidden_actions comment
  Do get it right this time. Fixing the documentation around :dependent => :restrict option
  ...
  • Loading branch information...
2 parents f7bbdcc + 563df87 commit e6ff135f1dbde5dcee915199116721528ab7d6e1 @delynn committed Feb 20, 2012
Showing with 740 additions and 144 deletions.
  1. +1 −1 actionmailer/README.rdoc
  2. +1 −2 actionmailer/lib/action_mailer/mail_helper.rb
  3. +5 −0 actionpack/CHANGELOG.md
  4. +1 −1 actionpack/README.rdoc
  5. +2 −2 actionpack/lib/abstract_controller/base.rb
  6. +9 −2 actionpack/lib/action_controller/metal/conditional_get.rb
  7. +1 −1 actionpack/lib/action_controller/metal/responder.rb
  8. +14 −0 actionpack/lib/action_dispatch/http/cache.rb
  9. +11 −2 actionpack/lib/action_dispatch/middleware/static.rb
  10. +7 −1 actionpack/lib/action_dispatch/testing/integration.rb
  11. +2 −1 actionpack/lib/action_dispatch/testing/test_process.rb
  12. +5 −5 actionpack/lib/action_view/helpers/form_helper.rb
  13. +13 −12 actionpack/lib/action_view/helpers/form_options_helper.rb
  14. +6 −1 actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
  15. +1 −0 actionpack/lib/action_view/template.rb
  16. +1 −0 actionpack/lib/action_view/template/handlers.rb
  17. +3 −3 actionpack/lib/sprockets/helpers/rails_helper.rb
  18. +17 −1 actionpack/test/controller/integration_test.rb
  19. +27 −0 actionpack/test/controller/render_test.rb
  20. +5 −0 actionpack/test/controller/test_case_test.rb
  21. +90 −0 actionpack/test/dispatch/static_test.rb
  22. +1 −0 actionpack/test/fixtures/with_format.json.erb
  23. +34 −16 actionpack/test/template/form_options_helper_test.rb
  24. +13 −0 actionpack/test/template/render_test.rb
  25. +2 −0 actionpack/test/template/template_test.rb
  26. +2 −0 activemodel/CHANGELOG.md
  27. +14 −3 activemodel/lib/active_model/errors.rb
  28. +10 −0 activemodel/test/cases/errors_test.rb
  29. +1 −1 activerecord/CHANGELOG.md
  30. +47 −13 activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
  31. +1 −1 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
  32. +42 −18 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
  33. +3 −2 activerecord/lib/active_record/railties/databases.rake
  34. +7 −3 activerecord/test/cases/adapters/postgresql/schema_test.rb
  35. +21 −0 activerecord/test/cases/connection_management_test.rb
  36. +1 −1 activesupport/lib/active_support/core_ext/time/calculations.rb
  37. +1 −0 railties/guides/assets/stylesheets/main.css
  38. +1 −1 railties/guides/source/active_record_validations_callbacks.textile
  39. +105 −0 railties/guides/source/association_basics.textile
  40. +160 −46 railties/guides/source/engines.textile
  41. +1 −1 railties/guides/source/layouts_and_rendering.textile
  42. +2 −2 railties/lib/rails/engine.rb
  43. +49 −1 railties/test/application/rake/migrations_test.rb
@@ -82,7 +82,7 @@ Note that every value you set with this method will get over written if you use
Example:
- class Authenticationmailer < ActionMailer::Base
+ class AuthenticationMailer < ActionMailer::Base
default :from => "awesome@application.com", :subject => Proc.new { "E-mail was generated at #{Time.now}" }
.....
end
@@ -1,7 +1,6 @@
module ActionMailer
module MailHelper
- # Uses Text::Format to take the text and format it, indented two spaces for
- # each line, and wrapped at 72 columns.
+ # Take the text and format it, indented two spaces for each line, and wrapped at 72 columns.
def block_format(text)
formatted = text.split(/\n\r?\n/).collect { |paragraph|
format_paragraph(paragraph)
@@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##
+* Integration tests support the `OPTIONS` method. *Jeremy Kemper*
+
+* `expires_in` accepts a `must_revalidate` flag. If true, "must-revalidate"
+ is added to the Cache-Control header. *fxn*
+
* Add `date_field` and `date_field_tag` helpers which render an `input[type="date"]` tag *Olek Janiszewski*
* Adds `image_url`, `javascript_url`, `stylesheet_url`, `audio_url`, `video_url`, and `font_url`
@@ -218,7 +218,7 @@ A short rundown of some of the major features:
def show
# the output of the method will be cached as
- # ActionController::Base.page_cache_directory + "/weblog/show/n.html"
+ # ActionController::Base.page_cache_directory + "/weblog/show.html"
# and the web server will pick it up without even hitting Rails
end
@@ -42,8 +42,8 @@ def internal_methods
controller.public_instance_methods(true)
end
- # The list of hidden actions to an empty array. Defaults to an
- # empty array. This can be modified by other modules or subclasses
+ # The list of hidden actions. Defaults to an empty array.
+ # This can be modified by other modules or subclasses
# to specify particular actions as hidden.
#
# ==== Returns
@@ -110,16 +110,23 @@ def stale?(record_or_options, additional_options = {})
#
# Examples:
# expires_in 20.minutes
- # expires_in 3.hours, :public => true
+ # expires_in 3.hours, :public => true, :must_revalidate => true
# expires_in 3.hours, 'max-stale' => 5.hours, :public => true
#
# This method will overwrite an existing Cache-Control header.
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
+ #
+ # The method will also ensure a HTTP Date header for client compatibility.
def expires_in(seconds, options = {}) #:doc:
- response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public))
+ response.cache_control.merge!(
+ :max_age => seconds,
+ :public => options.delete(:public),
+ :must_revalidate => options.delete(:must_revalidate)
+ )
options.delete(:private)
response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
+ response.date = Time.now unless response.date?
end
# Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should occur by the browser or
@@ -267,7 +267,7 @@ def default_action
end
def resource_errors
- respond_to?("#{format}_resource_errors") ? send("#{format}_resource_errors") : resource.errors
+ respond_to?("#{format}_resource_errors", true) ? send("#{format}_resource_errors") : resource.errors
end
def json_resource_errors
@@ -60,6 +60,20 @@ def last_modified=(utc_time)
headers[LAST_MODIFIED] = utc_time.httpdate
end
+ def date
+ if date_header = headers['Date']
+ Time.httpdate(date_header)
+ end
+ end
+
+ def date?
+ headers.include?('Date')
+ end
+
+ def date=(utc_time)
+ headers['Date'] = utc_time.httpdate
+ end
+
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
@etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
@@ -1,4 +1,5 @@
require 'rack/utils'
+require 'active_support/core_ext/uri'
module ActionDispatch
class FileHandler
@@ -11,14 +12,14 @@ def initialize(root, cache_control)
def match?(path)
path = path.dup
- full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
+ full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
paths = "#{full_path}#{ext}"
matches = Dir[paths]
match = matches.detect { |m| File.file?(m) }
if match
match.sub!(@compiled_root, '')
- match
+ ::Rack::Utils.escape(match)
end
end
@@ -32,6 +33,14 @@ def ext
"{,#{ext},/index#{ext}}"
end
end
+
+ def unescape_path(path)
+ URI.parser.unescape(path)
+ end
+
+ def escape_glob_chars(path)
+ path.gsub(/[*?{}\[\]]/, "\\\\\\&")
+ end
end
class Static
@@ -56,6 +56,12 @@ def head(path, parameters = nil, headers = nil)
process :head, path, parameters, headers
end
+ # Performs a OPTIONS request with the given parameters. See +#get+ for
+ # more details.
+ def options(path, parameters = nil, headers = nil)
+ process :options, path, parameters, headers
+ end
+
# Performs an XMLHttpRequest request with the given parameters, mirroring
# a request from the Prototype library.
#
@@ -312,7 +318,7 @@ def reset!
@integration_session = Integration::Session.new(app)
end
- %w(get post put head delete cookies assigns
+ %w(get post put head delete options cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session
@@ -5,7 +5,8 @@
module ActionDispatch
module TestProcess
def assigns(key = nil)
- assigns = @controller.view_assigns.with_indifferent_access
+ assigns = {}.with_indifferent_access
+ @controller.view_assigns.each {|k, v| assigns.regular_writer(k, v)}
key.nil? ? assigns : assigns[key]
end
@@ -1102,22 +1102,22 @@ def submit(value=nil, options={})
# <% end %>
#
# In the example above, if @post is a new record, it will use "Create Post" as
- # submit button label, otherwise, it uses "Update Post".
+ # button label, otherwise, it uses "Update Post".
#
- # Those labels can be customized using I18n, under the helpers.submit key and accept
- # the %{model} as translation interpolation:
+ # Those labels can be customized using I18n, under the helpers.submit key
+ # (the same as submit helper) and accept the %{model} as translation interpolation:
#
# en:
# helpers:
- # button:
+ # submit:
# create: "Create a %{model}"
# update: "Confirm changes to %{model}"
#
# It also searches for a key specific for the given object:
#
# en:
# helpers:
- # button:
+ # submit:
# post:
# create: "Add %{model}"
#
@@ -330,9 +330,12 @@ def options_for_select(container, selected = nil)
container.map do |element|
html_attributes = option_html_attributes(element)
text, value = option_text_and_value(element).map { |item| item.to_s }
- selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
- disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
- %(<option value="#{ERB::Util.html_escape(value)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{ERB::Util.html_escape(text)}</option>)
+
+ html_attributes[:selected] = 'selected' if option_value_selected?(value, selected)
+ html_attributes[:disabled] = 'disabled' if disabled && option_value_selected?(value, disabled)
+ html_attributes[:value] = value
+
+ content_tag(:option, text, html_attributes)
end.join("\n").html_safe
end
@@ -472,16 +475,16 @@ def option_groups_from_collection_for_select(collection, group_method, group_lab
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
# wrap the output in an appropriate <tt><select></tt> tag.
def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil)
- body = ''
- body << content_tag(:option, prompt, { :value => "" }, true) if prompt
+ body = "".html_safe
+ body.safe_concat content_tag(:option, prompt, :value => "") if prompt
grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
- grouped_options.each do |group|
- body << content_tag(:optgroup, options_for_select(group[1], selected_key), :label => group[0])
+ grouped_options.each do |label, container|
+ body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label)
end
- body.html_safe
+ body
end
# Returns a string of option tags for pretty much any time zone in the
@@ -649,11 +652,9 @@ def collection_check_boxes(object, method, collection, value_method, text_method
private
def option_html_attributes(element)
- return "" unless Array === element
+ return {} unless Array === element
- element.select { |e| Hash === e }.reduce({}, :merge).map do |k, v|
- " #{k}=\"#{ERB::Util.html_escape(v.to_s)}\""
- end.join
+ Hash[element.select { |e| Hash === e }.reduce({}, :merge).map { |k, v| [k, ERB::Util.html_escape(v.to_s)] }]
end
def option_text_and_value(option)
@@ -14,8 +14,13 @@ def initialize(object_name, method_name, template_object, collection, group_meth
end
def render
+ option_tags_options = {
+ :selected => @options.fetch(:selected) { value(@object) },
+ :disabled => @options[:disabled]
+ }
+
select_content_tag(
- option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, value(@object)), @options, @html_options
+ option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, option_tags_options), @options, @html_options
)
end
end
@@ -163,6 +163,7 @@ def refresh(view)
pieces = @virtual_path.split("/")
name = pieces.pop
partial = !!name.sub!(/^_/, "")
+ lookup.formats = @formats
lookup.disable_cache do
lookup.find_template(name, [ pieces.join('/') ], partial, @locals)
end
@@ -23,6 +23,7 @@ def self.extensions
# and should return the rendered template as a String.
def register_template_handler(extension, handler)
@@template_handlers[extension.to_sym] = handler
+ @@template_extensions = nil
end
def template_handler_extensions
@@ -19,9 +19,9 @@ def asset_paths
def javascript_include_tag(*sources)
options = sources.extract_options!
- debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
- body = options.key?(:body) ? options.delete(:body) : false
- digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
+ debug = options.delete(:debug) { debug_assets? }
+ body = options.delete(:body) { false }
+ digest = options.delete(:digest) { digest_assets? }
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'js')
@@ -105,6 +105,12 @@ def test_head
@session.head(path,params,headers)
end
+ def test_options
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:options,path,params,headers)
+ @session.options(path,params,headers)
+ end
+
def test_xml_http_request_get
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
@@ -155,6 +161,16 @@ def test_xml_http_request_head
@session.xml_http_request(:head,path,params,headers)
end
+ def test_xml_http_request_options
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:options,path,params,headers_after_xhr)
+ @session.xml_http_request(:options,path,params,headers)
+ end
+
def test_xml_http_request_override_accept
path = "/index"; params = "blah"; headers = {:location => 'blah', "HTTP_ACCEPT" => "application/xml"}
headers_after_xhr = headers.merge(
@@ -212,7 +228,7 @@ def test_integration_methods_called
@integration_session.stubs(:generic_url_rewriter)
@integration_session.stubs(:process)
- %w( get post head put delete ).each do |verb|
+ %w( get post head put delete options ).each do |verb|
assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
end
end
@@ -91,6 +91,16 @@ def conditional_hello_with_expires_in_with_public
render :action => 'hello_world'
end
+ def conditional_hello_with_expires_in_with_must_revalidate
+ expires_in 1.minute, :must_revalidate => true
+ render :action => 'hello_world'
+ end
+
+ def conditional_hello_with_expires_in_with_public_and_must_revalidate
+ expires_in 1.minute, :public => true, :must_revalidate => true
+ render :action => 'hello_world'
+ end
+
def conditional_hello_with_expires_in_with_public_with_more_keys
expires_in 1.minute, :public => true, 'max-stale' => 5.hours
render :action => 'hello_world'
@@ -1399,6 +1409,16 @@ def test_expires_in_header_with_public
assert_equal "max-age=60, public", @response.headers["Cache-Control"]
end
+ def test_expires_in_header_with_must_revalidate
+ get :conditional_hello_with_expires_in_with_must_revalidate
+ assert_equal "max-age=60, private, must-revalidate", @response.headers["Cache-Control"]
+ end
+
+ def test_expires_in_header_with_public_and_must_revalidate
+ get :conditional_hello_with_expires_in_with_public_and_must_revalidate
+ assert_equal "max-age=60, public, must-revalidate", @response.headers["Cache-Control"]
+ end
+
def test_expires_in_header_with_additional_headers
get :conditional_hello_with_expires_in_with_public_with_more_keys
assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
@@ -1413,6 +1433,13 @@ def test_expires_now
get :conditional_hello_with_expires_now
assert_equal "no-cache", @response.headers["Cache-Control"]
end
+
+ def test_date_header_when_expires_in
+ time = Time.mktime(2011,10,30)
+ Time.stubs(:now).returns(time)
+ get :conditional_hello_with_expires_in
+ assert_equal Time.now.httpdate, @response.headers["Date"]
+ end
end
class LastModifiedRenderTest < ActionController::TestCase
Oops, something went wrong.

0 comments on commit e6ff135

Please sign in to comment.