From 45d41f0dadd9fa171f306ff356770c4492726f30 Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Thu, 19 Jun 2008 16:25:27 +0200
Subject: integrating I18n into Rails
---
actionpack/lib/action_view.rb | 2 +
.../action_view/helpers/active_record_helper.rb | 33 +-
actionpack/lib/action_view/helpers/date_helper.rb | 81 ++--
.../lib/action_view/helpers/form_options_helper.rb | 52 +-
.../lib/action_view/helpers/number_helper.rb | 18 +-
actionpack/lib/action_view/lang/en-US.rb | 93 ++++
.../template/active_record_helper_i18n_test.rb | 47 ++
actionpack/test/template/date_helper_i18n_test.rb | 99 ++++
.../test/template/form_options_helper_i18n_test.rb | 26 +
.../test/template/number_helper_i18n_test.rb | 27 ++
activerecord/lib/active_record.rb | 2 +
activerecord/lib/active_record/lang/en-US.rb | 25 +
activerecord/lib/active_record/validations.rb | 184 ++++---
activerecord/test/cases/validations_i18n_test.rb | 539 +++++++++++++++++++++
.../active_support/core_ext/array/conversions.rb | 14 +-
activesupport/lib/active_support/vendor.rb | 7 +
activesupport/lib/active_support/vendor/i18n-0.0.1 | 1 +
17 files changed, 1082 insertions(+), 168 deletions(-)
create mode 100644 actionpack/lib/action_view/lang/en-US.rb
create mode 100644 actionpack/test/template/active_record_helper_i18n_test.rb
create mode 100644 actionpack/test/template/date_helper_i18n_test.rb
create mode 100644 actionpack/test/template/form_options_helper_i18n_test.rb
create mode 100644 actionpack/test/template/number_helper_i18n_test.rb
create mode 100644 activerecord/lib/active_record/lang/en-US.rb
create mode 100644 activerecord/test/cases/validations_i18n_test.rb
create mode 160000 activesupport/lib/active_support/vendor/i18n-0.0.1
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 973020a768..33d50a61c4 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -32,6 +32,8 @@ require 'action_view/base'
require 'action_view/partials'
require 'action_view/template_error'
+require 'action_view/lang/en-US.rb'
+
ActionView::Base.class_eval do
include ActionView::Partials
diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb
index f3f204cc97..5ad9d5f76d 100644
--- a/actionpack/lib/action_view/helpers/active_record_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -151,12 +151,17 @@ module ActionView
# instance yourself and set it up. View the source of this method to see how easy it is.
def error_messages_for(*params)
options = params.extract_options!.symbolize_keys
+
if object = options.delete(:object)
objects = [object].flatten
else
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
end
- count = objects.inject(0) {|sum, object| sum + object.errors.count }
+
+ count = objects.inject(0) {|sum, object| sum + object.errors.count }
+ locale = options[:locale]
+ locale ||= request.locale if respond_to?(:request)
+
unless count.zero?
html = {}
[:id, :class].each do |key|
@@ -168,21 +173,29 @@ module ActionView
end
end
options[:object_name] ||= params.first
- options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message)
- options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message)
- error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
- contents = ''
- contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank?
- contents << content_tag(:p, options[:message]) unless options[:message].blank?
- contents << content_tag(:ul, error_messages)
+ I18n.with_options :locale => locale, :scope => [:active_record, :error] do |locale|
+ header_message = if options.include?(:header_message)
+ options[:header_message]
+ else
+ object_name = options[:object_name].to_s.gsub('_', ' ')
+ locale.t :header_message, :count => count, :object_name => object_name
+ end
+ message = options.include?(:message) ? options[:message] : locale.t(:message)
+ error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
+
+ contents = ''
+ contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
+ contents << content_tag(:p, message) unless message.blank?
+ contents << content_tag(:ul, error_messages)
- content_tag(:div, contents, html)
+ content_tag(:div, contents, html)
+ end
else
''
end
end
-
+
private
def all_input_tags(record, record_name, options)
input_block = options[:input_block] || default_input_block
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 7ed6272898..0337be0744 100755
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -58,35 +58,43 @@ module ActionView
# distance_of_time_in_words(to_time, from_time, true) # => over 6 years
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
#
- def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
+ locale = options[:locale]
+ locale ||= request.locale if respond_to?(:request)
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
distance_in_minutes = (((to_time - from_time).abs)/60).round
distance_in_seconds = ((to_time - from_time).abs).round
- case distance_in_minutes
- when 0..1
- return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
- case distance_in_seconds
- when 0..4 then 'less than 5 seconds'
- when 5..9 then 'less than 10 seconds'
- when 10..19 then 'less than 20 seconds'
- when 20..39 then 'half a minute'
- when 40..59 then 'less than a minute'
- else '1 minute'
- end
+ I18n.with_options :locale => locale, :scope => :'datetime.distance_in_words' do |locale|
+ case distance_in_minutes
+ when 0..1
+ return distance_in_minutes == 0 ?
+ locale.t(:less_than_x_minutes, :count => 1) :
+ locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds
+
+ case distance_in_seconds
+ when 0..4 then locale.t :less_than_x_seconds, :count => 5
+ when 5..9 then locale.t :less_than_x_seconds, :count => 10
+ when 10..19 then locale.t :less_than_x_seconds, :count => 20
+ when 20..39 then locale.t :half_a_minute
+ when 40..59 then locale.t :less_than_x_minutes, :count => 1
+ else locale.t :x_minutes, :count => 1
+ end
- when 2..44 then "#{distance_in_minutes} minutes"
- when 45..89 then 'about 1 hour'
- when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
- when 1440..2879 then '1 day'
- when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
- when 43200..86399 then 'about 1 month'
- when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
- when 525600..1051199 then 'about 1 year'
- else "over #{(distance_in_minutes / 525600).round} years"
+ when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
+ when 45..89 then locale.t :about_x_hours, :count => 1
+ when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
+ when 1440..2879 then locale.t :x_days, :count => 1
+ when 2880..43199 then locale.t :x_days, :count => (distance_in_minutes / 1440).round
+ when 43200..86399 then locale.t :about_x_months, :count => 1
+ when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes / 43200).round
+ when 525600..1051199 then locale.t :about_x_years, :count => 1
+ else locale.t :over_x_years, :count => (distance_in_minutes / 525600).round
+ end
end
- end
+ end
# Like distance_of_time_in_words, but where to_time is fixed to Time.now .
#
@@ -498,13 +506,19 @@ module ActionView
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
#
def select_month(date, options = {}, html_options = {})
+ locale = options[:locale]
+ locale ||= request.locale if respond_to?(:request)
+
val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
if options[:use_hidden]
hidden_html(options[:field_name] || 'month', val, options)
else
month_options = []
- month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES)
+ month_names = options[:use_month_names] || begin
+ (options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names').t locale
+ end
month_names.unshift(nil) if month_names.size < 13
+
1.upto(12) do |month_number|
month_name = if options[:use_month_numbers]
month_number
@@ -522,7 +536,7 @@ module ActionView
end
select_html(options[:field_name] || 'month', month_options.join, options, html_options)
end
- end
+ end
# Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
# can be changed using the :start_year and :end_year keys in the +options+. Both ascending and descending year
@@ -612,15 +626,17 @@ module ActionView
private
def date_or_time_select(options, html_options = {})
+ locale = options[:locale]
+
defaults = { :discard_type => true }
options = defaults.merge(options)
datetime = value(object)
datetime ||= default_time_from_options(options[:default]) unless options[:include_blank]
-
+
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
- order = (options[:order] ||= [:year, :month, :day])
-
+ order = options[:order] ||= :'date.order'.t(locale)
+
# Discard explicit and implicit by not being included in the :order
discard = {}
discard[:year] = true if options[:discard_year] or !order.include?(:year)
@@ -629,19 +645,19 @@ module ActionView
discard[:hour] = true if options[:discard_hour]
discard[:minute] = true if options[:discard_minute] or discard[:hour]
discard[:second] = true unless options[:include_seconds] && !discard[:minute]
-
+
# If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are valid
# (otherwise it could be 31 and february wouldn't be a valid date)
if datetime && discard[:day] && !discard[:month]
datetime = datetime.change(:day => 1)
end
-
+
# Maintain valid dates by including hidden fields for discarded elements
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
-
+
# Ensure proper ordering of :hour, :minute and :second
[:hour, :minute, :second].each { |o| order.delete(o); order.push(o) }
-
+
date_or_time_select = ''
order.reverse.each do |param|
# Send hidden fields for discarded elements once output has started
@@ -656,9 +672,8 @@ module ActionView
when :second then options[:include_seconds] ? " : " : ""
else ""
end)
-
end
-
+
date_or_time_select
end
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index b3f8e63c1b..c782c0a816 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -276,16 +276,25 @@ module ActionView
# that they will be listed above the rest of the (long) list.
#
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
- def country_options_for_select(selected = nil, priority_countries = nil)
+ def country_options_for_select(*args)
+ options = args.extract_options!
+
+ locale = options[:locale]
+ locale ||= request.locale if respond_to?(:request)
+
+ selected, priority_countries = *args
+ countries = :'countries.names'.t options[:locale]
country_options = ""
if priority_countries
+ # TODO priority_countries need to be translated?
country_options += options_for_select(priority_countries, selected)
country_options += "------------- \n"
end
- return country_options + options_for_select(COUNTRIES, selected)
+ return country_options + options_for_select(countries, selected)
end
+
# Returns a string of option tags for pretty much any time zone in the
# world. Supply a TimeZone name as +selected+ to have it marked as the
@@ -340,43 +349,8 @@ module ActionView
end
# All the countries included in the country_options output.
- COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
- "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
- "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
- "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
- "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
- "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
- "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
- "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
- "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
- "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
- "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
- "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
- "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
- "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
- "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
- "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
- "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
- "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
- "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
- "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
- "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
- "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
- "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
- "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
- "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
- "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
- "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
- "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
- "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
- "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
- "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
- "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
- "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
- "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
- "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
- "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
- "Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES")
+ # only included for backwards compatibility, please use the I18n interface
+ COUNTRIES = :'countries.names'.t 'en-US' unless const_defined?("COUNTRIES")
end
class InstanceTag #:nodoc:
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 120bb4cc1f..9d98036f2d 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -69,13 +69,19 @@ module ActionView
# number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u")
# # => 1234567890,50 £
def number_to_currency(number, options = {})
- options = options.stringify_keys
- precision = options["precision"] || 2
- unit = options["unit"] || "$"
- separator = precision > 0 ? options["separator"] || "." : ""
- delimiter = options["delimiter"] || ","
- format = options["format"] || "%u%n"
+ options = options.symbolize_keys
+
+ locale = options[:locale]
+ locale ||= request.locale if respond_to?(:request)
+ defaults = :'currency.format'.t(locale) || {}
+ precision = options[:precision] || defaults[:precision]
+ unit = options[:unit] || defaults[:unit]
+ separator = options[:separator] || defaults[:separator]
+ delimiter = options[:delimiter] || defaults[:delimiter]
+ format = options[:format] || defaults[:format]
+ separator = '' if precision == 0
+
begin
parts = number_with_precision(number, precision).split('.')
format.gsub(/%n/, number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s).gsub(/%u/, unit)
diff --git a/actionpack/lib/action_view/lang/en-US.rb b/actionpack/lib/action_view/lang/en-US.rb
new file mode 100644
index 0000000000..659f96a5f3
--- /dev/null
+++ b/actionpack/lib/action_view/lang/en-US.rb
@@ -0,0 +1,93 @@
+I18n.backend.add_translations :'en-US', {
+ :date => {
+ :formats => {
+ :default => "%Y-%m-%d",
+ :short => "%b %d",
+ :long => "%B %d, %Y",
+ },
+ :day_names => Date::DAYNAMES,
+ :abbr_day_names => Date::ABBR_DAYNAMES,
+ :month_names => Date::MONTHNAMES,
+ :abbr_month_names => Date::ABBR_MONTHNAMES,
+ :order => [:year, :month, :day]
+ },
+ :time => {
+ :formats => {
+ :default => "%a, %d %b %Y %H:%M:%S %z",
+ :short => "%d %b %H:%M",
+ :long => "%B %d, %Y %H:%M",
+ },
+ :am => 'am',
+ :pm => 'pm'
+ },
+ :datetime => {
+ :distance_in_words => {
+ :half_a_minute => 'half a minute',
+ :less_than_x_seconds => ['less than 1 second', 'less than {{count}} seconds'],
+ :x_seconds => ['1 second', '{{count}} seconds'],
+ :less_than_x_minutes => ['less than a minute', 'less than {{count}} minutes'],
+ :x_minutes => ['1 minute', '{{count}} minutes'],
+ :about_x_hours => ['about 1 hour', 'about {{count}} hours'],
+ :x_days => ['1 day', '{{count}} days'],
+ :about_x_months => ['about 1 month', 'about {{count}} months'],
+ :x_months => ['1 month', '{{count}} months'],
+ :about_x_years => ['about 1 year', 'about {{count}} year'],
+ :over_x_years => ['over 1 year', 'over {{count}} years']
+ }
+ },
+ :currency => {
+ :format => {
+ :unit => '$',
+ :precision => 2,
+ :separator => '.',
+ :delimiter => ',',
+ :format => '%u%n',
+ }
+ },
+ :countries => {
+ :names => ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
+ "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
+ "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
+ "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
+ "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
+ "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
+ "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
+ "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
+ "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
+ "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
+ "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
+ "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece",
+ "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
+ "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
+ "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
+ "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
+ "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
+ "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
+ "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
+ "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
+ "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
+ "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
+ "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
+ "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
+ "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
+ "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
+ "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
+ "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
+ "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
+ "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
+ "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
+ "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
+ "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
+ "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
+ "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
+ "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
+ "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
+ "Yemen", "Zambia", "Zimbabwe"]
+ },
+ :active_record => {
+ :error => {
+ :header_message => ["1 error prohibited this {{object_name}} from being saved", "{{count}} errors prohibited this {{object_name}} from being saved"],
+ :message => "There were problems with the following fields:"
+ }
+ }
+}
diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb
new file mode 100644
index 0000000000..057fb9bd1a
--- /dev/null
+++ b/actionpack/test/template/active_record_helper_i18n_test.rb
@@ -0,0 +1,47 @@
+require 'abstract_unit'
+
+class ActiveRecordHelperI18nTest < Test::Unit::TestCase
+ include ActionView::Helpers::ActiveRecordHelper
+
+ attr_reader :request
+ def setup
+ @request = mock
+ @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
+ stubs(:content_tag).returns 'content_tag'
+
+ I18n.stubs(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns "1 error prohibited this from being saved"
+ I18n.stubs(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
+ end
+
+ def test_error_messages_for_given_a_locale_it_does_not_check_request_for_locale
+ request.expects(:locale).never
+ @object.errors.stubs(:count).returns 0
+ error_messages_for(:object => @object, :locale => 'en-US')
+ end
+
+ def test_error_messages_for_given_no_locale_it_checks_request_for_locale
+ request.expects(:locale).returns 'en-US'
+ @object.errors.stubs(:count).returns 0
+ error_messages_for(:object => @object)
+ end
+
+ def test_error_messages_for_given_a_header_message_option_it_does_not_translate_header_message
+ I18n.expects(:translate).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').never
+ error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en-US')
+ end
+
+ def test_error_messages_for_given_no_header_message_option_it_translates_header_message
+ I18n.expects(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns 'header message'
+ error_messages_for(:object => @object, :locale => 'en-US')
+ end
+
+ def test_error_messages_for_given_a_message_option_it_does_not_translate_message
+ I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).never
+ error_messages_for(:object => @object, :message => 'message', :locale => 'en-US')
+ end
+
+ def test_error_messages_for_given_no_message_option_it_translates_message
+ I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
+ error_messages_for(:object => @object, :locale => 'en-US')
+ end
+end
\ No newline at end of file
diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb
new file mode 100644
index 0000000000..9b7c03a400
--- /dev/null
+++ b/actionpack/test/template/date_helper_i18n_test.rb
@@ -0,0 +1,99 @@
+require 'abstract_unit'
+
+class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
+ include ActionView::Helpers::DateHelper
+ attr_reader :request
+
+ def setup
+ @request = mock
+ @from = Time.mktime(2004, 6, 6, 21, 45, 0)
+ end
+
+ # distance_of_time_in_words
+
+ def test_distance_of_time_in_words_given_a_locale_it_does_not_check_request_for_locale
+ request.expects(:locale).never
+ distance_of_time_in_words @from, @from + 1.second, false, :locale => 'en-US'
+ end
+
+ def test_distance_of_time_in_words_given_no_locale_it_checks_request_for_locale
+ request.expects(:locale).returns 'en-US'
+ distance_of_time_in_words @from, @from + 1.second
+ end
+
+ def test_distance_of_time_in_words_calls_i18n
+ { # with include_seconds
+ [2.seconds, true] => [:'less_than_x_seconds', 5],
+ [9.seconds, true] => [:'less_than_x_seconds', 10],
+ [19.seconds, true] => [:'less_than_x_seconds', 20],
+ [30.seconds, true] => [:'half_a_minute', nil],
+ [59.seconds, true] => [:'less_than_x_minutes', 1],
+ [60.seconds, true] => [:'x_minutes', 1],
+
+ # without include_seconds
+ [29.seconds, false] => [:'less_than_x_minutes', 1],
+ [60.seconds, false] => [:'x_minutes', 1],
+ [44.minutes, false] => [:'x_minutes', 44],
+ [61.minutes, false] => [:'about_x_hours', 1],
+ [24.hours, false] => [:'x_days', 1],
+ [30.days, false] => [:'about_x_months', 1],
+ [60.days, false] => [:'x_months', 2],
+ [1.year, false] => [:'about_x_years', 1],
+ [3.years, false] => [:'over_x_years', 3]
+
+ }.each do |passed, expected|
+ assert_distance_of_time_in_words_translates_key passed, expected
+ end
+ end
+
+ def assert_distance_of_time_in_words_translates_key(passed, expected)
+ diff, include_seconds = *passed
+ key, count = *expected
+ to = @from + diff
+
+ options = {:locale => 'en-US', :scope => :'datetime.distance_in_words'}
+ options[:count] = count if count
+
+ I18n.expects(:t).with(key, options)
+ distance_of_time_in_words(@from, to, include_seconds, :locale => 'en-US')
+ end
+end
+
+class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
+ include ActionView::Helpers::DateHelper
+ attr_reader :request
+
+ def setup
+ @request = mock
+ I18n.stubs(:translate).with(:'date.month_names', 'en-US').returns Date::MONTHNAMES
+ end
+
+ # select_month
+
+ def test_select_month_given_use_month_names_option_does_not_translate_monthnames
+ I18n.expects(:translate).never
+ select_month(8, :locale => 'en-US', :use_month_names => Date::MONTHNAMES)
+ end
+
+ def test_select_month_translates_monthnames
+ I18n.expects(:translate).with(:'date.month_names', 'en-US').returns Date::MONTHNAMES
+ select_month(8, :locale => 'en-US')
+ end
+
+ def test_select_month_given_use_short_month_option_translates_abbr_monthnames
+ I18n.expects(:translate).with(:'date.abbr_month_names', 'en-US').returns Date::ABBR_MONTHNAMES
+ select_month(8, :locale => 'en-US', :use_short_month => true)
+ end
+
+ # date_or_time_select
+
+ def test_date_or_time_select_given_an_order_options_does_not_translate_order
+ I18n.expects(:translate).never
+ datetime_select('post', 'updated_at', :order => [:year, :month, :day], :locale => 'en-US')
+ end
+
+ def test_date_or_time_select_given_no_order_options_translates_order
+ I18n.expects(:translate).with(:'date.order', 'en-US').returns [:year, :month, :day]
+ datetime_select('post', 'updated_at', :locale => 'en-US')
+ end
+end
\ No newline at end of file
diff --git a/actionpack/test/template/form_options_helper_i18n_test.rb b/actionpack/test/template/form_options_helper_i18n_test.rb
new file mode 100644
index 0000000000..c9fc0768bb
--- /dev/null
+++ b/actionpack/test/template/form_options_helper_i18n_test.rb
@@ -0,0 +1,26 @@
+require 'abstract_unit'
+
+class FormOptionsHelperI18nTests < Test::Unit::TestCase
+ include ActionView::Helpers::FormOptionsHelper
+ attr_reader :request
+
+ def setup
+ @request = mock
+ end
+
+ def test_country_options_for_select_given_a_locale_it_does_not_check_request_for_locale
+ request.expects(:locale).never
+ country_options_for_select :locale => 'en-US'
+ end
+
+ def test_country_options_for_select_given_no_locale_it_checks_request_for_locale
+ request.expects(:locale).returns 'en-US'
+ country_options_for_select
+ end
+
+ def test_country_options_for_select_translates_country_names
+ countries = ActionView::Helpers::FormOptionsHelper::COUNTRIES
+ I18n.expects(:translate).with(:'countries.names', 'en-US').returns countries
+ country_options_for_select :locale => 'en-US'
+ end
+end
\ No newline at end of file
diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb
new file mode 100644
index 0000000000..47cb035f56
--- /dev/null
+++ b/actionpack/test/template/number_helper_i18n_test.rb
@@ -0,0 +1,27 @@
+require 'abstract_unit'
+
+class NumberHelperI18nTests < Test::Unit::TestCase
+ include ActionView::Helpers::NumberHelper
+
+ attr_reader :request
+ def setup
+ @request = mock
+ @defaults = {:separator => ".", :unit => "$", :format => "%u%n", :delimiter => ",", :precision => 2}
+ I18n.backend.add_translations 'en-US', :currency => {:format => @defaults}
+ end
+
+ def test_number_to_currency_given_a_locale_it_does_not_check_request_for_locale
+ request.expects(:locale).never
+ number_to_currency(1, :locale => 'en-US')
+ end
+
+ def test_number_to_currency_given_no_locale_it_checks_request_for_locale
+ request.expects(:locale).returns 'en-US'
+ number_to_currency(1)
+ end
+
+ def test_number_to_currency_translates_currency_formats
+ I18n.expects(:translate).with(:'currency.format', 'en-US').returns @defaults
+ number_to_currency(1, :locale => 'en-US')
+ end
+end
\ No newline at end of file
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index d4f7170305..b379bd26f8 100755
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -80,3 +80,5 @@ end
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/schema_dumper'
+
+require 'active_record/lang/en-US.rb'
diff --git a/activerecord/lib/active_record/lang/en-US.rb b/activerecord/lib/active_record/lang/en-US.rb
new file mode 100644
index 0000000000..7c3bcfd85e
--- /dev/null
+++ b/activerecord/lib/active_record/lang/en-US.rb
@@ -0,0 +1,25 @@
+I18n.backend.add_translations :'en-US', {
+ :active_record => {
+ :error_messages => {
+ :inclusion => "is not included in the list",
+ :exclusion => "is reserved",
+ :invalid => "is invalid",
+ :confirmation => "doesn't match confirmation",
+ :accepted => "must be accepted",
+ :empty => "can't be empty",
+ :blank => "can't be blank",
+ :too_long => "is too long (maximum is {{count}} characters)",
+ :too_short => "is too short (minimum is {{count}} characters)",
+ :wrong_length => "is the wrong length (should be {{count}} characters)",
+ :taken => "has already been taken",
+ :not_a_number => "is not a number",
+ :greater_than => "must be greater than {{count}}",
+ :greater_than_or_equal_to => "must be greater than or equal to {{count}}",
+ :equal_to => "must be equal to {{count}}",
+ :less_than => "must be less than {{count}}",
+ :less_than_or_equal_to => "must be less than or equal to {{count}}",
+ :odd => "must be odd",
+ :even => "must be even"
+ }
+ }
+}
\ No newline at end of file
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index c4e370d017..f54fb80137 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -23,30 +23,30 @@ module ActiveRecord
@base, @errors = base, {}
end
- @@default_error_messages = {
- :inclusion => "is not included in the list",
- :exclusion => "is reserved",
- :invalid => "is invalid",
- :confirmation => "doesn't match confirmation",
- :accepted => "must be accepted",
- :empty => "can't be empty",
- :blank => "can't be blank",
- :too_long => "is too long (maximum is %d characters)",
- :too_short => "is too short (minimum is %d characters)",
- :wrong_length => "is the wrong length (should be %d characters)",
- :taken => "has already been taken",
- :not_a_number => "is not a number",
- :greater_than => "must be greater than %d",
- :greater_than_or_equal_to => "must be greater than or equal to %d",
- :equal_to => "must be equal to %d",
- :less_than => "must be less than %d",
- :less_than_or_equal_to => "must be less than or equal to %d",
- :odd => "must be odd",
- :even => "must be even"
- }
-
- # Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
- cattr_accessor :default_error_messages
+ # @@default_error_messages = {
+ # :inclusion => "is not included in the list",
+ # :exclusion => "is reserved",
+ # :invalid => "is invalid",
+ # :confirmation => "doesn't match confirmation",
+ # :accepted => "must be accepted",
+ # :empty => "can't be empty",
+ # :blank => "can't be blank",
+ # :too_long => "is too long (maximum is %d characters)",
+ # :too_short => "is too short (minimum is %d characters)",
+ # :wrong_length => "is the wrong length (should be %d characters)",
+ # :taken => "has already been taken",
+ # :not_a_number => "is not a number",
+ # :greater_than => "must be greater than %d",
+ # :greater_than_or_equal_to => "must be greater than or equal to %d",
+ # :equal_to => "must be equal to %d",
+ # :less_than => "must be less than %d",
+ # :less_than_or_equal_to => "must be less than or equal to %d",
+ # :odd => "must be odd",
+ # :even => "must be even"
+ # }
+ #
+ # # Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
+ # cattr_accessor :default_error_messages
# Adds an error to the base object instead of any particular attribute. This is used
@@ -61,27 +61,34 @@ module ActiveRecord
# for the same attribute and ensure that this error object returns false when asked if empty? . More than one
# error can be added to the same +attribute+ in which case an array will be returned on a call to on(attribute) .
# If no +msg+ is supplied, "invalid" is assumed.
- def add(attribute, msg = @@default_error_messages[:invalid])
- @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
- @errors[attribute.to_s] << msg
- end
+ def add(attribute, message = nil)
+ message ||= :"active_record.error_messages.invalid".t
+ @errors[attribute.to_s] ||= []
+ @errors[attribute.to_s] << message
+ end
# Will add an error message to each of the attributes in +attributes+ that is empty.
- def add_on_empty(attributes, msg = @@default_error_messages[:empty])
+ def add_on_empty(attributes, custom_message = nil)
for attr in [attributes].flatten
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
- is_empty = value.respond_to?("empty?") ? value.empty? : false
- add(attr, msg) unless !value.nil? && !is_empty
+ is_empty = value.respond_to?("empty?") ? value.empty? : false
+ add(attr, generate_message(attr, :empty, :default => custom_message)) unless !value.nil? && !is_empty
end
end
# Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
- def add_on_blank(attributes, msg = @@default_error_messages[:blank])
+ def add_on_blank(attributes, custom_message = nil)
for attr in [attributes].flatten
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
- add(attr, msg) if value.blank?
+ add(attr, generate_message(attr, :blank, :default => custom_message)) if value.blank?
end
end
+
+ def generate_message(attr, key, options = {})
+ scope = [:active_record, :error_messages]
+ key.t(options.merge(:scope => scope + [:custom, @base.class.name.downcase, attr])) ||
+ key.t(options.merge(:scope => scope))
+ end
# Returns true if the specified +attribute+ has errors associated with it.
#
@@ -166,22 +173,25 @@ module ActiveRecord
# company = Company.create(:address => '123 First St.')
# company.errors.full_messages # =>
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
- def full_messages
+ def full_messages(options = {})
full_messages = []
+ locale = options[:locale]
@errors.each_key do |attr|
- @errors[attr].each do |msg|
- next if msg.nil?
-
+ @errors[attr].each do |message|
+ next unless message
+
if attr == "base"
- full_messages << msg
+ full_messages << message
else
- full_messages << @base.class.human_attribute_name(attr) + " " + msg
+ key = :"active_record.human_attribute_names.#{@base.class.name.underscore.to_sym}.#{attr}"
+ attr_name = key.t(locale) || @base.class.human_attribute_name(attr)
+ full_messages << attr_name + " " + message
end
end
end
full_messages
- end
+ end
# Returns true if no errors have been added.
def empty?
@@ -388,15 +398,18 @@ module ActiveRecord
# not occur (e.g. :unless => :skip_validation , or :unless => Proc.new { |user| user.signup_step <= 2 } ). The
# method, proc or string should return or evaluate to a true or false value.
def validates_confirmation_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
+ configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
attr_accessor(*(attr_names.map { |n| "#{n}_confirmation" }))
validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
+ unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
+ message = record.errors.generate_message(attr_name, :confirmation, :default => configuration[:message])
+ record.errors.add(attr_name, message)
+ end
end
- end
+ end
# Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
#
@@ -422,7 +435,7 @@ module ActiveRecord
# not occur (e.g. :unless => :skip_validation , or :unless => Proc.new { |user| user.signup_step <= 2 } ). The
# method, proc or string should return or evaluate to a true or false value.
def validates_acceptance_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save, :allow_nil => true, :accept => "1" }
+ configuration = { :on => :save, :allow_nil => true, :accept => "1" }
configuration.update(attr_names.extract_options!)
db_cols = begin
@@ -434,7 +447,10 @@ module ActiveRecord
attr_accessor(*names)
validates_each(attr_names,configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message]) unless value == configuration[:accept]
+ unless value == configuration[:accept]
+ message = record.errors.generate_message(attr_name, :accepted, :default => configuration[:message])
+ record.errors.add(attr_name, message)
+ end
end
end
@@ -461,7 +477,7 @@ module ActiveRecord
# method, proc or string should return or evaluate to a true or false value.
#
def validates_presence_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save }
+ configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
# can't use validates_each here, because it cannot cope with nonexistent attributes,
@@ -505,11 +521,7 @@ module ActiveRecord
# method, proc or string should return or evaluate to a true or false value.
def validates_length_of(*attrs)
# Merge given options with defaults.
- options = {
- :too_long => ActiveRecord::Errors.default_error_messages[:too_long],
- :too_short => ActiveRecord::Errors.default_error_messages[:too_short],
- :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
- }.merge(DEFAULT_VALIDATION_OPTIONS)
+ options = {}.merge(DEFAULT_VALIDATION_OPTIONS)
options.update(attrs.extract_options!.symbolize_keys)
# Ensure that one and only one range option is specified.
@@ -531,15 +543,14 @@ module ActiveRecord
when :within, :in
raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
- too_short = options[:too_short] % option_value.begin
- too_long = options[:too_long] % option_value.end
-
validates_each(attrs, options) do |record, attr, value|
value = value.split(//) if value.kind_of?(String)
if value.nil? or value.size < option_value.begin
- record.errors.add(attr, too_short)
+ message = record.errors.generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
+ record.errors.add(attr, message)
elsif value.size > option_value.end
- record.errors.add(attr, too_long)
+ message = record.errors.generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end)
+ record.errors.add(attr, message)
end
end
when :is, :minimum, :maximum
@@ -549,11 +560,14 @@ module ActiveRecord
validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
- message = (options[:message] || options[message_options[option]]) % option_value
-
validates_each(attrs, options) do |record, attr, value|
value = value.split(//) if value.kind_of?(String)
- record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
+ unless !value.nil? and value.size.method(validity_checks[option])[option_value]
+ key = message_options[option]
+ custom_message = options[:message] || options[key]
+ message = record.errors.generate_message(attr, key, :default => custom_message, :count => option_value)
+ record.errors.add(attr, message)
+ end
end
end
end
@@ -595,7 +609,7 @@ module ActiveRecord
# not occur (e.g. :unless => :skip_validation , or :unless => Proc.new { |user| user.signup_step <= 2 } ). The
# method, proc or string should return or evaluate to a true or false value.
def validates_uniqueness_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken], :case_sensitive => true }
+ configuration = { :case_sensitive => true }
configuration.update(attr_names.extract_options!)
validates_each(attr_names,configuration) do |record, attr_name, value|
@@ -654,8 +668,11 @@ module ActiveRecord
if configuration[:case_sensitive] && finder_class.columns_hash[attr_name.to_s].text?
found = results.any? { |a| a[attr_name.to_s] == value }
end
-
- record.errors.add(attr_name, configuration[:message]) if found
+
+ if found
+ message = record.errors.generate_message(attr_name, :taken, :default => configuration[:message])
+ record.errors.add(attr_name, message)
+ end
end
end
end
@@ -685,13 +702,16 @@ module ActiveRecord
# not occur (e.g. :unless => :skip_validation , or :unless => Proc.new { |user| user.signup_step <= 2 } ). The
# method, proc or string should return or evaluate to a true or false value.
def validates_format_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
+ configuration = { :on => :save, :with => nil }
configuration.update(attr_names.extract_options!)
raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message] % value) unless value.to_s =~ configuration[:with]
+ unless value.to_s =~ configuration[:with]
+ message = record.errors.generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
+ record.errors.add(attr_name, message)
+ end
end
end
@@ -715,7 +735,7 @@ module ActiveRecord
# not occur (e.g. :unless => :skip_validation , or :unless => Proc.new { |user| user.signup_step <= 2 } ). The
# method, proc or string should return or evaluate to a true or false value.
def validates_inclusion_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save }
+ configuration = { :on => :save, :with => nil }
configuration.update(attr_names.extract_options!)
enum = configuration[:in] || configuration[:within]
@@ -723,7 +743,10 @@ module ActiveRecord
raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message] % value) unless enum.include?(value)
+ unless enum.include?(value)
+ message = record.errors.generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
+ record.errors.add(attr_name, message)
+ end
end
end
@@ -747,7 +770,7 @@ module ActiveRecord
# not occur (e.g. :unless => :skip_validation , or :unless => Proc.new { |user| user.signup_step <= 2 } ). The
# method, proc or string should return or evaluate to a true or false value.
def validates_exclusion_of(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:exclusion], :on => :save }
+ configuration = { :on => :save, :with => nil }
configuration.update(attr_names.extract_options!)
enum = configuration[:in] || configuration[:within]
@@ -755,7 +778,10 @@ module ActiveRecord
raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message] % value) if enum.include?(value)
+ if enum.include?(value)
+ message = record.errors.generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value)
+ record.errors.add(attr_name, message)
+ end
end
end
@@ -791,12 +817,14 @@ module ActiveRecord
# not occur (e.g. :unless => :skip_validation , or :unless => Proc.new { |user| user.signup_step <= 2 } ). The
# method, proc or string should return or evaluate to a true or false value.
def validates_associated(*attr_names)
- configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save }
+ configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, configuration[:message]) unless
- (value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v }
+ unless (value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v }
+ message = record.errors.generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
+ record.errors.add(attr_name, message)
+ end
end
end
@@ -844,7 +872,8 @@ module ActiveRecord
if configuration[:only_integer]
unless raw_value.to_s =~ /\A[+-]?\d+\Z/
- record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
+ message = record.errors.generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
+ record.errors.add(attr_name, message)
next
end
raw_value = raw_value.to_i
@@ -852,7 +881,8 @@ module ActiveRecord
begin
raw_value = Kernel.Float(raw_value.to_s)
rescue ArgumentError, TypeError
- record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
+ message = record.errors.generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
+ record.errors.add(attr_name, message)
next
end
end
@@ -860,10 +890,12 @@ module ActiveRecord
numericality_options.each do |option|
case option
when :odd, :even
- record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[option]) unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
+ unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
+ message = record.errors.generate_message(attr_name, option, :value => raw_value, :default => configuration[:message])
+ record.errors.add(attr_name, message)
+ end
else
- message = configuration[:message] || ActiveRecord::Errors.default_error_messages[option]
- message = message % configuration[option] if configuration[option]
+ message = record.errors.generate_message(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option])
record.errors.add(attr_name, message) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
end
end
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb
new file mode 100644
index 0000000000..eb454fca20
--- /dev/null
+++ b/activerecord/test/cases/validations_i18n_test.rb
@@ -0,0 +1,539 @@
+require "cases/helper"
+require 'models/topic'
+require 'models/reply'
+
+class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
+ def setup
+ reset_callbacks Topic
+ @topic = Topic.new
+ I18n.backend.add_translations('en-US', :active_record => {:error_messages => {:custom => nil}})
+ end
+
+ def teardown
+ reset_callbacks Topic
+ load 'active_record/lang/en-US.rb'
+ end
+
+ def unique_topic
+ @unique ||= Topic.create :title => 'unique!'
+ end
+
+ def replied_topic
+ @replied_topic ||= begin
+ topic = Topic.create(:title => "topic")
+ topic.replies << Reply.new
+ topic
+ end
+ end
+
+ def reset_callbacks(*models)
+ models.each do |model|
+ model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ end
+ end
+
+ # ActiveRecord::Errors
+
+ def test_errors_generate_message_translates_custom_model_attribute_key
+ global_scope = [:active_record, :error_messages]
+ custom_scope = global_scope + [:custom, 'topic', :title]
+
+ I18n.expects(:translate).with(:invalid, :scope => custom_scope).returns 'translation'
+ I18n.expects(:translate).with(:invalid, :scope => global_scope).never
+
+ @topic.errors.generate_message :title, :invalid
+ end
+
+ def test_errors_generate_message_given_a_custom_message_translates_custom_model_attribute_key_with_custom_message_as_default
+ custom_scope = [:active_record, :error_messages, :custom, 'topic', :title]
+
+ I18n.expects(:translate).with(:invalid, :scope => custom_scope, :default => 'default from class def').returns 'translation'
+ @topic.errors.generate_message :title, :invalid, :default => 'default from class def'
+ end
+
+ def test_errors_generate_message_given_no_custom_message_falls_back_to_global_default_key_translation
+ global_scope = [:active_record, :error_messages]
+ custom_scope = global_scope + [:custom, 'topic', :title]
+
+ I18n.stubs(:translate).with(:invalid, :scope => custom_scope).returns nil
+ I18n.expects(:translate).with(:invalid, :scope => global_scope)
+ @topic.errors.generate_message :title, :invalid
+ end
+
+ def test_errors_add_given_no_message_it_translates_invalid
+ I18n.expects(:translate).with(:"active_record.error_messages.invalid")
+ @topic.errors.add :title
+ end
+
+ def test_errors_add_on_empty_generates_message
+ @topic.errors.expects(:generate_message).with(:title, :empty, {:default => nil})
+ @topic.errors.add_on_empty :title
+ end
+
+ def test_errors_add_on_empty_generates_message_with_custom_default_message
+ @topic.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'})
+ @topic.errors.add_on_empty :title, 'custom'
+ end
+
+ def test_errors_add_on_blank_generates_message
+ @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
+ @topic.errors.add_on_blank :title
+ end
+
+ def test_errors_add_on_blank_generates_message_with_custom_default_message
+ @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
+ @topic.errors.add_on_blank :title, 'custom'
+ end
+
+ def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
+ @topic.errors.instance_variable_set :@errors, { 'title' => 'empty' }
+ I18n.expects(:translate).with(:"active_record.human_attribute_names.topic.title", 'en-US').returns('Title')
+ @topic.errors.full_messages :locale => 'en-US'
+ end
+
+
+ # ActiveRecord::Validations
+
+ # validates_confirmation_of
+
+ def test_validates_confirmation_of_generates_message
+ Topic.validates_confirmation_of :title
+ @topic.title_confirmation = 'foo'
+ @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_confirmation_of_generates_message_with_custom_default_message
+ Topic.validates_confirmation_of :title, :message => 'custom'
+ @topic.title_confirmation = 'foo'
+ @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_confirmation_of_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:confirmation => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
+
+ Topic.validates_confirmation_of :title
+ @topic.title_confirmation = 'foo'
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_confirmation_of_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
+
+ Topic.validates_confirmation_of :title
+ @topic.title_confirmation = 'foo'
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_acceptance_of
+
+ def test_validates_acceptance_of_generates_message
+ Topic.validates_acceptance_of :title, :allow_nil => false
+ @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_acceptance_of_generates_message_with_custom_default_message
+ Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false
+ @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_acceptance_of_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:accepted => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
+
+ Topic.validates_acceptance_of :title, :allow_nil => false
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_acceptance_of_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
+
+ Topic.validates_acceptance_of :title, :allow_nil => false
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_presence_of
+
+ def test_validates_presence_of_generates_message
+ Topic.validates_presence_of :title
+ @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_presence_of_generates_message_with_custom_default_message
+ Topic.validates_presence_of :title, :message => 'custom'
+ @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_presence_of_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:blank => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
+
+ Topic.validates_presence_of :title
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_presence_of_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
+
+ Topic.validates_presence_of :title
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_length_of :within
+
+ def test_validates_length_of_within_generates_message
+ Topic.validates_length_of :title, :within => 3..5
+ @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_custom_default_message
+ Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom'
+ @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_within_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:too_short => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
+
+ Topic.validates_length_of :title, :within => 3..5
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_length_of_within_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
+
+ Topic.validates_length_of :title, :within => 3..5
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_length_of :is
+
+ def test_validates_length_of_is_generates_message
+ Topic.validates_length_of :title, :is => 5
+ @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_is_generates_message_with_custom_default_message
+ Topic.validates_length_of :title, :is => 5, :message => 'custom'
+ @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_within_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
+
+ Topic.validates_length_of :title, :is => 5
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_length_of_within_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
+
+ Topic.validates_length_of :title, :is => 5
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_uniqueness_of
+
+ def test_validates_uniqueness_of_generates_message
+ Topic.validates_uniqueness_of :title
+ @topic.title = unique_topic.title
+ @topic.errors.expects(:generate_message).with(:title, :taken, {:default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_uniqueness_of_generates_message_with_custom_default_message
+ Topic.validates_uniqueness_of :title, :message => 'custom'
+ @topic.title = unique_topic.title
+ @topic.errors.expects(:generate_message).with(:title, :taken, {:default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_within_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
+
+ Topic.validates_length_of :title, :is => 5
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_length_of_within_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
+
+ Topic.validates_length_of :title, :is => 5
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_format_of
+
+ def test_validates_format_of_generates_message
+ Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
+ @topic.title = '72x'
+ @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_format_of_generates_message_with_custom_default_message
+ Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom'
+ @topic.title = '72x'
+ @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_format_of_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:invalid => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
+
+ Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_format_of_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
+
+ Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_inclusion_of
+
+ def test_validates_inclusion_of_generates_message
+ Topic.validates_inclusion_of :title, :in => %w(a b c)
+ @topic.title = 'z'
+ @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_inclusion_of_generates_message_with_custom_default_message
+ Topic.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom'
+ @topic.title = 'z'
+ @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_inclusion_of_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:inclusion => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
+
+ Topic.validates_inclusion_of :title, :in => %w(a b c)
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_inclusion_of_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
+
+ Topic.validates_inclusion_of :title, :in => %w(a b c)
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_exclusion_of
+
+ def test_validates_exclusion_of_generates_message
+ Topic.validates_exclusion_of :title, :in => %w(a b c)
+ @topic.title = 'a'
+ @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_exclusion_of_generates_message_with_custom_default_message
+ Topic.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom'
+ @topic.title = 'a'
+ @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_exclusion_of_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:exclusion => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
+
+ Topic.validates_exclusion_of :title, :in => %w(a b c)
+ @topic.title = 'a'
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_exclusion_of_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
+
+ Topic.validates_exclusion_of :title, :in => %w(a b c)
+ @topic.title = 'a'
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_numericality_of :only_integer
+
+ def test_validates_numericality_of_only_integer_generates_message
+ Topic.validates_numericality_of :title, :only_integer => true
+ @topic.title = 'a'
+ @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message
+ Topic.validates_numericality_of :title, :only_integer => true, :message => 'custom'
+ @topic.title = 'a'
+ @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:not_a_number => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
+
+ Topic.validates_numericality_of :title, :only_integer => true
+ @topic.title = 'a'
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_numericality_of_only_integer_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
+
+ Topic.validates_numericality_of :title, :only_integer => true
+ @topic.title = 'a'
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_numericality_of :odd
+
+ def test_validates_numericality_of_odd_generates_message
+ Topic.validates_numericality_of :title, :only_integer => true, :odd => true
+ @topic.title = 0
+ @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_numericality_of_odd_generates_message_with_custom_default_message
+ Topic.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom'
+ @topic.title = 0
+ @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_numericality_of_odd_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:odd => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
+
+ Topic.validates_numericality_of :title, :only_integer => true, :odd => true
+ @topic.title = 0
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_numericality_of_odd_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
+
+ Topic.validates_numericality_of :title, :only_integer => true, :odd => true
+ @topic.title = 0
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_numericality_of :less_than
+
+ def test_validates_numericality_of_less_than_generates_message
+ Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
+ @topic.title = 1
+ @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_numericality_of_odd_generates_message_with_custom_default_message
+ Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom'
+ @topic.title = 1
+ @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_numericality_of_less_than_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:less_than => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
+
+ Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
+ @topic.title = 1
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_numericality_of_less_than_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
+
+ Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
+ @topic.title = 1
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_associated
+
+ def test_validates_associated_generates_message
+ Topic.validates_associated :replies
+ replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
+ replied_topic.valid?
+ end
+
+ def test_validates_associated_generates_message_with_custom_default_message
+ Topic.validates_associated :replies
+ replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
+ replied_topic.valid?
+ end
+
+ def test_validates_associated_finds_custom_model_key_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:replies => {:invalid => 'custom message'}}}}}
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
+
+ Topic.validates_associated :replies
+ replied_topic.valid?
+ assert_equal 'custom message', replied_topic.errors.on(:replies)
+ end
+
+ def test_validates_associated_finds_global_default_translation
+ I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
+
+ Topic.validates_associated :replies
+ replied_topic.valid?
+ assert_equal 'global message', replied_topic.errors.on(:replies)
+ end
+end
\ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index a9882828ca..08e608d346 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -6,10 +6,15 @@ module ActiveSupport #:nodoc:
module Conversions
# Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
# * :connector - The word used to join the last element in arrays with two or more elements (default: "and")
- # * :skip_last_comma - Set to true to return "a, b and c" instead of "a, b, and c".
- def to_sentence(options = {})
- options.assert_valid_keys(:connector, :skip_last_comma)
- options.reverse_merge! :connector => 'and', :skip_last_comma => false
+ # * :skip_last_comma - Set to true to return "a, b and c" instead of "a, b, and c".
+ def to_sentence(options = {})
+ options.assert_valid_keys(:connector, :skip_last_comma, :locale)
+
+ locale = options[:locale]
+ locale ||= request.locale if respond_to?(:request)
+
+ default = :'support.array.sentence_connector'.t(locale)
+ options.reverse_merge! :connector => default, :skip_last_comma => false
options[:connector] = "#{options[:connector]} " unless options[:connector].nil? || options[:connector].strip == ''
case length
@@ -23,6 +28,7 @@ module ActiveSupport #:nodoc:
"#{self[0...-1].join(', ')}#{options[:skip_last_comma] ? '' : ','} #{options[:connector]}#{self[-1]}"
end
end
+
# Calls to_param on all its elements and joins the result with
# slashes. This is used by url_for in Action Pack.
diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb
index a02e42f791..bca2664fa4 100644
--- a/activesupport/lib/active_support/vendor.rb
+++ b/activesupport/lib/active_support/vendor.rb
@@ -23,4 +23,11 @@ begin
gem 'tzinfo', '~> 0.3.9'
rescue Gem::LoadError
$:.unshift "#{File.dirname(__FILE__)}/vendor/tzinfo-0.3.9"
+end
+
+begin
+ gem 'i18n', '~> 0.3.9'
+rescue Gem::LoadError
+ $:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.0.1/lib" # TODO
+ require 'i18n'
end
\ No newline at end of file
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1 b/activesupport/lib/active_support/vendor/i18n-0.0.1
new file mode 160000
index 0000000000..70ab0f3cc5
--- /dev/null
+++ b/activesupport/lib/active_support/vendor/i18n-0.0.1
@@ -0,0 +1 @@
+Subproject commit 70ab0f3cc5921cc67e09741939a08b2582d707cb
--
cgit v1.2.3
From 4a8486a1b1aa28d4cab5571b55301917221870e9 Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Thu, 19 Jun 2008 16:43:30 +0200
Subject: add lang file for active_support
---
activesupport/lib/active_support.rb | 1 +
activesupport/lib/active_support/lang/en-US.rb | 7 +++++++
2 files changed, 8 insertions(+)
create mode 100644 activesupport/lib/active_support/lang/en-US.rb
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 1a8603e892..acdb3056d2 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -55,6 +55,7 @@ require 'active_support/multibyte'
require 'active_support/base64'
require 'active_support/time_with_zone'
+require 'active_support/lang/en-US.rb'
Inflector = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Inflector', 'ActiveSupport::Inflector')
Dependencies = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Dependencies', 'ActiveSupport::Dependencies')
diff --git a/activesupport/lib/active_support/lang/en-US.rb b/activesupport/lib/active_support/lang/en-US.rb
new file mode 100644
index 0000000000..8732927f48
--- /dev/null
+++ b/activesupport/lib/active_support/lang/en-US.rb
@@ -0,0 +1,7 @@
+I18n.backend.add_translations :'en-US', {
+ :support => {
+ :array => {
+ :sentence_connector => 'and'
+ }
+ }
+}
\ No newline at end of file
--
cgit v1.2.3
From fdb5f810dc41f54f8cdb9c51154ff8987362c13a Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Thu, 19 Jun 2008 16:55:47 +0200
Subject: I18n has not been released as a gem, yet
---
activesupport/lib/active_support/vendor.rb | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb
index bca2664fa4..381471b833 100644
--- a/activesupport/lib/active_support/vendor.rb
+++ b/activesupport/lib/active_support/vendor.rb
@@ -25,9 +25,10 @@ rescue Gem::LoadError
$:.unshift "#{File.dirname(__FILE__)}/vendor/tzinfo-0.3.9"
end
-begin
- gem 'i18n', '~> 0.3.9'
-rescue Gem::LoadError
- $:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.0.1/lib" # TODO
+# TODO I18n gem has not been released yet
+# begin
+# gem 'i18n', '~> 0.0.1'
+# rescue Gem::LoadError
+ $:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.0.1/lib"
require 'i18n'
-end
\ No newline at end of file
+# end
\ No newline at end of file
--
cgit v1.2.3
From 2fe4d350e98d7f825cf3d1f9233075a5a79e32a1 Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Thu, 19 Jun 2008 18:31:11 +0200
Subject: make ActiveRecord::Errors.default_error_messages look up translated
error messages
---
activerecord/lib/active_record/validations.rb | 33 ++++++---------------------
1 file changed, 7 insertions(+), 26 deletions(-)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index f54fb80137..0ca68989d2 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -18,37 +18,18 @@ module ActiveRecord
# determine whether the object is in a valid state to be saved. See usage example in Validations.
class Errors
include Enumerable
+
+ class << self
+ def default_error_messages
+ # TODO deprecate this?
+ :'active_record.error_messages'.t
+ end
+ end
def initialize(base) # :nodoc:
@base, @errors = base, {}
end
- # @@default_error_messages = {
- # :inclusion => "is not included in the list",
- # :exclusion => "is reserved",
- # :invalid => "is invalid",
- # :confirmation => "doesn't match confirmation",
- # :accepted => "must be accepted",
- # :empty => "can't be empty",
- # :blank => "can't be blank",
- # :too_long => "is too long (maximum is %d characters)",
- # :too_short => "is too short (minimum is %d characters)",
- # :wrong_length => "is the wrong length (should be %d characters)",
- # :taken => "has already been taken",
- # :not_a_number => "is not a number",
- # :greater_than => "must be greater than %d",
- # :greater_than_or_equal_to => "must be greater than or equal to %d",
- # :equal_to => "must be equal to %d",
- # :less_than => "must be less than %d",
- # :less_than_or_equal_to => "must be less than or equal to %d",
- # :odd => "must be odd",
- # :even => "must be even"
- # }
- #
- # # Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
- # cattr_accessor :default_error_messages
-
-
# Adds an error to the base object instead of any particular attribute. This is used
# to report errors that don't tie to any specific attribute, but rather to the object
# as a whole. These error messages don't get prepended with any field name when iterating
--
cgit v1.2.3
From b09c6e7444cf05f986e601bcc22cf17ede7b63bd Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Thu, 19 Jun 2008 19:08:14 +0200
Subject: add a generic tranlate view helper
---
actionpack/lib/action_view/helpers/i18n_helper.rb | 19 +++++++++++++
actionpack/test/template/i18n_helper_test.rb | 33 +++++++++++++++++++++++
2 files changed, 52 insertions(+)
create mode 100644 actionpack/lib/action_view/helpers/i18n_helper.rb
create mode 100644 actionpack/test/template/i18n_helper_test.rb
diff --git a/actionpack/lib/action_view/helpers/i18n_helper.rb b/actionpack/lib/action_view/helpers/i18n_helper.rb
new file mode 100644
index 0000000000..1b1d1f301d
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/i18n_helper.rb
@@ -0,0 +1,19 @@
+module ActionView
+ module Helpers
+ module I18nHelper
+ def translate(*args)
+ # inserts the locale or current request locale to the argument list if no locale
+ # has been passed or the locale has been passed as part of the options hash
+ options = args.extract_options!
+ if args.size != 2
+ locale = options.delete :locale
+ locale ||= request.locale if respond_to? :request
+ args << locale if locale
+ end
+ args << options unless options.empty?
+ I18n.translate *args
+ end
+ alias :t :translate
+ end
+ end
+end
\ No newline at end of file
diff --git a/actionpack/test/template/i18n_helper_test.rb b/actionpack/test/template/i18n_helper_test.rb
new file mode 100644
index 0000000000..598731568c
--- /dev/null
+++ b/actionpack/test/template/i18n_helper_test.rb
@@ -0,0 +1,33 @@
+require 'abstract_unit'
+require 'action_view/helpers/i18n_helper'
+
+class I18nHelperTests < Test::Unit::TestCase
+ include ActionView::Helpers::I18nHelper
+
+ attr_reader :request
+ def setup
+ @request = stub :locale => 'en-US'
+ I18n.stubs(:translate).with(:'foo.bar', 'en-US').returns 'Foo Bar'
+ end
+
+ def test_translate_given_a_locale_argument_it_does_not_check_request_for_locale
+ request.expects(:locale).never
+ assert_equal 'Foo Bar', translate(:'foo.bar', :locale => 'en-US')
+ end
+
+ def test_translate_given_a_locale_option_it_does_not_check_request_for_locale
+ request.expects(:locale).never
+ I18n.expects(:translate).with(:'foo.bar', 'en-US').returns 'Foo Bar'
+ assert_equal 'Foo Bar', translate(:'foo.bar', :locale => 'en-US')
+ end
+
+ def test_translate_given_no_locale_it_checks_request_for_locale
+ request.expects(:locale).returns 'en-US'
+ assert_equal 'Foo Bar', translate(:'foo.bar')
+ end
+
+ def test_translate_delegates_to_i18n_translate
+ I18n.expects(:translate).with(:'foo.bar', 'en-US').returns 'Foo Bar'
+ assert_equal 'Foo Bar', translate(:'foo.bar')
+ end
+end
\ No newline at end of file
--
cgit v1.2.3
From 585c8c17c303fc46fcf014a644a541eae6cb5ffd Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Fri, 20 Jun 2008 09:13:20 +0200
Subject: rename Backend::Simple#add_translations to set_translations because
it overwrites existing translations
---
.../lib/action_view/helpers/form_options_helper.rb | 2 +-
actionpack/lib/action_view/lang/en-US.rb | 2 +-
.../test/template/number_helper_i18n_test.rb | 2 +-
activerecord/lib/active_record/lang/en-US.rb | 2 +-
activerecord/lib/active_record/validations.rb | 2 +-
activerecord/test/cases/validations_i18n_test.rb | 80 +++++++++++-----------
activesupport/lib/active_support/lang/en-US.rb | 2 +-
activesupport/lib/active_support/vendor/i18n-0.0.1 | 2 +-
8 files changed, 47 insertions(+), 47 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index c782c0a816..a6b9e65a77 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -349,7 +349,7 @@ module ActionView
end
# All the countries included in the country_options output.
- # only included for backwards compatibility, please use the I18n interface
+ # deprecated. please use :'countries.names'.t directly
COUNTRIES = :'countries.names'.t 'en-US' unless const_defined?("COUNTRIES")
end
diff --git a/actionpack/lib/action_view/lang/en-US.rb b/actionpack/lib/action_view/lang/en-US.rb
index 659f96a5f3..70eb1b79de 100644
--- a/actionpack/lib/action_view/lang/en-US.rb
+++ b/actionpack/lib/action_view/lang/en-US.rb
@@ -1,4 +1,4 @@
-I18n.backend.add_translations :'en-US', {
+I18n.backend.set_translations :'en-US', {
:date => {
:formats => {
:default => "%Y-%m-%d",
diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb
index 47cb035f56..d002ad4a2f 100644
--- a/actionpack/test/template/number_helper_i18n_test.rb
+++ b/actionpack/test/template/number_helper_i18n_test.rb
@@ -7,7 +7,7 @@ class NumberHelperI18nTests < Test::Unit::TestCase
def setup
@request = mock
@defaults = {:separator => ".", :unit => "$", :format => "%u%n", :delimiter => ",", :precision => 2}
- I18n.backend.add_translations 'en-US', :currency => {:format => @defaults}
+ I18n.backend.set_translations 'en-US', :currency => {:format => @defaults}
end
def test_number_to_currency_given_a_locale_it_does_not_check_request_for_locale
diff --git a/activerecord/lib/active_record/lang/en-US.rb b/activerecord/lib/active_record/lang/en-US.rb
index 7c3bcfd85e..f307f40f1a 100644
--- a/activerecord/lib/active_record/lang/en-US.rb
+++ b/activerecord/lib/active_record/lang/en-US.rb
@@ -1,4 +1,4 @@
-I18n.backend.add_translations :'en-US', {
+I18n.backend.set_translations :'en-US', {
:active_record => {
:error_messages => {
:inclusion => "is not included in the list",
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 0ca68989d2..bcb204f1ba 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -21,7 +21,7 @@ module ActiveRecord
class << self
def default_error_messages
- # TODO deprecate this?
+ # ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use :'active_record.error_messages'.t.")
:'active_record.error_messages'.t
end
end
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb
index eb454fca20..8f8574c242 100644
--- a/activerecord/test/cases/validations_i18n_test.rb
+++ b/activerecord/test/cases/validations_i18n_test.rb
@@ -6,7 +6,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def setup
reset_callbacks Topic
@topic = Topic.new
- I18n.backend.add_translations('en-US', :active_record => {:error_messages => {:custom => nil}})
+ I18n.backend.set_translations('en-US', :active_record => {:error_messages => {:custom => nil}})
end
def teardown
@@ -113,8 +113,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_confirmation_of_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:confirmation => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:confirmation => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@@ -123,7 +123,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_confirmation_of_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@@ -147,8 +147,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_acceptance_of_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:accepted => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:accepted => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.valid?
@@ -156,7 +156,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_acceptance_of_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.valid?
@@ -179,8 +179,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_presence_of_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:blank => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:blank => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
Topic.validates_presence_of :title
@topic.valid?
@@ -188,7 +188,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_presence_of_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
Topic.validates_presence_of :title
@topic.valid?
@@ -211,8 +211,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:too_short => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:too_short => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
Topic.validates_length_of :title, :within => 3..5
@topic.valid?
@@ -220,7 +220,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
Topic.validates_length_of :title, :within => 3..5
@topic.valid?
@@ -243,8 +243,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
@@ -252,7 +252,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
@@ -277,8 +277,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
@@ -286,7 +286,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
@@ -311,8 +311,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_format_of_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:invalid => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:invalid => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.valid?
@@ -320,7 +320,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_format_of_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.valid?
@@ -345,8 +345,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_inclusion_of_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:inclusion => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:inclusion => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.valid?
@@ -354,7 +354,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_inclusion_of_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.valid?
@@ -379,8 +379,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_exclusion_of_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:exclusion => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:exclusion => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@@ -389,7 +389,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_exclusion_of_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@@ -415,8 +415,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:not_a_number => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:not_a_number => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@@ -425,7 +425,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_only_integer_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@@ -451,8 +451,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_odd_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:odd => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:odd => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@@ -461,7 +461,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_odd_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@@ -487,8 +487,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_less_than_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:less_than => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:less_than => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@@ -497,7 +497,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_less_than_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@@ -521,8 +521,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_associated_finds_custom_model_key_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:replies => {:invalid => 'custom message'}}}}}
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:replies => {:invalid => 'custom message'}}}}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
Topic.validates_associated :replies
replied_topic.valid?
@@ -530,7 +530,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_associated_finds_global_default_translation
- I18n.backend.add_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
+ I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
Topic.validates_associated :replies
replied_topic.valid?
diff --git a/activesupport/lib/active_support/lang/en-US.rb b/activesupport/lib/active_support/lang/en-US.rb
index 8732927f48..5b8e04363e 100644
--- a/activesupport/lib/active_support/lang/en-US.rb
+++ b/activesupport/lib/active_support/lang/en-US.rb
@@ -1,4 +1,4 @@
-I18n.backend.add_translations :'en-US', {
+I18n.backend.set_translations :'en-US', {
:support => {
:array => {
:sentence_connector => 'and'
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1 b/activesupport/lib/active_support/vendor/i18n-0.0.1
index 70ab0f3cc5..1af3435539 160000
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1
+++ b/activesupport/lib/active_support/vendor/i18n-0.0.1
@@ -1 +1 @@
-Subproject commit 70ab0f3cc5921cc67e09741939a08b2582d707cb
+Subproject commit 1af3435539b4a0729c13d21c5df037a635fe98c1
--
cgit v1.2.3
From c1e2506494107892a0962b8491cd234f77949c08 Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Sat, 21 Jun 2008 11:27:19 +0200
Subject: Changed process of storing translations from the client libraries to
the backend: clients now can pass a block to backend#populate which can
contain code to load and register translations.
This makes sense for backends that persist their translations (e.g. to db)
so the repeated loading and passing of translations throughout the server
startup would be wasted resources.
---
actionpack/lib/action_view.rb | 4 +-
actionpack/lib/action_view/lang/en-US.rb | 2 +-
.../test/template/number_helper_i18n_test.rb | 2 +-
activerecord/lib/active_record.rb | 5 +-
activerecord/lib/active_record/lang/en-US.rb | 2 +-
activerecord/test/cases/validations_i18n_test.rb | 80 +++++++++++-----------
activesupport/lib/active_support.rb | 5 +-
activesupport/lib/active_support/lang/en-US.rb | 2 +-
activesupport/lib/active_support/vendor/i18n-0.0.1 | 2 +-
9 files changed, 56 insertions(+), 48 deletions(-)
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 33d50a61c4..dff487377f 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -32,7 +32,9 @@ require 'action_view/base'
require 'action_view/partials'
require 'action_view/template_error'
-require 'action_view/lang/en-US.rb'
+I18n.backend.populate do
+ require 'action_view/lang/en-US.rb'
+end
ActionView::Base.class_eval do
include ActionView::Partials
diff --git a/actionpack/lib/action_view/lang/en-US.rb b/actionpack/lib/action_view/lang/en-US.rb
index 70eb1b79de..6b5345ed90 100644
--- a/actionpack/lib/action_view/lang/en-US.rb
+++ b/actionpack/lib/action_view/lang/en-US.rb
@@ -1,4 +1,4 @@
-I18n.backend.set_translations :'en-US', {
+I18n.backend.store_translations :'en-US', {
:date => {
:formats => {
:default => "%Y-%m-%d",
diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb
index d002ad4a2f..b75af03378 100644
--- a/actionpack/test/template/number_helper_i18n_test.rb
+++ b/actionpack/test/template/number_helper_i18n_test.rb
@@ -7,7 +7,7 @@ class NumberHelperI18nTests < Test::Unit::TestCase
def setup
@request = mock
@defaults = {:separator => ".", :unit => "$", :format => "%u%n", :delimiter => ",", :precision => 2}
- I18n.backend.set_translations 'en-US', :currency => {:format => @defaults}
+ I18n.backend.store_translations 'en-US', :currency => {:format => @defaults}
end
def test_number_to_currency_given_a_locale_it_does_not_check_request_for_locale
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index b379bd26f8..71882833d4 100755
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -81,4 +81,7 @@ require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/schema_dumper'
-require 'active_record/lang/en-US.rb'
+I18n.backend.populate do
+ require 'active_record/lang/en-US.rb'
+end
+
diff --git a/activerecord/lib/active_record/lang/en-US.rb b/activerecord/lib/active_record/lang/en-US.rb
index f307f40f1a..b31e13ed3a 100644
--- a/activerecord/lib/active_record/lang/en-US.rb
+++ b/activerecord/lib/active_record/lang/en-US.rb
@@ -1,4 +1,4 @@
-I18n.backend.set_translations :'en-US', {
+I18n.backend.store_translations :'en-US', {
:active_record => {
:error_messages => {
:inclusion => "is not included in the list",
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb
index 8f8574c242..de844bf5a6 100644
--- a/activerecord/test/cases/validations_i18n_test.rb
+++ b/activerecord/test/cases/validations_i18n_test.rb
@@ -6,7 +6,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def setup
reset_callbacks Topic
@topic = Topic.new
- I18n.backend.set_translations('en-US', :active_record => {:error_messages => {:custom => nil}})
+ I18n.backend.store_translations('en-US', :active_record => {:error_messages => {:custom => nil}})
end
def teardown
@@ -113,8 +113,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_confirmation_of_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:confirmation => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:confirmation => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@@ -123,7 +123,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_confirmation_of_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@@ -147,8 +147,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_acceptance_of_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:accepted => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:accepted => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.valid?
@@ -156,7 +156,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_acceptance_of_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.valid?
@@ -179,8 +179,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_presence_of_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:blank => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:blank => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
Topic.validates_presence_of :title
@topic.valid?
@@ -188,7 +188,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_presence_of_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
Topic.validates_presence_of :title
@topic.valid?
@@ -211,8 +211,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:too_short => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:too_short => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
Topic.validates_length_of :title, :within => 3..5
@topic.valid?
@@ -220,7 +220,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
Topic.validates_length_of :title, :within => 3..5
@topic.valid?
@@ -243,8 +243,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
@@ -252,7 +252,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
@@ -277,8 +277,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
@@ -286,7 +286,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_length_of_within_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
Topic.validates_length_of :title, :is => 5
@topic.valid?
@@ -311,8 +311,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_format_of_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:invalid => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:invalid => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.valid?
@@ -320,7 +320,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_format_of_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.valid?
@@ -345,8 +345,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_inclusion_of_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:inclusion => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:inclusion => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.valid?
@@ -354,7 +354,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_inclusion_of_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.valid?
@@ -379,8 +379,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_exclusion_of_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:exclusion => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:exclusion => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@@ -389,7 +389,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_exclusion_of_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@@ -415,8 +415,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:not_a_number => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:not_a_number => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@@ -425,7 +425,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_only_integer_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@@ -451,8 +451,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_odd_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:odd => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:odd => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@@ -461,7 +461,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_odd_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@@ -487,8 +487,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_less_than_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:less_than => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:less_than => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@@ -497,7 +497,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_numericality_of_less_than_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@@ -521,8 +521,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_associated_finds_custom_model_key_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:replies => {:invalid => 'custom message'}}}}}
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:replies => {:invalid => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
Topic.validates_associated :replies
replied_topic.valid?
@@ -530,7 +530,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
def test_validates_associated_finds_global_default_translation
- I18n.backend.set_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
Topic.validates_associated :replies
replied_topic.valid?
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index acdb3056d2..0de948dda9 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -55,7 +55,10 @@ require 'active_support/multibyte'
require 'active_support/base64'
require 'active_support/time_with_zone'
-require 'active_support/lang/en-US.rb'
+
+I18n.backend.populate do
+ require 'active_support/lang/en-US.rb'
+end
Inflector = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Inflector', 'ActiveSupport::Inflector')
Dependencies = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Dependencies', 'ActiveSupport::Dependencies')
diff --git a/activesupport/lib/active_support/lang/en-US.rb b/activesupport/lib/active_support/lang/en-US.rb
index 5b8e04363e..aa06fe14bd 100644
--- a/activesupport/lib/active_support/lang/en-US.rb
+++ b/activesupport/lib/active_support/lang/en-US.rb
@@ -1,4 +1,4 @@
-I18n.backend.set_translations :'en-US', {
+I18n.backend.store_translations :'en-US', {
:support => {
:array => {
:sentence_connector => 'and'
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1 b/activesupport/lib/active_support/vendor/i18n-0.0.1
index 1af3435539..8e43afa38a 160000
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1
+++ b/activesupport/lib/active_support/vendor/i18n-0.0.1
@@ -1 +1 @@
-Subproject commit 1af3435539b4a0729c13d21c5df037a635fe98c1
+Subproject commit 8e43afa38aa007d1de6d6acf44d43143c403d13f
--
cgit v1.2.3
From 428aa24d24032d382dc3d9ccf131e0c874043dbd Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Sat, 21 Jun 2008 11:35:02 +0200
Subject: Renamed lang/ to locale/ because that's what we seem to standarize
on. Also, in future this place can be used for data/code that's not literally
translations but conceptually belongs to the locale (like custom
pluralization algorithms etc.).
---
actionpack/lib/action_view.rb | 2 +-
actionpack/lib/action_view/lang/en-US.rb | 93 ------------------------
actionpack/lib/action_view/locale/en-US.rb | 93 ++++++++++++++++++++++++
activerecord/lib/active_record.rb | 2 +-
activerecord/lib/active_record/lang/en-US.rb | 25 -------
activerecord/lib/active_record/locale/en-US.rb | 25 +++++++
activerecord/test/cases/validations_i18n_test.rb | 2 +-
activesupport/lib/active_support.rb | 2 +-
activesupport/lib/active_support/lang/en-US.rb | 7 --
activesupport/lib/active_support/locale/en-US.rb | 7 ++
10 files changed, 129 insertions(+), 129 deletions(-)
delete mode 100644 actionpack/lib/action_view/lang/en-US.rb
create mode 100644 actionpack/lib/action_view/locale/en-US.rb
delete mode 100644 activerecord/lib/active_record/lang/en-US.rb
create mode 100644 activerecord/lib/active_record/locale/en-US.rb
delete mode 100644 activesupport/lib/active_support/lang/en-US.rb
create mode 100644 activesupport/lib/active_support/locale/en-US.rb
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index dff487377f..067a871f79 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -33,7 +33,7 @@ require 'action_view/partials'
require 'action_view/template_error'
I18n.backend.populate do
- require 'action_view/lang/en-US.rb'
+ require 'action_view/locale/en-US.rb'
end
ActionView::Base.class_eval do
diff --git a/actionpack/lib/action_view/lang/en-US.rb b/actionpack/lib/action_view/lang/en-US.rb
deleted file mode 100644
index 6b5345ed90..0000000000
--- a/actionpack/lib/action_view/lang/en-US.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-I18n.backend.store_translations :'en-US', {
- :date => {
- :formats => {
- :default => "%Y-%m-%d",
- :short => "%b %d",
- :long => "%B %d, %Y",
- },
- :day_names => Date::DAYNAMES,
- :abbr_day_names => Date::ABBR_DAYNAMES,
- :month_names => Date::MONTHNAMES,
- :abbr_month_names => Date::ABBR_MONTHNAMES,
- :order => [:year, :month, :day]
- },
- :time => {
- :formats => {
- :default => "%a, %d %b %Y %H:%M:%S %z",
- :short => "%d %b %H:%M",
- :long => "%B %d, %Y %H:%M",
- },
- :am => 'am',
- :pm => 'pm'
- },
- :datetime => {
- :distance_in_words => {
- :half_a_minute => 'half a minute',
- :less_than_x_seconds => ['less than 1 second', 'less than {{count}} seconds'],
- :x_seconds => ['1 second', '{{count}} seconds'],
- :less_than_x_minutes => ['less than a minute', 'less than {{count}} minutes'],
- :x_minutes => ['1 minute', '{{count}} minutes'],
- :about_x_hours => ['about 1 hour', 'about {{count}} hours'],
- :x_days => ['1 day', '{{count}} days'],
- :about_x_months => ['about 1 month', 'about {{count}} months'],
- :x_months => ['1 month', '{{count}} months'],
- :about_x_years => ['about 1 year', 'about {{count}} year'],
- :over_x_years => ['over 1 year', 'over {{count}} years']
- }
- },
- :currency => {
- :format => {
- :unit => '$',
- :precision => 2,
- :separator => '.',
- :delimiter => ',',
- :format => '%u%n',
- }
- },
- :countries => {
- :names => ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
- "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
- "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
- "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
- "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
- "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
- "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
- "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
- "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
- "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
- "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
- "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece",
- "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
- "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
- "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
- "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
- "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
- "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
- "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
- "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
- "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
- "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
- "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
- "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
- "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
- "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
- "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
- "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
- "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
- "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
- "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
- "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
- "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
- "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
- "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
- "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
- "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
- "Yemen", "Zambia", "Zimbabwe"]
- },
- :active_record => {
- :error => {
- :header_message => ["1 error prohibited this {{object_name}} from being saved", "{{count}} errors prohibited this {{object_name}} from being saved"],
- :message => "There were problems with the following fields:"
- }
- }
-}
diff --git a/actionpack/lib/action_view/locale/en-US.rb b/actionpack/lib/action_view/locale/en-US.rb
new file mode 100644
index 0000000000..6b5345ed90
--- /dev/null
+++ b/actionpack/lib/action_view/locale/en-US.rb
@@ -0,0 +1,93 @@
+I18n.backend.store_translations :'en-US', {
+ :date => {
+ :formats => {
+ :default => "%Y-%m-%d",
+ :short => "%b %d",
+ :long => "%B %d, %Y",
+ },
+ :day_names => Date::DAYNAMES,
+ :abbr_day_names => Date::ABBR_DAYNAMES,
+ :month_names => Date::MONTHNAMES,
+ :abbr_month_names => Date::ABBR_MONTHNAMES,
+ :order => [:year, :month, :day]
+ },
+ :time => {
+ :formats => {
+ :default => "%a, %d %b %Y %H:%M:%S %z",
+ :short => "%d %b %H:%M",
+ :long => "%B %d, %Y %H:%M",
+ },
+ :am => 'am',
+ :pm => 'pm'
+ },
+ :datetime => {
+ :distance_in_words => {
+ :half_a_minute => 'half a minute',
+ :less_than_x_seconds => ['less than 1 second', 'less than {{count}} seconds'],
+ :x_seconds => ['1 second', '{{count}} seconds'],
+ :less_than_x_minutes => ['less than a minute', 'less than {{count}} minutes'],
+ :x_minutes => ['1 minute', '{{count}} minutes'],
+ :about_x_hours => ['about 1 hour', 'about {{count}} hours'],
+ :x_days => ['1 day', '{{count}} days'],
+ :about_x_months => ['about 1 month', 'about {{count}} months'],
+ :x_months => ['1 month', '{{count}} months'],
+ :about_x_years => ['about 1 year', 'about {{count}} year'],
+ :over_x_years => ['over 1 year', 'over {{count}} years']
+ }
+ },
+ :currency => {
+ :format => {
+ :unit => '$',
+ :precision => 2,
+ :separator => '.',
+ :delimiter => ',',
+ :format => '%u%n',
+ }
+ },
+ :countries => {
+ :names => ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
+ "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
+ "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
+ "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
+ "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
+ "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
+ "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
+ "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
+ "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
+ "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
+ "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
+ "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece",
+ "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
+ "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
+ "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
+ "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
+ "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
+ "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
+ "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
+ "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
+ "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
+ "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
+ "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
+ "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
+ "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
+ "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
+ "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
+ "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
+ "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
+ "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
+ "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
+ "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
+ "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
+ "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
+ "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
+ "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
+ "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
+ "Yemen", "Zambia", "Zimbabwe"]
+ },
+ :active_record => {
+ :error => {
+ :header_message => ["1 error prohibited this {{object_name}} from being saved", "{{count}} errors prohibited this {{object_name}} from being saved"],
+ :message => "There were problems with the following fields:"
+ }
+ }
+}
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 71882833d4..17a7949959 100755
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -82,6 +82,6 @@ require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/schema_dumper'
I18n.backend.populate do
- require 'active_record/lang/en-US.rb'
+ require 'active_record/locale/en-US.rb'
end
diff --git a/activerecord/lib/active_record/lang/en-US.rb b/activerecord/lib/active_record/lang/en-US.rb
deleted file mode 100644
index b31e13ed3a..0000000000
--- a/activerecord/lib/active_record/lang/en-US.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-I18n.backend.store_translations :'en-US', {
- :active_record => {
- :error_messages => {
- :inclusion => "is not included in the list",
- :exclusion => "is reserved",
- :invalid => "is invalid",
- :confirmation => "doesn't match confirmation",
- :accepted => "must be accepted",
- :empty => "can't be empty",
- :blank => "can't be blank",
- :too_long => "is too long (maximum is {{count}} characters)",
- :too_short => "is too short (minimum is {{count}} characters)",
- :wrong_length => "is the wrong length (should be {{count}} characters)",
- :taken => "has already been taken",
- :not_a_number => "is not a number",
- :greater_than => "must be greater than {{count}}",
- :greater_than_or_equal_to => "must be greater than or equal to {{count}}",
- :equal_to => "must be equal to {{count}}",
- :less_than => "must be less than {{count}}",
- :less_than_or_equal_to => "must be less than or equal to {{count}}",
- :odd => "must be odd",
- :even => "must be even"
- }
- }
-}
\ No newline at end of file
diff --git a/activerecord/lib/active_record/locale/en-US.rb b/activerecord/lib/active_record/locale/en-US.rb
new file mode 100644
index 0000000000..b31e13ed3a
--- /dev/null
+++ b/activerecord/lib/active_record/locale/en-US.rb
@@ -0,0 +1,25 @@
+I18n.backend.store_translations :'en-US', {
+ :active_record => {
+ :error_messages => {
+ :inclusion => "is not included in the list",
+ :exclusion => "is reserved",
+ :invalid => "is invalid",
+ :confirmation => "doesn't match confirmation",
+ :accepted => "must be accepted",
+ :empty => "can't be empty",
+ :blank => "can't be blank",
+ :too_long => "is too long (maximum is {{count}} characters)",
+ :too_short => "is too short (minimum is {{count}} characters)",
+ :wrong_length => "is the wrong length (should be {{count}} characters)",
+ :taken => "has already been taken",
+ :not_a_number => "is not a number",
+ :greater_than => "must be greater than {{count}}",
+ :greater_than_or_equal_to => "must be greater than or equal to {{count}}",
+ :equal_to => "must be equal to {{count}}",
+ :less_than => "must be less than {{count}}",
+ :less_than_or_equal_to => "must be less than or equal to {{count}}",
+ :odd => "must be odd",
+ :even => "must be even"
+ }
+ }
+}
\ No newline at end of file
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb
index de844bf5a6..10e36b1512 100644
--- a/activerecord/test/cases/validations_i18n_test.rb
+++ b/activerecord/test/cases/validations_i18n_test.rb
@@ -11,7 +11,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def teardown
reset_callbacks Topic
- load 'active_record/lang/en-US.rb'
+ load 'active_record/locale/en-US.rb'
end
def unique_topic
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 0de948dda9..de50aafe16 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -57,7 +57,7 @@ require 'active_support/base64'
require 'active_support/time_with_zone'
I18n.backend.populate do
- require 'active_support/lang/en-US.rb'
+ require 'active_support/locale/en-US.rb'
end
Inflector = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Inflector', 'ActiveSupport::Inflector')
diff --git a/activesupport/lib/active_support/lang/en-US.rb b/activesupport/lib/active_support/lang/en-US.rb
deleted file mode 100644
index aa06fe14bd..0000000000
--- a/activesupport/lib/active_support/lang/en-US.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-I18n.backend.store_translations :'en-US', {
- :support => {
- :array => {
- :sentence_connector => 'and'
- }
- }
-}
\ No newline at end of file
diff --git a/activesupport/lib/active_support/locale/en-US.rb b/activesupport/lib/active_support/locale/en-US.rb
new file mode 100644
index 0000000000..aa06fe14bd
--- /dev/null
+++ b/activesupport/lib/active_support/locale/en-US.rb
@@ -0,0 +1,7 @@
+I18n.backend.store_translations :'en-US', {
+ :support => {
+ :array => {
+ :sentence_connector => 'and'
+ }
+ }
+}
\ No newline at end of file
--
cgit v1.2.3
From 8bfdabbd8b5137f91d8bcddc8c3d18961c8e316b Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Sat, 21 Jun 2008 17:50:37 +0200
Subject: incorporate #translate usage with several default keys (use first
default key that resolves to a translation). this might, depending on the
backend implementation save some expensive lookups (like db lookups)
---
activerecord/lib/active_record/validations.rb | 19 ++++-
activerecord/test/cases/validations_i18n_test.rb | 86 +++++++++-------------
activesupport/lib/active_support/vendor/i18n-0.0.1 | 2 +-
3 files changed, 52 insertions(+), 55 deletions(-)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index bcb204f1ba..49d3c59ca7 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -66,9 +66,11 @@ module ActiveRecord
end
def generate_message(attr, key, options = {})
- scope = [:active_record, :error_messages]
- key.t(options.merge(:scope => scope + [:custom, @base.class.name.downcase, attr])) ||
- key.t(options.merge(:scope => scope))
+ msgs = base_classes(@base.class).map{|klass| :"custom.#{klass.name.underscore}.#{attr}.#{key}"}
+ msgs << options[:default] if options[:default]
+ msgs << key
+
+ I18n.t options.merge(:default => msgs, :scope => [:active_record, :error_messages])
end
# Returns true if the specified +attribute+ has errors associated with it.
@@ -217,6 +219,17 @@ module ActiveRecord
full_messages.each { |msg| e.error(msg) }
end
end
+
+ protected
+
+ # TODO maybe this should be on ActiveRecord::Base, maybe #self_and_descendents_from_active_record
+ def base_classes(klass)
+ classes = [klass]
+ while klass != klass.base_class
+ classes << klass = klass.superclass
+ end
+ classes
+ end
end
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb
index 10e36b1512..37a7c1ce49 100644
--- a/activerecord/test/cases/validations_i18n_test.rb
+++ b/activerecord/test/cases/validations_i18n_test.rb
@@ -40,31 +40,15 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
global_scope = [:active_record, :error_messages]
custom_scope = global_scope + [:custom, 'topic', :title]
- I18n.expects(:translate).with(:invalid, :scope => custom_scope).returns 'translation'
- I18n.expects(:translate).with(:invalid, :scope => global_scope).never
-
- @topic.errors.generate_message :title, :invalid
+ I18n.expects(:t).with :scope => [:active_record, :error_messages], :default => [:"custom.topic.title.invalid", 'default from class def', :invalid]
+ @topic.errors.generate_message :title, :invalid, :default => 'default from class def'
end
- def test_errors_generate_message_given_a_custom_message_translates_custom_model_attribute_key_with_custom_message_as_default
+ def test_errors_generate_message_translates_custom_model_attribute_keys_with_sti
custom_scope = [:active_record, :error_messages, :custom, 'topic', :title]
- I18n.expects(:translate).with(:invalid, :scope => custom_scope, :default => 'default from class def').returns 'translation'
- @topic.errors.generate_message :title, :invalid, :default => 'default from class def'
- end
-
- def test_errors_generate_message_given_no_custom_message_falls_back_to_global_default_key_translation
- global_scope = [:active_record, :error_messages]
- custom_scope = global_scope + [:custom, 'topic', :title]
-
- I18n.stubs(:translate).with(:invalid, :scope => custom_scope).returns nil
- I18n.expects(:translate).with(:invalid, :scope => global_scope)
- @topic.errors.generate_message :title, :invalid
- end
-
- def test_errors_add_given_no_message_it_translates_invalid
- I18n.expects(:translate).with(:"active_record.error_messages.invalid")
- @topic.errors.add :title
+ I18n.expects(:t).with :scope => [:active_record, :error_messages], :default => [:"custom.reply.title.invalid", :"custom.topic.title.invalid", 'default from class def', :invalid]
+ Reply.new.errors.generate_message :title, :invalid, :default => 'default from class def'
end
def test_errors_add_on_empty_generates_message
@@ -115,7 +99,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_confirmation_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:confirmation => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
-
+
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@topic.valid?
@@ -124,7 +108,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_confirmation_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:confirmation => 'global message'}}
-
+
Topic.validates_confirmation_of :title
@topic.title_confirmation = 'foo'
@topic.valid?
@@ -133,13 +117,13 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_acceptance_of
-
+
def test_validates_acceptance_of_generates_message
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.errors.expects(:generate_message).with(:title, :accepted, {:default => nil})
@topic.valid?
end
-
+
def test_validates_acceptance_of_generates_message_with_custom_default_message
Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false
@topic.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'})
@@ -149,7 +133,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_acceptance_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:accepted => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
-
+
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
@@ -157,7 +141,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_acceptance_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:accepted => 'global message'}}
-
+
Topic.validates_acceptance_of :title, :allow_nil => false
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
@@ -165,7 +149,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_presence_of
-
+
def test_validates_presence_of_generates_message
Topic.validates_presence_of :title
@topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
@@ -181,7 +165,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_presence_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:blank => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
-
+
Topic.validates_presence_of :title
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
@@ -189,7 +173,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_presence_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
-
+
Topic.validates_presence_of :title
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
@@ -197,7 +181,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_length_of :within
-
+
def test_validates_length_of_within_generates_message
Topic.validates_length_of :title, :within => 3..5
@topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
@@ -213,7 +197,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:too_short => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
-
+
Topic.validates_length_of :title, :within => 3..5
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
@@ -221,7 +205,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_length_of_within_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
-
+
Topic.validates_length_of :title, :within => 3..5
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
@@ -245,7 +229,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
-
+
Topic.validates_length_of :title, :is => 5
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
@@ -253,7 +237,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_length_of_within_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
-
+
Topic.validates_length_of :title, :is => 5
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
@@ -279,7 +263,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
-
+
Topic.validates_length_of :title, :is => 5
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
@@ -287,7 +271,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_length_of_within_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:wrong_length => 'global message'}}
-
+
Topic.validates_length_of :title, :is => 5
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
@@ -313,7 +297,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_format_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:invalid => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
-
+
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
@@ -321,7 +305,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_format_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
-
+
Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
@@ -347,7 +331,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_inclusion_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:inclusion => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
-
+
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.valid?
assert_equal 'custom message', @topic.errors.on(:title)
@@ -355,7 +339,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_inclusion_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:inclusion => 'global message'}}
-
+
Topic.validates_inclusion_of :title, :in => %w(a b c)
@topic.valid?
assert_equal 'global message', @topic.errors.on(:title)
@@ -381,7 +365,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_exclusion_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:exclusion => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
-
+
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@topic.valid?
@@ -390,7 +374,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_exclusion_of_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:exclusion => 'global message'}}
-
+
Topic.validates_exclusion_of :title, :in => %w(a b c)
@topic.title = 'a'
@topic.valid?
@@ -417,7 +401,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:not_a_number => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
-
+
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@topic.valid?
@@ -426,7 +410,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_numericality_of_only_integer_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
-
+
Topic.validates_numericality_of :title, :only_integer => true
@topic.title = 'a'
@topic.valid?
@@ -453,7 +437,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_numericality_of_odd_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:odd => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
-
+
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@topic.valid?
@@ -462,7 +446,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_numericality_of_odd_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:odd => 'global message'}}
-
+
Topic.validates_numericality_of :title, :only_integer => true, :odd => true
@topic.title = 0
@topic.valid?
@@ -489,7 +473,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_numericality_of_less_than_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:less_than => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
-
+
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@topic.valid?
@@ -498,7 +482,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_numericality_of_less_than_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:less_than => 'global message'}}
-
+
Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
@topic.title = 1
@topic.valid?
@@ -523,7 +507,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_associated_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:replies => {:invalid => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
-
+
Topic.validates_associated :replies
replied_topic.valid?
assert_equal 'custom message', replied_topic.errors.on(:replies)
@@ -531,7 +515,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_validates_associated_finds_global_default_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:invalid => 'global message'}}
-
+
Topic.validates_associated :replies
replied_topic.valid?
assert_equal 'global message', replied_topic.errors.on(:replies)
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1 b/activesupport/lib/active_support/vendor/i18n-0.0.1
index 8e43afa38a..20c331666b 160000
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1
+++ b/activesupport/lib/active_support/vendor/i18n-0.0.1
@@ -1 +1 @@
-Subproject commit 8e43afa38aa007d1de6d6acf44d43143c403d13f
+Subproject commit 20c331666b3b6a21791d4cded53c3d8654fba714
--
cgit v1.2.3
From 55e2e2e8b4efbe6fdb0a921c19cd8be5650eab0a Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Sat, 21 Jun 2008 18:12:59 +0200
Subject: experimental DeprecatedConstantToMethodProxy
---
activesupport/lib/active_support/deprecation.rb | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index ebdaf86146..36933b007d 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -194,6 +194,23 @@ module ActiveSupport
ActiveSupport::Deprecation.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack)
end
end
+
+ class DeprecatedConstantToMethodProxy < DeprecationProxy #:nodoc:
+ def initialize(old_const, new_target, new_method)
+ @old_const = old_const
+ @new_target = new_target
+ @new_method = new_method
+ end
+
+ private
+ def target
+ @new_target.__send__(@new_method)
+ end
+
+ def warn(callstack, called, args)
+ ActiveSupport::Deprecation.warn("#{@old_const} is deprecated! Use #{@new_target.inspect}.#{@new_method} instead.", callstack)
+ end
+ end
end
end
--
cgit v1.2.3
From d897acfbb11aaa2d7f3138e1f9772546ecb6f981 Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Sun, 22 Jun 2008 11:39:10 +0200
Subject: remove DeprecatedConstantToMethodProxy again
---
activesupport/lib/active_support/deprecation.rb | 17 -----------------
1 file changed, 17 deletions(-)
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 36933b007d..ebdaf86146 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -194,23 +194,6 @@ module ActiveSupport
ActiveSupport::Deprecation.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack)
end
end
-
- class DeprecatedConstantToMethodProxy < DeprecationProxy #:nodoc:
- def initialize(old_const, new_target, new_method)
- @old_const = old_const
- @new_target = new_target
- @new_method = new_method
- end
-
- private
- def target
- @new_target.__send__(@new_method)
- end
-
- def warn(callstack, called, args)
- ActiveSupport::Deprecation.warn("#{@old_const} is deprecated! Use #{@new_target.inspect}.#{@new_method} instead.", callstack)
- end
- end
end
end
--
cgit v1.2.3
From 20d6630c1bb70f09e1f6a135bd3f9d690ad28250 Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Sun, 22 Jun 2008 11:41:51 +0200
Subject: Replaced country_options_for_select with old, untranslated version
and moved country-related helpers to a new FormCountryHelper helper module so
that they can easily be moved to a plugin. Updated tests accordingly.
---
.../lib/action_view/helpers/form_country_helper.rb | 86 +++
.../lib/action_view/helpers/form_options_helper.rb | 32 +-
actionpack/lib/action_view/locale/en-US.rb | 40 --
.../test/template/form_country_helper_test.rb | 772 +++++++++++++++++++++
.../test/template/form_options_helper_i18n_test.rb | 26 -
.../test/template/form_options_helper_test.rb | 766 +-------------------
6 files changed, 862 insertions(+), 860 deletions(-)
create mode 100644 actionpack/lib/action_view/helpers/form_country_helper.rb
create mode 100644 actionpack/test/template/form_country_helper_test.rb
delete mode 100644 actionpack/test/template/form_options_helper_i18n_test.rb
diff --git a/actionpack/lib/action_view/helpers/form_country_helper.rb b/actionpack/lib/action_view/helpers/form_country_helper.rb
new file mode 100644
index 0000000000..b2d2b7741f
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/form_country_helper.rb
@@ -0,0 +1,86 @@
+require 'action_view/helpers/form_options_helper'
+
+module ActionView
+ module Helpers
+ module FormCountryHelper
+ # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
+ def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
+ InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
+ end
+
+ # Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
+ # have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
+ # that they will be listed above the rest of the (long) list.
+ #
+ # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
+ def country_options_for_select(selected = nil, priority_countries = nil)
+ country_options = ""
+
+ if priority_countries
+ country_options += options_for_select(priority_countries, selected)
+ country_options += "------------- \n"
+ end
+
+ return country_options + options_for_select(COUNTRIES, selected)
+ end
+
+ private
+
+ # All the countries included in the country_options output.
+ COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
+ "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
+ "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
+ "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
+ "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
+ "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
+ "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
+ "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
+ "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
+ "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
+ "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
+ "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
+ "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
+ "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
+ "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
+ "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
+ "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
+ "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
+ "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
+ "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
+ "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
+ "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
+ "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
+ "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
+ "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
+ "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
+ "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
+ "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
+ "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
+ "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
+ "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
+ "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
+ "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
+ "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
+ "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
+ "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
+ "Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES")
+
+ end
+
+ class InstanceTag #:nodoc:
+ include FormCountryHelper
+
+ def to_country_select_tag(priority_countries, options, html_options)
+ html_options = html_options.stringify_keys
+ add_default_name_and_id(html_options)
+ value = value(object)
+ content_tag("select",
+ add_options(
+ country_options_for_select(value, priority_countries),
+ options, value
+ ), html_options
+ )
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index a6b9e65a77..8ce092b015 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -133,11 +133,6 @@ module ActionView
InstanceTag.new(object, method, self, nil, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
end
- # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
- def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
- InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
- end
-
# Return select and option tags for the given object and method, using
# #time_zone_options_for_select to generate the list of option tags.
#
@@ -270,30 +265,6 @@ module ActionView
options_for_select += ''
end
end
-
- # Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
- # have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
- # that they will be listed above the rest of the (long) list.
- #
- # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
- def country_options_for_select(*args)
- options = args.extract_options!
-
- locale = options[:locale]
- locale ||= request.locale if respond_to?(:request)
-
- selected, priority_countries = *args
- countries = :'countries.names'.t options[:locale]
- country_options = ""
-
- if priority_countries
- # TODO priority_countries need to be translated?
- country_options += options_for_select(priority_countries, selected)
- country_options += "------------- \n"
- end
-
- return country_options + options_for_select(countries, selected)
- end
# Returns a string of option tags for pretty much any time zone in the
@@ -349,8 +320,7 @@ module ActionView
end
# All the countries included in the country_options output.
- # deprecated. please use :'countries.names'.t directly
- COUNTRIES = :'countries.names'.t 'en-US' unless const_defined?("COUNTRIES")
+ COUNTRIES = ActiveSupport::Deprecation::DeprecatedConstantProxy.new 'COUNTRIES', 'ActionView::Helpers::FormCountryHelper::COUNTRIES'
end
class InstanceTag #:nodoc:
diff --git a/actionpack/lib/action_view/locale/en-US.rb b/actionpack/lib/action_view/locale/en-US.rb
index 6b5345ed90..20d668a9e1 100644
--- a/actionpack/lib/action_view/locale/en-US.rb
+++ b/actionpack/lib/action_view/locale/en-US.rb
@@ -44,46 +44,6 @@ I18n.backend.store_translations :'en-US', {
:format => '%u%n',
}
},
- :countries => {
- :names => ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
- "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
- "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
- "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
- "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
- "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
- "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
- "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
- "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
- "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
- "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
- "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece",
- "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
- "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
- "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
- "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
- "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
- "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
- "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
- "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
- "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
- "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
- "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
- "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
- "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
- "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
- "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
- "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
- "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
- "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
- "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
- "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
- "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
- "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
- "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
- "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
- "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
- "Yemen", "Zambia", "Zimbabwe"]
- },
:active_record => {
:error => {
:header_message => ["1 error prohibited this {{object_name}} from being saved", "{{count}} errors prohibited this {{object_name}} from being saved"],
diff --git a/actionpack/test/template/form_country_helper_test.rb b/actionpack/test/template/form_country_helper_test.rb
new file mode 100644
index 0000000000..224b2e21c2
--- /dev/null
+++ b/actionpack/test/template/form_country_helper_test.rb
@@ -0,0 +1,772 @@
+require 'abstract_unit'
+
+class FormCountryHelperTest < ActionView::TestCase
+ tests ActionView::Helpers::FormCountryHelper
+
+ silence_warnings do
+ Post = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin)
+ end
+
+ def test_country_select
+ @post = Post.new
+ @post.origin = "Denmark"
+ expected_select = <<-COUNTRIES
+Afghanistan
+Aland Islands
+Albania
+Algeria
+American Samoa
+Andorra
+Angola
+Anguilla
+Antarctica
+Antigua And Barbuda
+Argentina
+Armenia
+Aruba
+Australia
+Austria
+Azerbaijan
+Bahamas
+Bahrain
+Bangladesh
+Barbados
+Belarus
+Belgium
+Belize
+Benin
+Bermuda
+Bhutan
+Bolivia
+Bosnia and Herzegowina
+Botswana
+Bouvet Island
+Brazil
+British Indian Ocean Territory
+Brunei Darussalam
+Bulgaria
+Burkina Faso
+Burundi
+Cambodia
+Cameroon
+Canada
+Cape Verde
+Cayman Islands
+Central African Republic
+Chad
+Chile
+China
+Christmas Island
+Cocos (Keeling) Islands
+Colombia
+Comoros
+Congo
+Congo, the Democratic Republic of the
+Cook Islands
+Costa Rica
+Cote d'Ivoire
+Croatia
+Cuba
+Cyprus
+Czech Republic
+Denmark
+Djibouti
+Dominica
+Dominican Republic
+Ecuador
+Egypt
+El Salvador
+Equatorial Guinea
+Eritrea
+Estonia
+Ethiopia
+Falkland Islands (Malvinas)
+Faroe Islands
+Fiji
+Finland
+France
+French Guiana
+French Polynesia
+French Southern Territories
+Gabon
+Gambia
+Georgia
+Germany
+Ghana
+Gibraltar
+Greece
+Greenland
+Grenada
+Guadeloupe
+Guam
+Guatemala
+Guernsey
+Guinea
+Guinea-Bissau
+Guyana
+Haiti
+Heard and McDonald Islands
+Holy See (Vatican City State)
+Honduras
+Hong Kong
+Hungary
+Iceland
+India
+Indonesia
+Iran, Islamic Republic of
+Iraq
+Ireland
+Isle of Man
+Israel
+Italy
+Jamaica
+Japan
+Jersey
+Jordan
+Kazakhstan
+Kenya
+Kiribati
+Korea, Democratic People's Republic of
+Korea, Republic of
+Kuwait
+Kyrgyzstan
+Lao People's Democratic Republic
+Latvia
+Lebanon
+Lesotho
+Liberia
+Libyan Arab Jamahiriya
+Liechtenstein
+Lithuania
+Luxembourg
+Macao
+Macedonia, The Former Yugoslav Republic Of
+Madagascar
+Malawi
+Malaysia
+Maldives
+Mali
+Malta
+Marshall Islands
+Martinique
+Mauritania
+Mauritius
+Mayotte
+Mexico
+Micronesia, Federated States of
+Moldova, Republic of
+Monaco
+Mongolia
+Montenegro
+Montserrat
+Morocco
+Mozambique
+Myanmar
+Namibia
+Nauru
+Nepal
+Netherlands
+Netherlands Antilles
+New Caledonia
+New Zealand
+Nicaragua
+Niger
+Nigeria
+Niue
+Norfolk Island
+Northern Mariana Islands
+Norway
+Oman
+Pakistan
+Palau
+Palestinian Territory, Occupied
+Panama
+Papua New Guinea
+Paraguay
+Peru
+Philippines
+Pitcairn
+Poland
+Portugal
+Puerto Rico
+Qatar
+Reunion
+Romania
+Russian Federation
+Rwanda
+Saint Barthelemy
+Saint Helena
+Saint Kitts and Nevis
+Saint Lucia
+Saint Pierre and Miquelon
+Saint Vincent and the Grenadines
+Samoa
+San Marino
+Sao Tome and Principe
+Saudi Arabia
+Senegal
+Serbia
+Seychelles
+Sierra Leone
+Singapore
+Slovakia
+Slovenia
+Solomon Islands
+Somalia
+South Africa
+South Georgia and the South Sandwich Islands
+Spain
+Sri Lanka
+Sudan
+Suriname
+Svalbard and Jan Mayen
+Swaziland
+Sweden
+Switzerland
+Syrian Arab Republic
+Taiwan, Province of China
+Tajikistan
+Tanzania, United Republic of
+Thailand
+Timor-Leste
+Togo
+Tokelau
+Tonga
+Trinidad and Tobago
+Tunisia
+Turkey
+Turkmenistan
+Turks and Caicos Islands
+Tuvalu
+Uganda
+Ukraine
+United Arab Emirates
+United Kingdom
+United States
+United States Minor Outlying Islands
+Uruguay
+Uzbekistan
+Vanuatu
+Venezuela
+Viet Nam
+Virgin Islands, British
+Virgin Islands, U.S.
+Wallis and Futuna
+Western Sahara
+Yemen
+Zambia
+Zimbabwe
+COUNTRIES
+ assert_dom_equal(expected_select[0..-2], country_select("post", "origin"))
+ end
+
+ def test_country_select_with_priority_countries
+ @post = Post.new
+ @post.origin = "Denmark"
+ expected_select = <<-COUNTRIES
+New Zealand
+Nicaragua -------------
+Afghanistan
+Aland Islands
+Albania
+Algeria
+American Samoa
+Andorra
+Angola
+Anguilla
+Antarctica
+Antigua And Barbuda
+Argentina
+Armenia
+Aruba
+Australia
+Austria
+Azerbaijan
+Bahamas
+Bahrain
+Bangladesh
+Barbados
+Belarus
+Belgium
+Belize
+Benin
+Bermuda
+Bhutan
+Bolivia
+Bosnia and Herzegowina
+Botswana
+Bouvet Island
+Brazil
+British Indian Ocean Territory
+Brunei Darussalam
+Bulgaria
+Burkina Faso
+Burundi
+Cambodia
+Cameroon
+Canada
+Cape Verde
+Cayman Islands
+Central African Republic
+Chad
+Chile
+China
+Christmas Island
+Cocos (Keeling) Islands
+Colombia
+Comoros
+Congo
+Congo, the Democratic Republic of the
+Cook Islands
+Costa Rica
+Cote d'Ivoire
+Croatia
+Cuba
+Cyprus
+Czech Republic
+Denmark
+Djibouti
+Dominica
+Dominican Republic
+Ecuador
+Egypt
+El Salvador
+Equatorial Guinea
+Eritrea
+Estonia
+Ethiopia
+Falkland Islands (Malvinas)
+Faroe Islands
+Fiji
+Finland
+France
+French Guiana
+French Polynesia
+French Southern Territories
+Gabon
+Gambia
+Georgia
+Germany
+Ghana
+Gibraltar
+Greece
+Greenland
+Grenada
+Guadeloupe
+Guam
+Guatemala
+Guernsey
+Guinea
+Guinea-Bissau
+Guyana
+Haiti
+Heard and McDonald Islands
+Holy See (Vatican City State)
+Honduras
+Hong Kong
+Hungary
+Iceland
+India
+Indonesia
+Iran, Islamic Republic of
+Iraq
+Ireland
+Isle of Man
+Israel
+Italy
+Jamaica
+Japan
+Jersey
+Jordan
+Kazakhstan
+Kenya
+Kiribati
+Korea, Democratic People's Republic of
+Korea, Republic of
+Kuwait
+Kyrgyzstan
+Lao People's Democratic Republic
+Latvia
+Lebanon
+Lesotho
+Liberia
+Libyan Arab Jamahiriya
+Liechtenstein
+Lithuania
+Luxembourg
+Macao
+Macedonia, The Former Yugoslav Republic Of
+Madagascar
+Malawi
+Malaysia
+Maldives
+Mali
+Malta
+Marshall Islands
+Martinique
+Mauritania
+Mauritius
+Mayotte
+Mexico
+Micronesia, Federated States of
+Moldova, Republic of
+Monaco
+Mongolia
+Montenegro
+Montserrat
+Morocco
+Mozambique
+Myanmar
+Namibia
+Nauru
+Nepal
+Netherlands
+Netherlands Antilles
+New Caledonia
+New Zealand
+Nicaragua
+Niger
+Nigeria
+Niue
+Norfolk Island
+Northern Mariana Islands
+Norway
+Oman
+Pakistan
+Palau
+Palestinian Territory, Occupied
+Panama
+Papua New Guinea
+Paraguay
+Peru
+Philippines
+Pitcairn
+Poland
+Portugal
+Puerto Rico
+Qatar
+Reunion
+Romania
+Russian Federation
+Rwanda
+Saint Barthelemy
+Saint Helena
+Saint Kitts and Nevis
+Saint Lucia
+Saint Pierre and Miquelon
+Saint Vincent and the Grenadines
+Samoa
+San Marino
+Sao Tome and Principe
+Saudi Arabia
+Senegal
+Serbia
+Seychelles
+Sierra Leone
+Singapore
+Slovakia
+Slovenia
+Solomon Islands
+Somalia
+South Africa
+South Georgia and the South Sandwich Islands
+Spain
+Sri Lanka
+Sudan
+Suriname
+Svalbard and Jan Mayen
+Swaziland
+Sweden
+Switzerland
+Syrian Arab Republic
+Taiwan, Province of China
+Tajikistan
+Tanzania, United Republic of
+Thailand
+Timor-Leste
+Togo
+Tokelau
+Tonga
+Trinidad and Tobago
+Tunisia
+Turkey
+Turkmenistan
+Turks and Caicos Islands
+Tuvalu
+Uganda
+Ukraine
+United Arab Emirates
+United Kingdom
+United States
+United States Minor Outlying Islands
+Uruguay
+Uzbekistan
+Vanuatu
+Venezuela
+Viet Nam
+Virgin Islands, British
+Virgin Islands, U.S.
+Wallis and Futuna
+Western Sahara
+Yemen
+Zambia
+Zimbabwe
+COUNTRIES
+ assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"]))
+ end
+
+ def test_country_select_with_selected_priority_country
+ @post = Post.new
+ @post.origin = "New Zealand"
+ expected_select = <<-COUNTRIES
+New Zealand
+Nicaragua -------------
+Afghanistan
+Aland Islands
+Albania
+Algeria
+American Samoa
+Andorra
+Angola
+Anguilla
+Antarctica
+Antigua And Barbuda
+Argentina
+Armenia
+Aruba
+Australia
+Austria
+Azerbaijan
+Bahamas
+Bahrain
+Bangladesh
+Barbados
+Belarus
+Belgium
+Belize
+Benin
+Bermuda
+Bhutan
+Bolivia
+Bosnia and Herzegowina
+Botswana
+Bouvet Island
+Brazil
+British Indian Ocean Territory
+Brunei Darussalam
+Bulgaria
+Burkina Faso
+Burundi
+Cambodia
+Cameroon
+Canada
+Cape Verde
+Cayman Islands
+Central African Republic
+Chad
+Chile
+China
+Christmas Island
+Cocos (Keeling) Islands
+Colombia
+Comoros
+Congo
+Congo, the Democratic Republic of the
+Cook Islands
+Costa Rica
+Cote d'Ivoire
+Croatia
+Cuba
+Cyprus
+Czech Republic
+Denmark
+Djibouti
+Dominica
+Dominican Republic
+Ecuador
+Egypt
+El Salvador
+Equatorial Guinea
+Eritrea
+Estonia
+Ethiopia
+Falkland Islands (Malvinas)
+Faroe Islands
+Fiji
+Finland
+France
+French Guiana
+French Polynesia
+French Southern Territories
+Gabon
+Gambia
+Georgia
+Germany
+Ghana
+Gibraltar
+Greece
+Greenland
+Grenada
+Guadeloupe
+Guam
+Guatemala
+Guernsey
+Guinea
+Guinea-Bissau
+Guyana
+Haiti
+Heard and McDonald Islands
+Holy See (Vatican City State)
+Honduras
+Hong Kong
+Hungary
+Iceland
+India
+Indonesia
+Iran, Islamic Republic of
+Iraq
+Ireland
+Isle of Man
+Israel
+Italy
+Jamaica
+Japan
+Jersey
+Jordan
+Kazakhstan
+Kenya
+Kiribati
+Korea, Democratic People's Republic of
+Korea, Republic of
+Kuwait
+Kyrgyzstan
+Lao People's Democratic Republic
+Latvia
+Lebanon
+Lesotho
+Liberia
+Libyan Arab Jamahiriya
+Liechtenstein
+Lithuania
+Luxembourg
+Macao
+Macedonia, The Former Yugoslav Republic Of
+Madagascar
+Malawi
+Malaysia
+Maldives
+Mali
+Malta
+Marshall Islands
+Martinique
+Mauritania
+Mauritius
+Mayotte
+Mexico
+Micronesia, Federated States of
+Moldova, Republic of
+Monaco
+Mongolia
+Montenegro
+Montserrat
+Morocco
+Mozambique
+Myanmar
+Namibia
+Nauru
+Nepal
+Netherlands
+Netherlands Antilles
+New Caledonia
+New Zealand
+Nicaragua
+Niger
+Nigeria
+Niue
+Norfolk Island
+Northern Mariana Islands
+Norway
+Oman
+Pakistan
+Palau
+Palestinian Territory, Occupied
+Panama
+Papua New Guinea
+Paraguay
+Peru
+Philippines
+Pitcairn
+Poland
+Portugal
+Puerto Rico
+Qatar
+Reunion
+Romania
+Russian Federation
+Rwanda
+Saint Barthelemy
+Saint Helena
+Saint Kitts and Nevis
+Saint Lucia
+Saint Pierre and Miquelon
+Saint Vincent and the Grenadines
+Samoa
+San Marino
+Sao Tome and Principe
+Saudi Arabia
+Senegal
+Serbia
+Seychelles
+Sierra Leone
+Singapore
+Slovakia
+Slovenia
+Solomon Islands
+Somalia
+South Africa
+South Georgia and the South Sandwich Islands
+Spain
+Sri Lanka
+Sudan
+Suriname
+Svalbard and Jan Mayen
+Swaziland
+Sweden
+Switzerland
+Syrian Arab Republic
+Taiwan, Province of China
+Tajikistan
+Tanzania, United Republic of
+Thailand
+Timor-Leste
+Togo
+Tokelau
+Tonga
+Trinidad and Tobago
+Tunisia
+Turkey
+Turkmenistan
+Turks and Caicos Islands
+Tuvalu
+Uganda
+Ukraine
+United Arab Emirates
+United Kingdom
+United States
+United States Minor Outlying Islands
+Uruguay
+Uzbekistan
+Vanuatu
+Venezuela
+Viet Nam
+Virgin Islands, British
+Virgin Islands, U.S.
+Wallis and Futuna
+Western Sahara
+Yemen
+Zambia
+Zimbabwe
+COUNTRIES
+ assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"]))
+ end
+end
\ No newline at end of file
diff --git a/actionpack/test/template/form_options_helper_i18n_test.rb b/actionpack/test/template/form_options_helper_i18n_test.rb
deleted file mode 100644
index c9fc0768bb..0000000000
--- a/actionpack/test/template/form_options_helper_i18n_test.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'abstract_unit'
-
-class FormOptionsHelperI18nTests < Test::Unit::TestCase
- include ActionView::Helpers::FormOptionsHelper
- attr_reader :request
-
- def setup
- @request = mock
- end
-
- def test_country_options_for_select_given_a_locale_it_does_not_check_request_for_locale
- request.expects(:locale).never
- country_options_for_select :locale => 'en-US'
- end
-
- def test_country_options_for_select_given_no_locale_it_checks_request_for_locale
- request.expects(:locale).returns 'en-US'
- country_options_for_select
- end
-
- def test_country_options_for_select_translates_country_names
- countries = ActionView::Helpers::FormOptionsHelper::COUNTRIES
- I18n.expects(:translate).with(:'countries.names', 'en-US').returns countries
- country_options_for_select :locale => 'en-US'
- end
-end
\ No newline at end of file
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 3f89a5e426..5ba81aac02 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -412,769 +412,6 @@ class FormOptionsHelperTest < ActionView::TestCase
assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true, :name => 'post[author_name][]' }, :multiple => true)
end
- def test_country_select
- @post = Post.new
- @post.origin = "Denmark"
- expected_select = <<-COUNTRIES
-Afghanistan
-Aland Islands
-Albania
-Algeria
-American Samoa
-Andorra
-Angola
-Anguilla
-Antarctica
-Antigua And Barbuda
-Argentina
-Armenia
-Aruba
-Australia
-Austria
-Azerbaijan
-Bahamas
-Bahrain
-Bangladesh
-Barbados
-Belarus
-Belgium
-Belize
-Benin
-Bermuda
-Bhutan
-Bolivia
-Bosnia and Herzegowina
-Botswana
-Bouvet Island
-Brazil
-British Indian Ocean Territory
-Brunei Darussalam
-Bulgaria
-Burkina Faso
-Burundi
-Cambodia
-Cameroon
-Canada
-Cape Verde
-Cayman Islands
-Central African Republic
-Chad
-Chile
-China
-Christmas Island
-Cocos (Keeling) Islands
-Colombia
-Comoros
-Congo
-Congo, the Democratic Republic of the
-Cook Islands
-Costa Rica
-Cote d'Ivoire
-Croatia
-Cuba
-Cyprus
-Czech Republic
-Denmark
-Djibouti
-Dominica
-Dominican Republic
-Ecuador
-Egypt
-El Salvador
-Equatorial Guinea
-Eritrea
-Estonia
-Ethiopia
-Falkland Islands (Malvinas)
-Faroe Islands
-Fiji
-Finland
-France
-French Guiana
-French Polynesia
-French Southern Territories
-Gabon
-Gambia
-Georgia
-Germany
-Ghana
-Gibraltar
-Greece
-Greenland
-Grenada
-Guadeloupe
-Guam
-Guatemala
-Guernsey
-Guinea
-Guinea-Bissau
-Guyana
-Haiti
-Heard and McDonald Islands
-Holy See (Vatican City State)
-Honduras
-Hong Kong
-Hungary
-Iceland
-India
-Indonesia
-Iran, Islamic Republic of
-Iraq
-Ireland
-Isle of Man
-Israel
-Italy
-Jamaica
-Japan
-Jersey
-Jordan
-Kazakhstan
-Kenya
-Kiribati
-Korea, Democratic People's Republic of
-Korea, Republic of
-Kuwait
-Kyrgyzstan
-Lao People's Democratic Republic
-Latvia
-Lebanon
-Lesotho
-Liberia
-Libyan Arab Jamahiriya
-Liechtenstein
-Lithuania
-Luxembourg
-Macao
-Macedonia, The Former Yugoslav Republic Of
-Madagascar
-Malawi
-Malaysia
-Maldives
-Mali
-Malta
-Marshall Islands
-Martinique
-Mauritania
-Mauritius
-Mayotte
-Mexico
-Micronesia, Federated States of
-Moldova, Republic of
-Monaco
-Mongolia
-Montenegro
-Montserrat
-Morocco
-Mozambique
-Myanmar
-Namibia
-Nauru
-Nepal
-Netherlands
-Netherlands Antilles
-New Caledonia
-New Zealand
-Nicaragua
-Niger
-Nigeria
-Niue
-Norfolk Island
-Northern Mariana Islands
-Norway
-Oman
-Pakistan
-Palau
-Palestinian Territory, Occupied
-Panama
-Papua New Guinea
-Paraguay
-Peru
-Philippines
-Pitcairn
-Poland
-Portugal
-Puerto Rico
-Qatar
-Reunion
-Romania
-Russian Federation
-Rwanda
-Saint Barthelemy
-Saint Helena
-Saint Kitts and Nevis
-Saint Lucia
-Saint Pierre and Miquelon
-Saint Vincent and the Grenadines
-Samoa
-San Marino
-Sao Tome and Principe
-Saudi Arabia
-Senegal
-Serbia
-Seychelles
-Sierra Leone
-Singapore
-Slovakia
-Slovenia
-Solomon Islands
-Somalia
-South Africa
-South Georgia and the South Sandwich Islands
-Spain
-Sri Lanka
-Sudan
-Suriname
-Svalbard and Jan Mayen
-Swaziland
-Sweden
-Switzerland
-Syrian Arab Republic
-Taiwan, Province of China
-Tajikistan
-Tanzania, United Republic of
-Thailand
-Timor-Leste
-Togo
-Tokelau
-Tonga
-Trinidad and Tobago
-Tunisia
-Turkey
-Turkmenistan
-Turks and Caicos Islands
-Tuvalu
-Uganda
-Ukraine
-United Arab Emirates
-United Kingdom
-United States
-United States Minor Outlying Islands
-Uruguay
-Uzbekistan
-Vanuatu
-Venezuela
-Viet Nam
-Virgin Islands, British
-Virgin Islands, U.S.
-Wallis and Futuna
-Western Sahara
-Yemen
-Zambia
-Zimbabwe
-COUNTRIES
- assert_dom_equal(expected_select[0..-2], country_select("post", "origin"))
- end
-
- def test_country_select_with_priority_countries
- @post = Post.new
- @post.origin = "Denmark"
- expected_select = <<-COUNTRIES
-New Zealand
-Nicaragua -------------
-Afghanistan
-Aland Islands
-Albania
-Algeria
-American Samoa
-Andorra
-Angola
-Anguilla
-Antarctica
-Antigua And Barbuda
-Argentina
-Armenia
-Aruba
-Australia
-Austria
-Azerbaijan
-Bahamas
-Bahrain
-Bangladesh
-Barbados
-Belarus
-Belgium
-Belize
-Benin
-Bermuda
-Bhutan
-Bolivia
-Bosnia and Herzegowina
-Botswana
-Bouvet Island
-Brazil
-British Indian Ocean Territory
-Brunei Darussalam
-Bulgaria
-Burkina Faso
-Burundi
-Cambodia
-Cameroon
-Canada
-Cape Verde
-Cayman Islands
-Central African Republic
-Chad
-Chile
-China
-Christmas Island
-Cocos (Keeling) Islands
-Colombia
-Comoros
-Congo
-Congo, the Democratic Republic of the
-Cook Islands
-Costa Rica
-Cote d'Ivoire
-Croatia
-Cuba
-Cyprus
-Czech Republic
-Denmark
-Djibouti
-Dominica
-Dominican Republic
-Ecuador
-Egypt
-El Salvador
-Equatorial Guinea
-Eritrea
-Estonia
-Ethiopia
-Falkland Islands (Malvinas)
-Faroe Islands
-Fiji
-Finland
-France
-French Guiana
-French Polynesia
-French Southern Territories
-Gabon
-Gambia
-Georgia
-Germany
-Ghana
-Gibraltar
-Greece
-Greenland
-Grenada
-Guadeloupe
-Guam
-Guatemala
-Guernsey
-Guinea
-Guinea-Bissau
-Guyana
-Haiti
-Heard and McDonald Islands
-Holy See (Vatican City State)
-Honduras
-Hong Kong
-Hungary
-Iceland
-India
-Indonesia
-Iran, Islamic Republic of
-Iraq
-Ireland
-Isle of Man
-Israel
-Italy
-Jamaica
-Japan
-Jersey
-Jordan
-Kazakhstan
-Kenya
-Kiribati
-Korea, Democratic People's Republic of
-Korea, Republic of
-Kuwait
-Kyrgyzstan
-Lao People's Democratic Republic
-Latvia
-Lebanon
-Lesotho
-Liberia
-Libyan Arab Jamahiriya
-Liechtenstein
-Lithuania
-Luxembourg
-Macao
-Macedonia, The Former Yugoslav Republic Of
-Madagascar
-Malawi
-Malaysia
-Maldives
-Mali
-Malta
-Marshall Islands
-Martinique
-Mauritania
-Mauritius
-Mayotte
-Mexico
-Micronesia, Federated States of
-Moldova, Republic of
-Monaco
-Mongolia
-Montenegro
-Montserrat
-Morocco
-Mozambique
-Myanmar
-Namibia
-Nauru
-Nepal
-Netherlands
-Netherlands Antilles
-New Caledonia
-New Zealand
-Nicaragua
-Niger
-Nigeria
-Niue
-Norfolk Island
-Northern Mariana Islands
-Norway
-Oman
-Pakistan
-Palau
-Palestinian Territory, Occupied
-Panama
-Papua New Guinea
-Paraguay
-Peru
-Philippines
-Pitcairn
-Poland
-Portugal
-Puerto Rico
-Qatar
-Reunion
-Romania
-Russian Federation
-Rwanda
-Saint Barthelemy
-Saint Helena
-Saint Kitts and Nevis
-Saint Lucia
-Saint Pierre and Miquelon
-Saint Vincent and the Grenadines
-Samoa
-San Marino
-Sao Tome and Principe
-Saudi Arabia
-Senegal
-Serbia
-Seychelles
-Sierra Leone
-Singapore
-Slovakia
-Slovenia
-Solomon Islands
-Somalia
-South Africa
-South Georgia and the South Sandwich Islands
-Spain
-Sri Lanka
-Sudan
-Suriname
-Svalbard and Jan Mayen
-Swaziland
-Sweden
-Switzerland
-Syrian Arab Republic
-Taiwan, Province of China
-Tajikistan
-Tanzania, United Republic of
-Thailand
-Timor-Leste
-Togo
-Tokelau
-Tonga
-Trinidad and Tobago
-Tunisia
-Turkey
-Turkmenistan
-Turks and Caicos Islands
-Tuvalu
-Uganda
-Ukraine
-United Arab Emirates
-United Kingdom
-United States
-United States Minor Outlying Islands
-Uruguay
-Uzbekistan
-Vanuatu
-Venezuela
-Viet Nam
-Virgin Islands, British
-Virgin Islands, U.S.
-Wallis and Futuna
-Western Sahara
-Yemen
-Zambia
-Zimbabwe
-COUNTRIES
- assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"]))
- end
-
- def test_country_select_with_selected_priority_country
- @post = Post.new
- @post.origin = "New Zealand"
- expected_select = <<-COUNTRIES
-New Zealand
-Nicaragua -------------
-Afghanistan
-Aland Islands
-Albania
-Algeria
-American Samoa
-Andorra
-Angola
-Anguilla
-Antarctica
-Antigua And Barbuda
-Argentina
-Armenia
-Aruba
-Australia
-Austria
-Azerbaijan
-Bahamas
-Bahrain
-Bangladesh
-Barbados
-Belarus
-Belgium
-Belize
-Benin
-Bermuda
-Bhutan
-Bolivia
-Bosnia and Herzegowina
-Botswana
-Bouvet Island
-Brazil
-British Indian Ocean Territory
-Brunei Darussalam
-Bulgaria
-Burkina Faso
-Burundi
-Cambodia
-Cameroon
-Canada
-Cape Verde
-Cayman Islands
-Central African Republic
-Chad
-Chile
-China
-Christmas Island
-Cocos (Keeling) Islands
-Colombia
-Comoros
-Congo
-Congo, the Democratic Republic of the
-Cook Islands
-Costa Rica
-Cote d'Ivoire
-Croatia
-Cuba
-Cyprus
-Czech Republic
-Denmark
-Djibouti
-Dominica
-Dominican Republic
-Ecuador
-Egypt
-El Salvador
-Equatorial Guinea
-Eritrea
-Estonia
-Ethiopia
-Falkland Islands (Malvinas)
-Faroe Islands
-Fiji
-Finland
-France
-French Guiana
-French Polynesia
-French Southern Territories
-Gabon
-Gambia
-Georgia
-Germany
-Ghana
-Gibraltar
-Greece
-Greenland
-Grenada
-Guadeloupe
-Guam
-Guatemala
-Guernsey
-Guinea
-Guinea-Bissau
-Guyana
-Haiti
-Heard and McDonald Islands
-Holy See (Vatican City State)
-Honduras
-Hong Kong
-Hungary
-Iceland
-India
-Indonesia
-Iran, Islamic Republic of
-Iraq
-Ireland
-Isle of Man
-Israel
-Italy
-Jamaica
-Japan
-Jersey
-Jordan
-Kazakhstan
-Kenya
-Kiribati
-Korea, Democratic People's Republic of
-Korea, Republic of
-Kuwait
-Kyrgyzstan
-Lao People's Democratic Republic
-Latvia
-Lebanon
-Lesotho
-Liberia
-Libyan Arab Jamahiriya
-Liechtenstein
-Lithuania
-Luxembourg
-Macao
-Macedonia, The Former Yugoslav Republic Of
-Madagascar
-Malawi
-Malaysia
-Maldives
-Mali
-Malta
-Marshall Islands
-Martinique
-Mauritania
-Mauritius
-Mayotte
-Mexico
-Micronesia, Federated States of
-Moldova, Republic of
-Monaco
-Mongolia
-Montenegro
-Montserrat
-Morocco
-Mozambique
-Myanmar
-Namibia
-Nauru
-Nepal
-Netherlands
-Netherlands Antilles
-New Caledonia
-New Zealand
-Nicaragua
-Niger
-Nigeria
-Niue
-Norfolk Island
-Northern Mariana Islands
-Norway
-Oman
-Pakistan
-Palau
-Palestinian Territory, Occupied
-Panama
-Papua New Guinea
-Paraguay
-Peru
-Philippines
-Pitcairn
-Poland
-Portugal
-Puerto Rico
-Qatar
-Reunion
-Romania
-Russian Federation
-Rwanda
-Saint Barthelemy
-Saint Helena
-Saint Kitts and Nevis
-Saint Lucia
-Saint Pierre and Miquelon
-Saint Vincent and the Grenadines
-Samoa
-San Marino
-Sao Tome and Principe
-Saudi Arabia
-Senegal
-Serbia
-Seychelles
-Sierra Leone
-Singapore
-Slovakia
-Slovenia
-Solomon Islands
-Somalia
-South Africa
-South Georgia and the South Sandwich Islands
-Spain
-Sri Lanka
-Sudan
-Suriname
-Svalbard and Jan Mayen
-Swaziland
-Sweden
-Switzerland
-Syrian Arab Republic
-Taiwan, Province of China
-Tajikistan
-Tanzania, United Republic of
-Thailand
-Timor-Leste
-Togo
-Tokelau
-Tonga
-Trinidad and Tobago
-Tunisia
-Turkey
-Turkmenistan
-Turks and Caicos Islands
-Tuvalu
-Uganda
-Ukraine
-United Arab Emirates
-United Kingdom
-United States
-United States Minor Outlying Islands
-Uruguay
-Uzbekistan
-Vanuatu
-Venezuela
-Viet Nam
-Virgin Islands, British
-Virgin Islands, U.S.
-Wallis and Futuna
-Western Sahara
-Yemen
-Zambia
-Zimbabwe
-COUNTRIES
- assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"]))
- end
-
def test_time_zone_select
@firm = Firm.new("D")
html = time_zone_select( "firm", "time_zone" )
@@ -1327,4 +564,7 @@ COUNTRIES
html
end
+ def test_countries_is_deprectated
+ assert_deprecated(/COUNTRIES/) { ActionView::Helpers::FormOptionsHelper::COUNTRIES.size }
+ end
end
--
cgit v1.2.3
From 67fce4671e8bcfe2aa670a89195b20837546183a Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Sun, 22 Jun 2008 13:49:08 +0200
Subject: crap, an array never has a request, stupid.
---
activesupport/lib/active_support/core_ext/array/conversions.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 08e608d346..80d91a6cbd 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -11,7 +11,7 @@ module ActiveSupport #:nodoc:
options.assert_valid_keys(:connector, :skip_last_comma, :locale)
locale = options[:locale]
- locale ||= request.locale if respond_to?(:request)
+ locale ||= self.locale if respond_to?(:locale)
default = :'support.array.sentence_connector'.t(locale)
options.reverse_merge! :connector => default, :skip_last_comma => false
--
cgit v1.2.3
From 3533dc68120ed40a4ec44ed9900c9035108cfcf1 Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Sun, 22 Jun 2008 13:49:38 +0200
Subject: check self.locale instead of request.locale in helpers
---
actionpack/lib/action_view/helpers/active_record_helper.rb | 2 +-
actionpack/lib/action_view/helpers/date_helper.rb | 4 ++--
actionpack/lib/action_view/helpers/number_helper.rb | 2 +-
actionpack/test/template/active_record_helper_i18n_test.rb | 6 +++---
actionpack/test/template/date_helper_i18n_test.rb | 8 ++++----
actionpack/test/template/number_helper_i18n_test.rb | 6 +++---
6 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb
index 5ad9d5f76d..716e303a5d 100644
--- a/actionpack/lib/action_view/helpers/active_record_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -160,7 +160,7 @@ module ActionView
count = objects.inject(0) {|sum, object| sum + object.errors.count }
locale = options[:locale]
- locale ||= request.locale if respond_to?(:request)
+ locale ||= self.locale if respond_to?(:locale)
unless count.zero?
html = {}
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 0337be0744..dbb5d458bf 100755
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -60,7 +60,7 @@ module ActionView
#
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
locale = options[:locale]
- locale ||= request.locale if respond_to?(:request)
+ locale ||= self.locale if respond_to?(:locale)
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
@@ -507,7 +507,7 @@ module ActionView
#
def select_month(date, options = {}, html_options = {})
locale = options[:locale]
- locale ||= request.locale if respond_to?(:request)
+ locale ||= self.locale if respond_to?(:locale)
val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
if options[:use_hidden]
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 9d98036f2d..dc56817c12 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -72,7 +72,7 @@ module ActionView
options = options.symbolize_keys
locale = options[:locale]
- locale ||= request.locale if respond_to?(:request)
+ locale ||= self.locale if respond_to?(:locale)
defaults = :'currency.format'.t(locale) || {}
precision = options[:precision] || defaults[:precision]
diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb
index 057fb9bd1a..3a2197ac93 100644
--- a/actionpack/test/template/active_record_helper_i18n_test.rb
+++ b/actionpack/test/template/active_record_helper_i18n_test.rb
@@ -5,22 +5,22 @@ class ActiveRecordHelperI18nTest < Test::Unit::TestCase
attr_reader :request
def setup
- @request = mock
@object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
stubs(:content_tag).returns 'content_tag'
+ stubs(:locale)
I18n.stubs(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns "1 error prohibited this from being saved"
I18n.stubs(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
end
def test_error_messages_for_given_a_locale_it_does_not_check_request_for_locale
- request.expects(:locale).never
+ expects(:locale).never
@object.errors.stubs(:count).returns 0
error_messages_for(:object => @object, :locale => 'en-US')
end
def test_error_messages_for_given_no_locale_it_checks_request_for_locale
- request.expects(:locale).returns 'en-US'
+ expects(:locale).returns 'en-US'
@object.errors.stubs(:count).returns 0
error_messages_for(:object => @object)
end
diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb
index 9b7c03a400..f245ca1fc8 100644
--- a/actionpack/test/template/date_helper_i18n_test.rb
+++ b/actionpack/test/template/date_helper_i18n_test.rb
@@ -5,19 +5,19 @@ class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
attr_reader :request
def setup
- @request = mock
+ stubs(:locale)
@from = Time.mktime(2004, 6, 6, 21, 45, 0)
end
# distance_of_time_in_words
def test_distance_of_time_in_words_given_a_locale_it_does_not_check_request_for_locale
- request.expects(:locale).never
+ expects(:locale).never
distance_of_time_in_words @from, @from + 1.second, false, :locale => 'en-US'
end
def test_distance_of_time_in_words_given_no_locale_it_checks_request_for_locale
- request.expects(:locale).returns 'en-US'
+ expects(:locale).returns 'en-US'
distance_of_time_in_words @from, @from + 1.second
end
@@ -64,7 +64,7 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
attr_reader :request
def setup
- @request = mock
+ # stubs(:locale)
I18n.stubs(:translate).with(:'date.month_names', 'en-US').returns Date::MONTHNAMES
end
diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb
index b75af03378..5db60ece04 100644
--- a/actionpack/test/template/number_helper_i18n_test.rb
+++ b/actionpack/test/template/number_helper_i18n_test.rb
@@ -5,18 +5,18 @@ class NumberHelperI18nTests < Test::Unit::TestCase
attr_reader :request
def setup
- @request = mock
+ stubs(:locale)
@defaults = {:separator => ".", :unit => "$", :format => "%u%n", :delimiter => ",", :precision => 2}
I18n.backend.store_translations 'en-US', :currency => {:format => @defaults}
end
def test_number_to_currency_given_a_locale_it_does_not_check_request_for_locale
- request.expects(:locale).never
+ expects(:locale).never
number_to_currency(1, :locale => 'en-US')
end
def test_number_to_currency_given_no_locale_it_checks_request_for_locale
- request.expects(:locale).returns 'en-US'
+ expects(:locale).returns 'en-US'
number_to_currency(1)
end
--
cgit v1.2.3
From 0dddba41fcfcd28de2ce1a88a23514fbde53afcf Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Mon, 23 Jun 2008 14:33:29 +0200
Subject: rather cosmetic improvements of test coverage
---
.../lib/action_view/helpers/number_helper.rb | 2 +-
activerecord/lib/active_record/validations.rb | 8 +--
activerecord/test/cases/validations_i18n_test.rb | 62 ++++++++++++++++++++--
3 files changed, 64 insertions(+), 8 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index dc56817c12..4373d063bb 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -70,7 +70,7 @@ module ActionView
# # => 1234567890,50 £
def number_to_currency(number, options = {})
options = options.symbolize_keys
-
+
locale = options[:locale]
locale ||= self.locale if respond_to?(:locale)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 49d3c59ca7..5bbd10394c 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -21,8 +21,8 @@ module ActiveRecord
class << self
def default_error_messages
- # ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use :'active_record.error_messages'.t.")
- :'active_record.error_messages'.t
+ ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use 'active_record.error_messages'.t.")
+ 'active_record.error_messages'.t
end
end
@@ -163,7 +163,7 @@ module ActiveRecord
@errors.each_key do |attr|
@errors[attr].each do |message|
next unless message
-
+
if attr == "base"
full_messages << message
else
@@ -872,7 +872,7 @@ module ActiveRecord
end
raw_value = raw_value.to_i
else
- begin
+ begin
raw_value = Kernel.Float(raw_value.to_s)
rescue ArgumentError, TypeError
message = record.errors.generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb
index 37a7c1ce49..158ff69e57 100644
--- a/activerecord/test/cases/validations_i18n_test.rb
+++ b/activerecord/test/cases/validations_i18n_test.rb
@@ -34,6 +34,12 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
end
+ def test_default_error_messages_is_deprecated
+ assert_deprecated('ActiveRecord::Errors.default_error_messages') do
+ ActiveRecord::Errors.default_error_messages
+ end
+ end
+
# ActiveRecord::Errors
def test_errors_generate_message_translates_custom_model_attribute_key
@@ -182,18 +188,32 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
# validates_length_of :within
- def test_validates_length_of_within_generates_message
+ def test_validates_length_of_within_generates_message_with_title_too_short
Topic.validates_length_of :title, :within => 3..5
@topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
@topic.valid?
end
- def test_validates_length_of_within_generates_message_with_custom_default_message
+ def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message
Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom'
@topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
@topic.valid?
end
+ def test_validates_length_of_within_generates_message_with_title_too_long
+ Topic.validates_length_of :title, :within => 3..5
+ @topic.title = 'this title is too long'
+ @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message
+ Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom'
+ @topic.title = 'this title is too long'
+ @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'})
+ @topic.valid?
+ end
+
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:too_short => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:too_short => 'global message'}}
@@ -382,7 +402,43 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
- # validates_numericality_of :only_integer
+ # validates_numericality_of without :only_integer
+
+ def test_validates_numericality_of_generates_message
+ Topic.validates_numericality_of :title
+ @topic.title = 'a'
+ @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_numericality_of_generates_message_with_custom_default_message
+ Topic.validates_numericality_of :title, :message => 'custom'
+ @topic.title = 'a'
+ @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_numericality_of_finds_custom_model_key_translation
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:not_a_number => 'custom message'}}}}}
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
+
+ Topic.validates_numericality_of :title
+ @topic.title = 'a'
+ @topic.valid?
+ assert_equal 'custom message', @topic.errors.on(:title)
+ end
+
+ def test_validates_numericality_of_finds_global_default_translation
+ I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:not_a_number => 'global message'}}
+
+ Topic.validates_numericality_of :title, :only_integer => true
+ @topic.title = 'a'
+ @topic.valid?
+ assert_equal 'global message', @topic.errors.on(:title)
+ end
+
+
+ # validates_numericality_of with :only_integer
def test_validates_numericality_of_only_integer_generates_message
Topic.validates_numericality_of :title, :only_integer => true
--
cgit v1.2.3
From 2ee9f2a0303cba95b2d8073fc7e22ec75229a8ee Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Mon, 23 Jun 2008 14:34:01 +0200
Subject: remove generic translate helpers
---
actionpack/lib/action_view/helpers/i18n_helper.rb | 19 -------------
actionpack/test/template/i18n_helper_test.rb | 33 -----------------------
2 files changed, 52 deletions(-)
delete mode 100644 actionpack/lib/action_view/helpers/i18n_helper.rb
delete mode 100644 actionpack/test/template/i18n_helper_test.rb
diff --git a/actionpack/lib/action_view/helpers/i18n_helper.rb b/actionpack/lib/action_view/helpers/i18n_helper.rb
deleted file mode 100644
index 1b1d1f301d..0000000000
--- a/actionpack/lib/action_view/helpers/i18n_helper.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module ActionView
- module Helpers
- module I18nHelper
- def translate(*args)
- # inserts the locale or current request locale to the argument list if no locale
- # has been passed or the locale has been passed as part of the options hash
- options = args.extract_options!
- if args.size != 2
- locale = options.delete :locale
- locale ||= request.locale if respond_to? :request
- args << locale if locale
- end
- args << options unless options.empty?
- I18n.translate *args
- end
- alias :t :translate
- end
- end
-end
\ No newline at end of file
diff --git a/actionpack/test/template/i18n_helper_test.rb b/actionpack/test/template/i18n_helper_test.rb
deleted file mode 100644
index 598731568c..0000000000
--- a/actionpack/test/template/i18n_helper_test.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'abstract_unit'
-require 'action_view/helpers/i18n_helper'
-
-class I18nHelperTests < Test::Unit::TestCase
- include ActionView::Helpers::I18nHelper
-
- attr_reader :request
- def setup
- @request = stub :locale => 'en-US'
- I18n.stubs(:translate).with(:'foo.bar', 'en-US').returns 'Foo Bar'
- end
-
- def test_translate_given_a_locale_argument_it_does_not_check_request_for_locale
- request.expects(:locale).never
- assert_equal 'Foo Bar', translate(:'foo.bar', :locale => 'en-US')
- end
-
- def test_translate_given_a_locale_option_it_does_not_check_request_for_locale
- request.expects(:locale).never
- I18n.expects(:translate).with(:'foo.bar', 'en-US').returns 'Foo Bar'
- assert_equal 'Foo Bar', translate(:'foo.bar', :locale => 'en-US')
- end
-
- def test_translate_given_no_locale_it_checks_request_for_locale
- request.expects(:locale).returns 'en-US'
- assert_equal 'Foo Bar', translate(:'foo.bar')
- end
-
- def test_translate_delegates_to_i18n_translate
- I18n.expects(:translate).with(:'foo.bar', 'en-US').returns 'Foo Bar'
- assert_equal 'Foo Bar', translate(:'foo.bar')
- end
-end
\ No newline at end of file
--
cgit v1.2.3
From c178a87b4326edd491922136c0a55bf4b889473d Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Mon, 23 Jun 2008 14:37:50 +0200
Subject: remove call to self.locale from helpers
---
actionpack/lib/action_view/helpers/active_record_helper.rb | 5 +----
actionpack/lib/action_view/helpers/date_helper.rb | 5 +----
actionpack/lib/action_view/helpers/number_helper.rb | 9 +++------
3 files changed, 5 insertions(+), 14 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb
index 716e303a5d..4ff16cd70c 100644
--- a/actionpack/lib/action_view/helpers/active_record_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -159,9 +159,6 @@ module ActionView
end
count = objects.inject(0) {|sum, object| sum + object.errors.count }
- locale = options[:locale]
- locale ||= self.locale if respond_to?(:locale)
-
unless count.zero?
html = {}
[:id, :class].each do |key|
@@ -174,7 +171,7 @@ module ActionView
end
options[:object_name] ||= params.first
- I18n.with_options :locale => locale, :scope => [:active_record, :error] do |locale|
+ I18n.with_options :locale => options[:locale], :scope => [:active_record, :error] do |locale|
header_message = if options.include?(:header_message)
options[:header_message]
else
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index dbb5d458bf..6ac4171fd5 100755
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -59,15 +59,12 @@ module ActionView
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
#
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
- locale = options[:locale]
- locale ||= self.locale if respond_to?(:locale)
-
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
distance_in_minutes = (((to_time - from_time).abs)/60).round
distance_in_seconds = ((to_time - from_time).abs).round
- I18n.with_options :locale => locale, :scope => :'datetime.distance_in_words' do |locale|
+ I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
case distance_in_minutes
when 0..1
return distance_in_minutes == 0 ?
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 4373d063bb..3e0d5b1db4 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -69,12 +69,9 @@ module ActionView
# number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u")
# # => 1234567890,50 £
def number_to_currency(number, options = {})
- options = options.symbolize_keys
-
- locale = options[:locale]
- locale ||= self.locale if respond_to?(:locale)
-
- defaults = :'currency.format'.t(locale) || {}
+ options = options.symbolize_keys
+ defaults = :'currency.format'.t(options[:locale]) || {}
+
precision = options[:precision] || defaults[:precision]
unit = options[:unit] || defaults[:unit]
separator = options[:separator] || defaults[:separator]
--
cgit v1.2.3
From ac66865ea3d0eb0de8e19fef49293feb9e61281b Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Mon, 23 Jun 2008 14:49:02 +0200
Subject: update tests according to removal of self.locale from helpers
---
actionpack/test/template/active_record_helper_i18n_test.rb | 13 -------------
actionpack/test/template/date_helper_i18n_test.rb | 12 ------------
actionpack/test/template/number_helper_i18n_test.rb | 11 -----------
3 files changed, 36 deletions(-)
diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb
index 3a2197ac93..d1b92c7e4d 100644
--- a/actionpack/test/template/active_record_helper_i18n_test.rb
+++ b/actionpack/test/template/active_record_helper_i18n_test.rb
@@ -7,24 +7,11 @@ class ActiveRecordHelperI18nTest < Test::Unit::TestCase
def setup
@object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
stubs(:content_tag).returns 'content_tag'
- stubs(:locale)
I18n.stubs(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns "1 error prohibited this from being saved"
I18n.stubs(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
end
- def test_error_messages_for_given_a_locale_it_does_not_check_request_for_locale
- expects(:locale).never
- @object.errors.stubs(:count).returns 0
- error_messages_for(:object => @object, :locale => 'en-US')
- end
-
- def test_error_messages_for_given_no_locale_it_checks_request_for_locale
- expects(:locale).returns 'en-US'
- @object.errors.stubs(:count).returns 0
- error_messages_for(:object => @object)
- end
-
def test_error_messages_for_given_a_header_message_option_it_does_not_translate_header_message
I18n.expects(:translate).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').never
error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en-US')
diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb
index f245ca1fc8..3e1eed61fd 100644
--- a/actionpack/test/template/date_helper_i18n_test.rb
+++ b/actionpack/test/template/date_helper_i18n_test.rb
@@ -5,22 +5,11 @@ class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
attr_reader :request
def setup
- stubs(:locale)
@from = Time.mktime(2004, 6, 6, 21, 45, 0)
end
# distance_of_time_in_words
- def test_distance_of_time_in_words_given_a_locale_it_does_not_check_request_for_locale
- expects(:locale).never
- distance_of_time_in_words @from, @from + 1.second, false, :locale => 'en-US'
- end
-
- def test_distance_of_time_in_words_given_no_locale_it_checks_request_for_locale
- expects(:locale).returns 'en-US'
- distance_of_time_in_words @from, @from + 1.second
- end
-
def test_distance_of_time_in_words_calls_i18n
{ # with include_seconds
[2.seconds, true] => [:'less_than_x_seconds', 5],
@@ -64,7 +53,6 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
attr_reader :request
def setup
- # stubs(:locale)
I18n.stubs(:translate).with(:'date.month_names', 'en-US').returns Date::MONTHNAMES
end
diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb
index 5db60ece04..bee9ceaa71 100644
--- a/actionpack/test/template/number_helper_i18n_test.rb
+++ b/actionpack/test/template/number_helper_i18n_test.rb
@@ -5,21 +5,10 @@ class NumberHelperI18nTests < Test::Unit::TestCase
attr_reader :request
def setup
- stubs(:locale)
@defaults = {:separator => ".", :unit => "$", :format => "%u%n", :delimiter => ",", :precision => 2}
I18n.backend.store_translations 'en-US', :currency => {:format => @defaults}
end
- def test_number_to_currency_given_a_locale_it_does_not_check_request_for_locale
- expects(:locale).never
- number_to_currency(1, :locale => 'en-US')
- end
-
- def test_number_to_currency_given_no_locale_it_checks_request_for_locale
- expects(:locale).returns 'en-US'
- number_to_currency(1)
- end
-
def test_number_to_currency_translates_currency_formats
I18n.expects(:translate).with(:'currency.format', 'en-US').returns @defaults
number_to_currency(1, :locale => 'en-US')
--
cgit v1.2.3
From 77177441d1bd8f62c5b6a990ddee155061df661c Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Mon, 23 Jun 2008 14:49:47 +0200
Subject: including rcov shell scripts for reference
---
actionpack/test/i18n_coverage | 9 +++++++++
activerecord/test/i18n_coverage | 6 ++++++
2 files changed, 15 insertions(+)
create mode 100755 actionpack/test/i18n_coverage
create mode 100755 activerecord/test/i18n_coverage
diff --git a/actionpack/test/i18n_coverage b/actionpack/test/i18n_coverage
new file mode 100755
index 0000000000..57b54e9d47
--- /dev/null
+++ b/actionpack/test/i18n_coverage
@@ -0,0 +1,9 @@
+rcov -x abstract_unit.rb \
+-i action_view/helpers/number_helper.rb,action_view/helpers/date_helper.rb,action_view/helpers/active_record_helper.rb \
+template/number_helper_i18n_test.rb \
+template/date_helper_i18n_test.rb \
+template/active_record_helper_i18n_test.rb \
+
+# template/number_helper_test.rb \
+# template/date_helper_test.rb \
+# template/active_record_helper_test.rb
\ No newline at end of file
diff --git a/activerecord/test/i18n_coverage b/activerecord/test/i18n_coverage
new file mode 100755
index 0000000000..1589a6c06f
--- /dev/null
+++ b/activerecord/test/i18n_coverage
@@ -0,0 +1,6 @@
+rcov -I connections/native_mysql \
+-x cases/helper,config,connection,models \
+-i active_record/validations.rb \
+cases/validations_i18n_test.rb \
+
+# cases/validations_test.rb
\ No newline at end of file
--
cgit v1.2.3
From 8461526f346b8d8387ba3b74221fbeefef3aefeb Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Mon, 23 Jun 2008 14:55:07 +0200
Subject: silence deprecation warning during validations test
---
activerecord/test/cases/validations_test.rb | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 7b71647d25..ad27ac951c 100755
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -853,7 +853,9 @@ class ValidationsTest < ActiveRecord::TestCase
end
def test_validates_length_with_globally_modified_error_message
- ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre %d'
+ ActiveSupport::Deprecation.silence do
+ ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre %d'
+ end
Topic.validates_length_of :title, :minimum => 10
t = Topic.create(:title => 'too short')
assert !t.valid?
--
cgit v1.2.3
From 66c2508ebbca06e551255a76cc47a1608f091992 Mon Sep 17 00:00:00 2001
From: Luca Guidi
Date: Fri, 27 Jun 2008 15:00:55 +0200
Subject: Make sure mocha is available
---
.../template/active_record_helper_i18n_test.rb | 48 +-
actionpack/test/template/date_helper_i18n_test.rb | 134 ++---
.../test/template/number_helper_i18n_test.rb | 16 +-
activerecord/test/cases/validations_i18n_test.rb | 576 +++++++++++----------
4 files changed, 413 insertions(+), 361 deletions(-)
diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb
index d1b92c7e4d..d78b0e4c0c 100644
--- a/actionpack/test/template/active_record_helper_i18n_test.rb
+++ b/actionpack/test/template/active_record_helper_i18n_test.rb
@@ -4,31 +4,33 @@ class ActiveRecordHelperI18nTest < Test::Unit::TestCase
include ActionView::Helpers::ActiveRecordHelper
attr_reader :request
- def setup
- @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
- stubs(:content_tag).returns 'content_tag'
-
- I18n.stubs(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns "1 error prohibited this from being saved"
- I18n.stubs(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
- end
+ uses_mocha 'active_record_helper_i18n_test' do
+ def setup
+ @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
+ stubs(:content_tag).returns 'content_tag'
- def test_error_messages_for_given_a_header_message_option_it_does_not_translate_header_message
- I18n.expects(:translate).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').never
- error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en-US')
- end
+ I18n.stubs(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns "1 error prohibited this from being saved"
+ I18n.stubs(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
+ end
- def test_error_messages_for_given_no_header_message_option_it_translates_header_message
- I18n.expects(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns 'header message'
- error_messages_for(:object => @object, :locale => 'en-US')
- end
-
- def test_error_messages_for_given_a_message_option_it_does_not_translate_message
- I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).never
- error_messages_for(:object => @object, :message => 'message', :locale => 'en-US')
- end
+ def test_error_messages_for_given_a_header_message_option_it_does_not_translate_header_message
+ I18n.expects(:translate).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').never
+ error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en-US')
+ end
+
+ def test_error_messages_for_given_no_header_message_option_it_translates_header_message
+ I18n.expects(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns 'header message'
+ error_messages_for(:object => @object, :locale => 'en-US')
+ end
+
+ def test_error_messages_for_given_a_message_option_it_does_not_translate_message
+ I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).never
+ error_messages_for(:object => @object, :message => 'message', :locale => 'en-US')
+ end
- def test_error_messages_for_given_no_message_option_it_translates_message
- I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
- error_messages_for(:object => @object, :locale => 'en-US')
+ def test_error_messages_for_given_no_message_option_it_translates_message
+ I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
+ error_messages_for(:object => @object, :locale => 'en-US')
+ end
end
end
\ No newline at end of file
diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb
index 3e1eed61fd..aeb06c55ea 100644
--- a/actionpack/test/template/date_helper_i18n_test.rb
+++ b/actionpack/test/template/date_helper_i18n_test.rb
@@ -8,43 +8,45 @@ class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
@from = Time.mktime(2004, 6, 6, 21, 45, 0)
end
- # distance_of_time_in_words
+ uses_mocha 'date_helper_distance_of_time_in_words_i18n_test' do
+ # distance_of_time_in_words
- def test_distance_of_time_in_words_calls_i18n
- { # with include_seconds
- [2.seconds, true] => [:'less_than_x_seconds', 5],
- [9.seconds, true] => [:'less_than_x_seconds', 10],
- [19.seconds, true] => [:'less_than_x_seconds', 20],
- [30.seconds, true] => [:'half_a_minute', nil],
- [59.seconds, true] => [:'less_than_x_minutes', 1],
- [60.seconds, true] => [:'x_minutes', 1],
-
- # without include_seconds
- [29.seconds, false] => [:'less_than_x_minutes', 1],
- [60.seconds, false] => [:'x_minutes', 1],
- [44.minutes, false] => [:'x_minutes', 44],
- [61.minutes, false] => [:'about_x_hours', 1],
- [24.hours, false] => [:'x_days', 1],
- [30.days, false] => [:'about_x_months', 1],
- [60.days, false] => [:'x_months', 2],
- [1.year, false] => [:'about_x_years', 1],
- [3.years, false] => [:'over_x_years', 3]
-
- }.each do |passed, expected|
- assert_distance_of_time_in_words_translates_key passed, expected
+ def test_distance_of_time_in_words_calls_i18n
+ { # with include_seconds
+ [2.seconds, true] => [:'less_than_x_seconds', 5],
+ [9.seconds, true] => [:'less_than_x_seconds', 10],
+ [19.seconds, true] => [:'less_than_x_seconds', 20],
+ [30.seconds, true] => [:'half_a_minute', nil],
+ [59.seconds, true] => [:'less_than_x_minutes', 1],
+ [60.seconds, true] => [:'x_minutes', 1],
+
+ # without include_seconds
+ [29.seconds, false] => [:'less_than_x_minutes', 1],
+ [60.seconds, false] => [:'x_minutes', 1],
+ [44.minutes, false] => [:'x_minutes', 44],
+ [61.minutes, false] => [:'about_x_hours', 1],
+ [24.hours, false] => [:'x_days', 1],
+ [30.days, false] => [:'about_x_months', 1],
+ [60.days, false] => [:'x_months', 2],
+ [1.year, false] => [:'about_x_years', 1],
+ [3.years, false] => [:'over_x_years', 3]
+
+ }.each do |passed, expected|
+ assert_distance_of_time_in_words_translates_key passed, expected
+ end
end
- end
-
- def assert_distance_of_time_in_words_translates_key(passed, expected)
- diff, include_seconds = *passed
- key, count = *expected
- to = @from + diff
- options = {:locale => 'en-US', :scope => :'datetime.distance_in_words'}
- options[:count] = count if count
-
- I18n.expects(:t).with(key, options)
- distance_of_time_in_words(@from, to, include_seconds, :locale => 'en-US')
+ def assert_distance_of_time_in_words_translates_key(passed, expected)
+ diff, include_seconds = *passed
+ key, count = *expected
+ to = @from + diff
+
+ options = {:locale => 'en-US', :scope => :'datetime.distance_in_words'}
+ options[:count] = count if count
+
+ I18n.expects(:t).with(key, options)
+ distance_of_time_in_words(@from, to, include_seconds, :locale => 'en-US')
+ end
end
end
@@ -52,36 +54,38 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
include ActionView::Helpers::DateHelper
attr_reader :request
- def setup
- I18n.stubs(:translate).with(:'date.month_names', 'en-US').returns Date::MONTHNAMES
- end
-
- # select_month
-
- def test_select_month_given_use_month_names_option_does_not_translate_monthnames
- I18n.expects(:translate).never
- select_month(8, :locale => 'en-US', :use_month_names => Date::MONTHNAMES)
- end
-
- def test_select_month_translates_monthnames
- I18n.expects(:translate).with(:'date.month_names', 'en-US').returns Date::MONTHNAMES
- select_month(8, :locale => 'en-US')
- end
-
- def test_select_month_given_use_short_month_option_translates_abbr_monthnames
- I18n.expects(:translate).with(:'date.abbr_month_names', 'en-US').returns Date::ABBR_MONTHNAMES
- select_month(8, :locale => 'en-US', :use_short_month => true)
- end
-
- # date_or_time_select
-
- def test_date_or_time_select_given_an_order_options_does_not_translate_order
- I18n.expects(:translate).never
- datetime_select('post', 'updated_at', :order => [:year, :month, :day], :locale => 'en-US')
- end
-
- def test_date_or_time_select_given_no_order_options_translates_order
- I18n.expects(:translate).with(:'date.order', 'en-US').returns [:year, :month, :day]
- datetime_select('post', 'updated_at', :locale => 'en-US')
+ uses_mocha 'date_helper_select_tags_i18n_tests' do
+ def setup
+ I18n.stubs(:translate).with(:'date.month_names', 'en-US').returns Date::MONTHNAMES
+ end
+
+ # select_month
+
+ def test_select_month_given_use_month_names_option_does_not_translate_monthnames
+ I18n.expects(:translate).never
+ select_month(8, :locale => 'en-US', :use_month_names => Date::MONTHNAMES)
+ end
+
+ def test_select_month_translates_monthnames
+ I18n.expects(:translate).with(:'date.month_names', 'en-US').returns Date::MONTHNAMES
+ select_month(8, :locale => 'en-US')
+ end
+
+ def test_select_month_given_use_short_month_option_translates_abbr_monthnames
+ I18n.expects(:translate).with(:'date.abbr_month_names', 'en-US').returns Date::ABBR_MONTHNAMES
+ select_month(8, :locale => 'en-US', :use_short_month => true)
+ end
+
+ # date_or_time_select
+
+ def test_date_or_time_select_given_an_order_options_does_not_translate_order
+ I18n.expects(:translate).never
+ datetime_select('post', 'updated_at', :order => [:year, :month, :day], :locale => 'en-US')
+ end
+
+ def test_date_or_time_select_given_no_order_options_translates_order
+ I18n.expects(:translate).with(:'date.order', 'en-US').returns [:year, :month, :day]
+ datetime_select('post', 'updated_at', :locale => 'en-US')
+ end
end
end
\ No newline at end of file
diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb
index bee9ceaa71..be40ddbc88 100644
--- a/actionpack/test/template/number_helper_i18n_test.rb
+++ b/actionpack/test/template/number_helper_i18n_test.rb
@@ -4,13 +4,15 @@ class NumberHelperI18nTests < Test::Unit::TestCase
include ActionView::Helpers::NumberHelper
attr_reader :request
- def setup
- @defaults = {:separator => ".", :unit => "$", :format => "%u%n", :delimiter => ",", :precision => 2}
- I18n.backend.store_translations 'en-US', :currency => {:format => @defaults}
- end
+ uses_mocha 'number_helper_i18n_tests' do
+ def setup
+ @defaults = {:separator => ".", :unit => "$", :format => "%u%n", :delimiter => ",", :precision => 2}
+ I18n.backend.store_translations 'en-US', :currency => {:format => @defaults}
+ end
- def test_number_to_currency_translates_currency_formats
- I18n.expects(:translate).with(:'currency.format', 'en-US').returns @defaults
- number_to_currency(1, :locale => 'en-US')
+ def test_number_to_currency_translates_currency_formats
+ I18n.expects(:translate).with(:'currency.format', 'en-US').returns @defaults
+ number_to_currency(1, :locale => 'en-US')
+ end
end
end
\ No newline at end of file
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb
index 158ff69e57..53e90f4d53 100644
--- a/activerecord/test/cases/validations_i18n_test.rb
+++ b/activerecord/test/cases/validations_i18n_test.rb
@@ -41,66 +41,307 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
# ActiveRecord::Errors
+ uses_mocha 'ActiveRecord::Errors' do
+ def test_errors_generate_message_translates_custom_model_attribute_key
+ global_scope = [:active_record, :error_messages]
+ custom_scope = global_scope + [:custom, 'topic', :title]
+
+ I18n.expects(:t).with :scope => [:active_record, :error_messages], :default => [:"custom.topic.title.invalid", 'default from class def', :invalid]
+ @topic.errors.generate_message :title, :invalid, :default => 'default from class def'
+ end
+
+ def test_errors_generate_message_translates_custom_model_attribute_keys_with_sti
+ custom_scope = [:active_record, :error_messages, :custom, 'topic', :title]
+
+ I18n.expects(:t).with :scope => [:active_record, :error_messages], :default => [:"custom.reply.title.invalid", :"custom.topic.title.invalid", 'default from class def', :invalid]
+ Reply.new.errors.generate_message :title, :invalid, :default => 'default from class def'
+ end
+
+ def test_errors_add_on_empty_generates_message
+ @topic.errors.expects(:generate_message).with(:title, :empty, {:default => nil})
+ @topic.errors.add_on_empty :title
+ end
+
+ def test_errors_add_on_empty_generates_message_with_custom_default_message
+ @topic.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'})
+ @topic.errors.add_on_empty :title, 'custom'
+ end
+
+ def test_errors_add_on_blank_generates_message
+ @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
+ @topic.errors.add_on_blank :title
+ end
+
+ def test_errors_add_on_blank_generates_message_with_custom_default_message
+ @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
+ @topic.errors.add_on_blank :title, 'custom'
+ end
+
+ def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
+ @topic.errors.instance_variable_set :@errors, { 'title' => 'empty' }
+ I18n.expects(:translate).with(:"active_record.human_attribute_names.topic.title", 'en-US').returns('Title')
+ @topic.errors.full_messages :locale => 'en-US'
+ end
+ end
- def test_errors_generate_message_translates_custom_model_attribute_key
- global_scope = [:active_record, :error_messages]
- custom_scope = global_scope + [:custom, 'topic', :title]
+ # ActiveRecord::Validations
+ uses_mocha 'ActiveRecord::Validations' do
+ # validates_confirmation_of w/ mocha
+
+ def test_validates_confirmation_of_generates_message
+ Topic.validates_confirmation_of :title
+ @topic.title_confirmation = 'foo'
+ @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_confirmation_of_generates_message_with_custom_default_message
+ Topic.validates_confirmation_of :title, :message => 'custom'
+ @topic.title_confirmation = 'foo'
+ @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'})
+ @topic.valid?
+ end
- I18n.expects(:t).with :scope => [:active_record, :error_messages], :default => [:"custom.topic.title.invalid", 'default from class def', :invalid]
- @topic.errors.generate_message :title, :invalid, :default => 'default from class def'
- end
-
- def test_errors_generate_message_translates_custom_model_attribute_keys_with_sti
- custom_scope = [:active_record, :error_messages, :custom, 'topic', :title]
+ # validates_acceptance_of w/ mocha
+
+ def test_validates_acceptance_of_generates_message
+ Topic.validates_acceptance_of :title, :allow_nil => false
+ @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_acceptance_of_generates_message_with_custom_default_message
+ Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false
+ @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'})
+ @topic.valid?
+ end
- I18n.expects(:t).with :scope => [:active_record, :error_messages], :default => [:"custom.reply.title.invalid", :"custom.topic.title.invalid", 'default from class def', :invalid]
- Reply.new.errors.generate_message :title, :invalid, :default => 'default from class def'
- end
-
- def test_errors_add_on_empty_generates_message
- @topic.errors.expects(:generate_message).with(:title, :empty, {:default => nil})
- @topic.errors.add_on_empty :title
- end
-
- def test_errors_add_on_empty_generates_message_with_custom_default_message
- @topic.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'})
- @topic.errors.add_on_empty :title, 'custom'
- end
-
- def test_errors_add_on_blank_generates_message
- @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
- @topic.errors.add_on_blank :title
- end
-
- def test_errors_add_on_blank_generates_message_with_custom_default_message
- @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
- @topic.errors.add_on_blank :title, 'custom'
- end
-
- def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
- @topic.errors.instance_variable_set :@errors, { 'title' => 'empty' }
- I18n.expects(:translate).with(:"active_record.human_attribute_names.topic.title", 'en-US').returns('Title')
- @topic.errors.full_messages :locale => 'en-US'
- end
-
-
- # ActiveRecord::Validations
-
- # validates_confirmation_of
-
- def test_validates_confirmation_of_generates_message
- Topic.validates_confirmation_of :title
- @topic.title_confirmation = 'foo'
- @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil})
- @topic.valid?
+ # validates_presence_of w/ mocha
+
+ def test_validates_presence_of_generates_message
+ Topic.validates_presence_of :title
+ @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_presence_of_generates_message_with_custom_default_message
+ Topic.validates_presence_of :title, :message => 'custom'
+ @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_short
+ Topic.validates_length_of :title, :within => 3..5
+ @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message
+ Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom'
+ @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long
+ Topic.validates_length_of :title, :within => 3..5
+ @topic.title = 'this title is too long'
+ @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message
+ Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom'
+ @topic.title = 'this title is too long'
+ @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'})
+ @topic.valid?
+ end
+
+ # validates_length_of :within w/ mocha
+
+ def test_validates_length_of_within_generates_message_with_title_too_short
+ Topic.validates_length_of :title, :within => 3..5
+ @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message
+ Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom'
+ @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long
+ Topic.validates_length_of :title, :within => 3..5
+ @topic.title = 'this title is too long'
+ @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message
+ Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom'
+ @topic.title = 'this title is too long'
+ @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'})
+ @topic.valid?
+ end
+
+ # validates_length_of :is w/ mocha
+
+ def test_validates_length_of_is_generates_message
+ Topic.validates_length_of :title, :is => 5
+ @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_length_of_is_generates_message_with_custom_default_message
+ Topic.validates_length_of :title, :is => 5, :message => 'custom'
+ @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'})
+ @topic.valid?
+ end
+
+ # validates_uniqueness_of w/ mocha
+
+ def test_validates_uniqueness_of_generates_message
+ Topic.validates_uniqueness_of :title
+ @topic.title = unique_topic.title
+ @topic.errors.expects(:generate_message).with(:title, :taken, {:default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_uniqueness_of_generates_message_with_custom_default_message
+ Topic.validates_uniqueness_of :title, :message => 'custom'
+ @topic.title = unique_topic.title
+ @topic.errors.expects(:generate_message).with(:title, :taken, {:default => 'custom'})
+ @topic.valid?
+ end
+
+ # validates_format_of w/ mocha
+
+ def test_validates_format_of_generates_message
+ Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
+ @topic.title = '72x'
+ @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_format_of_generates_message_with_custom_default_message
+ Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom'
+ @topic.title = '72x'
+ @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'})
+ @topic.valid?
+ end
+
+ # validates_inclusion_of w/ mocha
+
+ def test_validates_inclusion_of_generates_message
+ Topic.validates_inclusion_of :title, :in => %w(a b c)
+ @topic.title = 'z'
+ @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_inclusion_of_generates_message_with_custom_default_message
+ Topic.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom'
+ @topic.title = 'z'
+ @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'})
+ @topic.valid?
+ end
+
+ # validates_exclusion_of w/ mocha
+
+ def test_validates_exclusion_of_generates_message
+ Topic.validates_exclusion_of :title, :in => %w(a b c)
+ @topic.title = 'a'
+ @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_exclusion_of_generates_message_with_custom_default_message
+ Topic.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom'
+ @topic.title = 'a'
+ @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'})
+ @topic.valid?
+ end
+
+ # validates_numericality_of without :only_integer w/ mocha
+
+ def test_validates_numericality_of_generates_message
+ Topic.validates_numericality_of :title
+ @topic.title = 'a'
+ @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_numericality_of_generates_message_with_custom_default_message
+ Topic.validates_numericality_of :title, :message => 'custom'
+ @topic.title = 'a'
+ @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
+ @topic.valid?
+ end
+
+ # validates_numericality_of with :only_integer w/ mocha
+
+ def test_validates_numericality_of_only_integer_generates_message
+ Topic.validates_numericality_of :title, :only_integer => true
+ @topic.title = 'a'
+ @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message
+ Topic.validates_numericality_of :title, :only_integer => true, :message => 'custom'
+ @topic.title = 'a'
+ @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
+ @topic.valid?
+ end
+
+ # validates_numericality_of :odd w/ mocha
+
+ def test_validates_numericality_of_odd_generates_message
+ Topic.validates_numericality_of :title, :only_integer => true, :odd => true
+ @topic.title = 0
+ @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_numericality_of_odd_generates_message_with_custom_default_message
+ Topic.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom'
+ @topic.title = 0
+ @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'})
+ @topic.valid?
+ end
+
+ # validates_numericality_of :less_than w/ mocha
+
+ def test_validates_numericality_of_less_than_generates_message
+ Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
+ @topic.title = 1
+ @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil})
+ @topic.valid?
+ end
+
+ def test_validates_numericality_of_odd_generates_message_with_custom_default_message
+ Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom'
+ @topic.title = 1
+ @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'})
+ @topic.valid?
+ end
+
+ # validates_associated w/ mocha
+
+ def test_validates_associated_generates_message
+ Topic.validates_associated :replies
+ replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
+ replied_topic.valid?
+ end
+
+ def test_validates_associated_generates_message_with_custom_default_message
+ Topic.validates_associated :replies
+ replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
+ replied_topic.valid?
+ end
end
- def test_validates_confirmation_of_generates_message_with_custom_default_message
- Topic.validates_confirmation_of :title, :message => 'custom'
- @topic.title_confirmation = 'foo'
- @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'})
- @topic.valid?
- end
+ # validates_confirmation_of w/o mocha
def test_validates_confirmation_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:confirmation => 'custom message'}}}}}
@@ -121,20 +362,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
assert_equal 'global message', @topic.errors.on(:title)
end
-
- # validates_acceptance_of
-
- def test_validates_acceptance_of_generates_message
- Topic.validates_acceptance_of :title, :allow_nil => false
- @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => nil})
- @topic.valid?
- end
-
- def test_validates_acceptance_of_generates_message_with_custom_default_message
- Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false
- @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'})
- @topic.valid?
- end
+ # validates_acceptance_of w/o mocha
def test_validates_acceptance_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:accepted => 'custom message'}}}}}
@@ -153,21 +381,8 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
assert_equal 'global message', @topic.errors.on(:title)
end
-
- # validates_presence_of
-
- def test_validates_presence_of_generates_message
- Topic.validates_presence_of :title
- @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
- @topic.valid?
- end
-
- def test_validates_presence_of_generates_message_with_custom_default_message
- Topic.validates_presence_of :title, :message => 'custom'
- @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
- @topic.valid?
- end
-
+ # validates_presence_of w/o mocha
+
def test_validates_presence_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:blank => 'custom message'}}}}}
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:blank => 'global message'}}
@@ -185,34 +400,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
assert_equal 'global message', @topic.errors.on(:title)
end
-
- # validates_length_of :within
-
- def test_validates_length_of_within_generates_message_with_title_too_short
- Topic.validates_length_of :title, :within => 3..5
- @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
- @topic.valid?
- end
-
- def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message
- Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom'
- @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
- @topic.valid?
- end
-
- def test_validates_length_of_within_generates_message_with_title_too_long
- Topic.validates_length_of :title, :within => 3..5
- @topic.title = 'this title is too long'
- @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil})
- @topic.valid?
- end
-
- def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message
- Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom'
- @topic.title = 'this title is too long'
- @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'})
- @topic.valid?
- end
+ # validates_length_of :within w/o mocha
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:too_short => 'custom message'}}}}}
@@ -231,20 +419,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
assert_equal 'global message', @topic.errors.on(:title)
end
-
- # validates_length_of :is
-
- def test_validates_length_of_is_generates_message
- Topic.validates_length_of :title, :is => 5
- @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil})
- @topic.valid?
- end
-
- def test_validates_length_of_is_generates_message_with_custom_default_message
- Topic.validates_length_of :title, :is => 5, :message => 'custom'
- @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'})
- @topic.valid?
- end
+ # validates_length_of :is w/o mocha
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
@@ -263,22 +438,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
assert_equal 'global message', @topic.errors.on(:title)
end
-
- # validates_uniqueness_of
-
- def test_validates_uniqueness_of_generates_message
- Topic.validates_uniqueness_of :title
- @topic.title = unique_topic.title
- @topic.errors.expects(:generate_message).with(:title, :taken, {:default => nil})
- @topic.valid?
- end
-
- def test_validates_uniqueness_of_generates_message_with_custom_default_message
- Topic.validates_uniqueness_of :title, :message => 'custom'
- @topic.title = unique_topic.title
- @topic.errors.expects(:generate_message).with(:title, :taken, {:default => 'custom'})
- @topic.valid?
- end
+ # validates_uniqueness_of w/o mocha
def test_validates_length_of_within_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:wrong_length => 'custom message'}}}}}
@@ -298,21 +458,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
- # validates_format_of
-
- def test_validates_format_of_generates_message
- Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/
- @topic.title = '72x'
- @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil})
- @topic.valid?
- end
-
- def test_validates_format_of_generates_message_with_custom_default_message
- Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom'
- @topic.title = '72x'
- @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'})
- @topic.valid?
- end
+ # validates_format_of w/o mocha
def test_validates_format_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:invalid => 'custom message'}}}}}
@@ -331,22 +477,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
assert_equal 'global message', @topic.errors.on(:title)
end
-
- # validates_inclusion_of
-
- def test_validates_inclusion_of_generates_message
- Topic.validates_inclusion_of :title, :in => %w(a b c)
- @topic.title = 'z'
- @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil})
- @topic.valid?
- end
-
- def test_validates_inclusion_of_generates_message_with_custom_default_message
- Topic.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom'
- @topic.title = 'z'
- @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'})
- @topic.valid?
- end
+ # validates_inclusion_of w/o mocha
def test_validates_inclusion_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:inclusion => 'custom message'}}}}}
@@ -365,22 +496,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
assert_equal 'global message', @topic.errors.on(:title)
end
-
- # validates_exclusion_of
-
- def test_validates_exclusion_of_generates_message
- Topic.validates_exclusion_of :title, :in => %w(a b c)
- @topic.title = 'a'
- @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil})
- @topic.valid?
- end
-
- def test_validates_exclusion_of_generates_message_with_custom_default_message
- Topic.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom'
- @topic.title = 'a'
- @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'})
- @topic.valid?
- end
+ # validates_exclusion_of w/o mocha
def test_validates_exclusion_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:exclusion => 'custom message'}}}}}
@@ -401,22 +517,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
assert_equal 'global message', @topic.errors.on(:title)
end
-
- # validates_numericality_of without :only_integer
-
- def test_validates_numericality_of_generates_message
- Topic.validates_numericality_of :title
- @topic.title = 'a'
- @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
- @topic.valid?
- end
-
- def test_validates_numericality_of_generates_message_with_custom_default_message
- Topic.validates_numericality_of :title, :message => 'custom'
- @topic.title = 'a'
- @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
- @topic.valid?
- end
+ # validates_numericality_of without :only_integer w/o mocha
def test_validates_numericality_of_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:not_a_number => 'custom message'}}}}}
@@ -437,22 +538,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
assert_equal 'global message', @topic.errors.on(:title)
end
-
- # validates_numericality_of with :only_integer
-
- def test_validates_numericality_of_only_integer_generates_message
- Topic.validates_numericality_of :title, :only_integer => true
- @topic.title = 'a'
- @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
- @topic.valid?
- end
-
- def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message
- Topic.validates_numericality_of :title, :only_integer => true, :message => 'custom'
- @topic.title = 'a'
- @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
- @topic.valid?
- end
+ # validates_numericality_of with :only_integer w/o mocha
def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:not_a_number => 'custom message'}}}}}
@@ -473,22 +559,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
assert_equal 'global message', @topic.errors.on(:title)
end
-
- # validates_numericality_of :odd
-
- def test_validates_numericality_of_odd_generates_message
- Topic.validates_numericality_of :title, :only_integer => true, :odd => true
- @topic.title = 0
- @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil})
- @topic.valid?
- end
-
- def test_validates_numericality_of_odd_generates_message_with_custom_default_message
- Topic.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom'
- @topic.title = 0
- @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'})
- @topic.valid?
- end
+ # validates_numericality_of :odd w/o mocha
def test_validates_numericality_of_odd_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:odd => 'custom message'}}}}}
@@ -509,22 +580,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
assert_equal 'global message', @topic.errors.on(:title)
end
-
- # validates_numericality_of :less_than
-
- def test_validates_numericality_of_less_than_generates_message
- Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0
- @topic.title = 1
- @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil})
- @topic.valid?
- end
-
- def test_validates_numericality_of_odd_generates_message_with_custom_default_message
- Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom'
- @topic.title = 1
- @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'})
- @topic.valid?
- end
+ # validates_numericality_of :less_than w/o mocha
def test_validates_numericality_of_less_than_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:title => {:less_than => 'custom message'}}}}}
@@ -546,19 +602,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
end
- # validates_associated
-
- def test_validates_associated_generates_message
- Topic.validates_associated :replies
- replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
- replied_topic.valid?
- end
-
- def test_validates_associated_generates_message_with_custom_default_message
- Topic.validates_associated :replies
- replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
- replied_topic.valid?
- end
+ # validates_associated w/o mocha
def test_validates_associated_finds_custom_model_key_translation
I18n.backend.store_translations 'en-US', :active_record => {:error_messages => {:custom => {:topic => {:replies => {:invalid => 'custom message'}}}}}
--
cgit v1.2.3
From 6982acb0793fb6e59f52cab4062344a88e3691ce Mon Sep 17 00:00:00 2001
From: Luca Guidi
Date: Tue, 1 Jul 2008 16:52:48 +0200
Subject: Experimental I18n charset support for ActionMailer
---
actionmailer/lib/action_mailer.rb | 4 +
actionmailer/lib/action_mailer/base.rb | 14 ++-
actionmailer/lib/action_mailer/locale/en-US.rb | 3 +
actionmailer/test/i18n_test.rb | 133 +++++++++++++++++++++++++
4 files changed, 149 insertions(+), 5 deletions(-)
create mode 100644 actionmailer/lib/action_mailer/locale/en-US.rb
create mode 100644 actionmailer/test/i18n_test.rb
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 2e324d4637..806edf1f9e 100755
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -49,4 +49,8 @@ ActionMailer::Base.class_eval do
helper MailHelper
end
+I18n.backend.populate do
+ require 'action_mailer/locale/en-US.rb'
+end
+
silence_warnings { TMail::Encoder.const_set("MAX_LINE_LEN", 200) }
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 1518e23dfe..e787c1b8da 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -307,10 +307,6 @@ module ActionMailer #:nodoc:
# Specify the CC addresses for the message.
adv_attr_accessor :cc
- # Specify the charset to use for the message. This defaults to the
- # +default_charset+ specified for ActionMailer::Base.
- adv_attr_accessor :charset
-
# Specify the content type for the message. This defaults to text/plain
# in most cases, but can be automatically set in some situations.
adv_attr_accessor :content_type
@@ -348,6 +344,15 @@ module ActionMailer #:nodoc:
# have multiple mailer methods share the same template.
adv_attr_accessor :template
+ # Specify the charset to use for the message.
+ # It performs a lookup, on the specified charset, then on the charset from
+ # the current locale, and, finally, on the +default_charset+ specified
+ # for ActionMailer::Base.
+ def charset(charset = nil)
+ @charset ||= charset || :'charset'.t || @@default_charset
+ end
+ attr_writer :charset
+
# Override the mailer name, which defaults to an inflected version of the
# mailer's class name. If you want to use a template in a non-standard
# location, you can use this to specify that location.
@@ -517,7 +522,6 @@ module ActionMailer #:nodoc:
# mailer. Subclasses may override this method to provide different
# defaults.
def initialize_defaults(method_name)
- @charset ||= @@default_charset.dup
@content_type ||= @@default_content_type.dup
@implicit_parts_order ||= @@default_implicit_parts_order.dup
@template ||= method_name
diff --git a/actionmailer/lib/action_mailer/locale/en-US.rb b/actionmailer/lib/action_mailer/locale/en-US.rb
new file mode 100644
index 0000000000..369f2d1a1c
--- /dev/null
+++ b/actionmailer/lib/action_mailer/locale/en-US.rb
@@ -0,0 +1,3 @@
+I18n.backend.store_translations :'en-US', {
+ :charset => 'utf-8'
+}
\ No newline at end of file
diff --git a/actionmailer/test/i18n_test.rb b/actionmailer/test/i18n_test.rb
new file mode 100644
index 0000000000..92b128bce6
--- /dev/null
+++ b/actionmailer/test/i18n_test.rb
@@ -0,0 +1,133 @@
+require 'abstract_unit'
+
+class I18nMailer < ActionMailer::Base
+ def use_locale_charset(recipient)
+ recipients recipient
+ subject "using locale charset"
+ from "tester@example.com"
+ body "x"
+ end
+
+ def use_explicit_charset(recipient)
+ recipients recipient
+ subject "using explicit charset"
+ from "tester@example.com"
+ body "x"
+ charset "iso-8859-2"
+ end
+
+ def multiparted(recipient)
+ recipients recipient
+ subject "Multiparted"
+ from "tester@example.com"
+ body "x"
+
+ part "text/html" do |p|
+ p.body = "multiparted iso-8859-1 html "
+ end
+
+ part :content_type => "text/plain",
+ :body => "multiparted utf-8 text",
+ :charset => 'utf-8'
+ end
+
+ def rxml_template(recipient)
+ recipients recipient
+ subject "rendering rxml template"
+ from "tester@example.com"
+ end
+
+ def initialize_defaults(method_name)
+ super
+ mailer_name "test_mailer"
+ end
+end
+
+I18n.backend.store_translations :'en-GB', { }
+I18n.backend.store_translations :'de-DE', {
+ :charset => 'iso-8859-1'
+}
+
+class I18nTest < Test::Unit::TestCase
+ def setup
+ @charset = 'utf-8'
+ @recipient = 'test@localhost'
+ end
+
+ def test_should_use_locale_charset
+ assert_equal @charset, mail.charset
+ end
+
+ def test_should_use_default_charset_if_no_current_locale
+ uses_locale nil do
+ assert_equal @charset, mail.charset
+ end
+ end
+
+ def test_mail_headers_should_contains_current_charset
+ uses_locale 'de-DE' do
+ assert_match /iso-8859-1/, mail.header['content-type'].body
+ end
+ end
+
+ def test_should_use_charset_from_current_locale
+ uses_locale 'de-DE' do
+ assert_equal 'iso-8859-1', mail.charset
+ end
+ end
+
+ def test_should_raise_exception_if_current_locale_doesnt_specify_a_charset
+ assert_raise I18n::MissingTranslationData do
+ uses_locale 'en-GB' do
+ mail
+ end
+ end
+ end
+
+ def test_should_use_explicit_charset
+ assert_equal 'iso-8859-2', mail('use_explicit_charset').charset
+ end
+
+ def test_mail_parts_charsets
+ uses_locale 'de-DE' do
+ charsets = mail('multiparted').parts.map(&:charset)
+ assert_equal 'iso-8859-1', charsets[0]
+ assert_equal 'iso-8859-1', charsets[1]
+ assert_equal 'utf-8', charsets[2]
+ end
+ end
+
+ def test_mail_parts_headers
+ uses_locale 'de-DE' do
+ content_types = mail('multiparted').parts.map(&:header).map do |header|
+ header['content-type'].body
+ end
+ assert_match /iso-8859-1/, content_types[0]
+ assert_match /iso-8859-1/, content_types[1]
+ assert_match /utf-8/, content_types[2]
+ end
+ end
+
+ # TODO: this case depends on XML Builder,
+ # should we pass Builder::XmlMarkup.new :encoding => charset_from_i18n ?
+ def _ignore_test_rxml_template_should_use_current_charset
+ uses_locale 'de-DE' do
+ assert_equal "\n ",
+ mail('rxml_template').body.strip
+ end
+ end
+
+ private
+ def mail(method = 'use_locale_charset')
+ I18nMailer.__send__('create_' + method, @recipient)
+ end
+
+ def uses_locale(locale, &block)
+ begin
+ I18n.locale = locale
+ yield
+ ensure
+ I18n.locale = I18n.default_locale
+ end
+ end
+end
--
cgit v1.2.3
From 7403c825a05af320e20f1b7e20b0c565081ede89 Mon Sep 17 00:00:00 2001
From: Luca Guidi
Date: Wed, 2 Jul 2008 17:51:34 +0200
Subject: Fixed Date and Time localization for ActiveSupport
---
actionpack/lib/action_view/locale/en-US.rb | 21 -------
activesupport/lib/active_support/locale/en-US.rb | 21 +++++++
activesupport/test/core_ext/i18n_test.rb | 75 ++++++++++++++++++++++++
3 files changed, 96 insertions(+), 21 deletions(-)
create mode 100644 activesupport/test/core_ext/i18n_test.rb
diff --git a/actionpack/lib/action_view/locale/en-US.rb b/actionpack/lib/action_view/locale/en-US.rb
index 20d668a9e1..3adb199681 100644
--- a/actionpack/lib/action_view/locale/en-US.rb
+++ b/actionpack/lib/action_view/locale/en-US.rb
@@ -1,25 +1,4 @@
I18n.backend.store_translations :'en-US', {
- :date => {
- :formats => {
- :default => "%Y-%m-%d",
- :short => "%b %d",
- :long => "%B %d, %Y",
- },
- :day_names => Date::DAYNAMES,
- :abbr_day_names => Date::ABBR_DAYNAMES,
- :month_names => Date::MONTHNAMES,
- :abbr_month_names => Date::ABBR_MONTHNAMES,
- :order => [:year, :month, :day]
- },
- :time => {
- :formats => {
- :default => "%a, %d %b %Y %H:%M:%S %z",
- :short => "%d %b %H:%M",
- :long => "%B %d, %Y %H:%M",
- },
- :am => 'am',
- :pm => 'pm'
- },
:datetime => {
:distance_in_words => {
:half_a_minute => 'half a minute',
diff --git a/activesupport/lib/active_support/locale/en-US.rb b/activesupport/lib/active_support/locale/en-US.rb
index aa06fe14bd..51324a90bf 100644
--- a/activesupport/lib/active_support/locale/en-US.rb
+++ b/activesupport/lib/active_support/locale/en-US.rb
@@ -3,5 +3,26 @@ I18n.backend.store_translations :'en-US', {
:array => {
:sentence_connector => 'and'
}
+ },
+ :date => {
+ :formats => {
+ :default => "%Y-%m-%d",
+ :short => "%b %d",
+ :long => "%B %d, %Y",
+ },
+ :day_names => Date::DAYNAMES,
+ :abbr_day_names => Date::ABBR_DAYNAMES,
+ :month_names => Date::MONTHNAMES,
+ :abbr_month_names => Date::ABBR_MONTHNAMES,
+ :order => [:year, :month, :day]
+ },
+ :time => {
+ :formats => {
+ :default => "%a, %d %b %Y %H:%M:%S %z",
+ :short => "%d %b %H:%M",
+ :long => "%B %d, %Y %H:%M",
+ },
+ :am => 'am',
+ :pm => 'pm'
}
}
\ No newline at end of file
diff --git a/activesupport/test/core_ext/i18n_test.rb b/activesupport/test/core_ext/i18n_test.rb
new file mode 100644
index 0000000000..a67b6a5d8c
--- /dev/null
+++ b/activesupport/test/core_ext/i18n_test.rb
@@ -0,0 +1,75 @@
+require 'abstract_unit'
+
+class I18nTest < Test::Unit::TestCase
+ def setup
+ @date = Date.parse("2008-7-2")
+ @time = Time.utc(2008, 7, 2, 16, 47, 1)
+ end
+
+ uses_mocha 'I18nTimeZoneTest' do
+ def test_time_zone_localization_with_default_format
+ Time.zone.stubs(:now).returns Time.local(2000)
+ assert_equal "Sat, 01 Jan 2000 00:00:00 +0100", Time.zone.now.l
+ end
+ end
+
+ def test_date_localization_should_use_default_format
+ assert_equal "2008-07-02", @date.l
+ end
+
+ def test_date_localization_with_default_format
+ assert_equal "2008-07-02", @date.l(nil, :default)
+ end
+
+ def test_date_localization_with_short_format
+ assert_equal "Jul 02", @date.l(nil, :short)
+ end
+
+ def test_date_localization_with_long_format
+ assert_equal "July 02, 2008", @date.l(nil, :long)
+ end
+
+ def test_time_localization_should_use_default_format
+ assert_equal "Wed, 02 Jul 2008 16:47:01 +0100", @time.l
+ end
+
+ def test_time_localization_with_default_format
+ assert_equal "Wed, 02 Jul 2008 16:47:01 +0100", @time.l(nil, :default)
+ end
+
+ def test_time_localization_with_short_format
+ assert_equal "02 Jul 16:47", @time.l(nil, :short)
+ end
+
+ def test_time_localization_with_long_format
+ assert_equal "July 02, 2008 16:47", @time.l(nil, :long)
+ end
+
+ def test_day_names
+ assert_equal Date::DAYNAMES, :'date.day_names'.t
+ end
+
+ def test_abbr_day_names
+ assert_equal Date::ABBR_DAYNAMES, :'date.abbr_day_names'.t
+ end
+
+ def test_month_names
+ assert_equal Date::MONTHNAMES, :'date.month_names'.t
+ end
+
+ def test_abbr_month_names
+ assert_equal Date::ABBR_MONTHNAMES, :'date.abbr_month_names'.t
+ end
+
+ def test_date_order
+ assert_equal [:year, :month, :day], :'date.order'.t
+ end
+
+ def test_time_am
+ assert_equal 'am', :'time.am'.t
+ end
+
+ def test_time_pm
+ assert_equal 'pm', :'time.pm'.t
+ end
+end
--
cgit v1.2.3
From 8f74ba96c47e77e18ce363c8e7cd2fc9196faf7a Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Wed, 2 Jul 2008 19:21:07 +0200
Subject: remove core extensions in favor of I18n#translate and I18n#localize
---
actionmailer/lib/action_mailer/base.rb | 2 +-
actionpack/lib/action_view/helpers/date_helper.rb | 5 +-
.../lib/action_view/helpers/number_helper.rb | 2 +-
activerecord/lib/active_record/validations.rb | 6 +-
activerecord/test/cases/validations_i18n_test.rb | 2 +-
.../active_support/core_ext/array/conversions.rb | 2 +-
activesupport/lib/active_support/vendor/i18n-0.0.1 | 2 +-
activesupport/test/core_ext/i18n_test.rb | 75 ----------------------
activesupport/test/i18n_test.rb | 75 ++++++++++++++++++++++
9 files changed, 86 insertions(+), 85 deletions(-)
delete mode 100644 activesupport/test/core_ext/i18n_test.rb
create mode 100644 activesupport/test/i18n_test.rb
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index e787c1b8da..f7da90d10f 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -349,7 +349,7 @@ module ActionMailer #:nodoc:
# the current locale, and, finally, on the +default_charset+ specified
# for ActionMailer::Base.
def charset(charset = nil)
- @charset ||= charset || :'charset'.t || @@default_charset
+ @charset ||= charset || I18n.translate(:charset) || @@default_charset
end
attr_writer :charset
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 6ac4171fd5..d306c7a742 100755
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -512,7 +512,8 @@ module ActionView
else
month_options = []
month_names = options[:use_month_names] || begin
- (options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names').t locale
+ key = options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
+ I18n.translate key, locale
end
month_names.unshift(nil) if month_names.size < 13
@@ -632,7 +633,7 @@ module ActionView
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
- order = options[:order] ||= :'date.order'.t(locale)
+ order = options[:order] ||= I18n.translate(:'date.order', locale)
# Discard explicit and implicit by not being included in the :order
discard = {}
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 3e0d5b1db4..981589437d 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -70,7 +70,7 @@ module ActionView
# # => 1234567890,50 £
def number_to_currency(number, options = {})
options = options.symbolize_keys
- defaults = :'currency.format'.t(options[:locale]) || {}
+ defaults = I18n.translate(:'currency.format', options[:locale]) || {}
precision = options[:precision] || defaults[:precision]
unit = options[:unit] || defaults[:unit]
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 5bbd10394c..5245f65869 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -22,7 +22,7 @@ module ActiveRecord
class << self
def default_error_messages
ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use 'active_record.error_messages'.t.")
- 'active_record.error_messages'.t
+ I18n.translate 'active_record.error_messages'
end
end
@@ -43,7 +43,7 @@ module ActiveRecord
# error can be added to the same +attribute+ in which case an array will be returned on a call to on(attribute) .
# If no +msg+ is supplied, "invalid" is assumed.
def add(attribute, message = nil)
- message ||= :"active_record.error_messages.invalid".t
+ message ||= I18n.translate :"active_record.error_messages.invalid"
@errors[attribute.to_s] ||= []
@errors[attribute.to_s] << message
end
@@ -168,7 +168,7 @@ module ActiveRecord
full_messages << message
else
key = :"active_record.human_attribute_names.#{@base.class.name.underscore.to_sym}.#{attr}"
- attr_name = key.t(locale) || @base.class.human_attribute_name(attr)
+ attr_name = I18n.translate(key, locale, :raise => true) rescue @base.class.human_attribute_name(attr)
full_messages << attr_name + " " + message
end
end
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb
index 53e90f4d53..840fcd81d0 100644
--- a/activerecord/test/cases/validations_i18n_test.rb
+++ b/activerecord/test/cases/validations_i18n_test.rb
@@ -79,7 +79,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
@topic.errors.instance_variable_set :@errors, { 'title' => 'empty' }
- I18n.expects(:translate).with(:"active_record.human_attribute_names.topic.title", 'en-US').returns('Title')
+ I18n.expects(:translate).with(:"active_record.human_attribute_names.topic.title", 'en-US', :raise => true).returns('Title')
@topic.errors.full_messages :locale => 'en-US'
end
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 80d91a6cbd..80bf1404de 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -13,7 +13,7 @@ module ActiveSupport #:nodoc:
locale = options[:locale]
locale ||= self.locale if respond_to?(:locale)
- default = :'support.array.sentence_connector'.t(locale)
+ default = I18n.translate(:'support.array.sentence_connector', locale)
options.reverse_merge! :connector => default, :skip_last_comma => false
options[:connector] = "#{options[:connector]} " unless options[:connector].nil? || options[:connector].strip == ''
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1 b/activesupport/lib/active_support/vendor/i18n-0.0.1
index 20c331666b..970bc7ab5f 160000
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1
+++ b/activesupport/lib/active_support/vendor/i18n-0.0.1
@@ -1 +1 @@
-Subproject commit 20c331666b3b6a21791d4cded53c3d8654fba714
+Subproject commit 970bc7ab5faa94e41ee4a56bc8913c144c1cdd19
diff --git a/activesupport/test/core_ext/i18n_test.rb b/activesupport/test/core_ext/i18n_test.rb
deleted file mode 100644
index a67b6a5d8c..0000000000
--- a/activesupport/test/core_ext/i18n_test.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'abstract_unit'
-
-class I18nTest < Test::Unit::TestCase
- def setup
- @date = Date.parse("2008-7-2")
- @time = Time.utc(2008, 7, 2, 16, 47, 1)
- end
-
- uses_mocha 'I18nTimeZoneTest' do
- def test_time_zone_localization_with_default_format
- Time.zone.stubs(:now).returns Time.local(2000)
- assert_equal "Sat, 01 Jan 2000 00:00:00 +0100", Time.zone.now.l
- end
- end
-
- def test_date_localization_should_use_default_format
- assert_equal "2008-07-02", @date.l
- end
-
- def test_date_localization_with_default_format
- assert_equal "2008-07-02", @date.l(nil, :default)
- end
-
- def test_date_localization_with_short_format
- assert_equal "Jul 02", @date.l(nil, :short)
- end
-
- def test_date_localization_with_long_format
- assert_equal "July 02, 2008", @date.l(nil, :long)
- end
-
- def test_time_localization_should_use_default_format
- assert_equal "Wed, 02 Jul 2008 16:47:01 +0100", @time.l
- end
-
- def test_time_localization_with_default_format
- assert_equal "Wed, 02 Jul 2008 16:47:01 +0100", @time.l(nil, :default)
- end
-
- def test_time_localization_with_short_format
- assert_equal "02 Jul 16:47", @time.l(nil, :short)
- end
-
- def test_time_localization_with_long_format
- assert_equal "July 02, 2008 16:47", @time.l(nil, :long)
- end
-
- def test_day_names
- assert_equal Date::DAYNAMES, :'date.day_names'.t
- end
-
- def test_abbr_day_names
- assert_equal Date::ABBR_DAYNAMES, :'date.abbr_day_names'.t
- end
-
- def test_month_names
- assert_equal Date::MONTHNAMES, :'date.month_names'.t
- end
-
- def test_abbr_month_names
- assert_equal Date::ABBR_MONTHNAMES, :'date.abbr_month_names'.t
- end
-
- def test_date_order
- assert_equal [:year, :month, :day], :'date.order'.t
- end
-
- def test_time_am
- assert_equal 'am', :'time.am'.t
- end
-
- def test_time_pm
- assert_equal 'pm', :'time.pm'.t
- end
-end
diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb
new file mode 100644
index 0000000000..e6d90f09c1
--- /dev/null
+++ b/activesupport/test/i18n_test.rb
@@ -0,0 +1,75 @@
+require 'abstract_unit'
+
+class I18nTest < Test::Unit::TestCase
+ def setup
+ @date = Date.parse("2008-7-2")
+ @time = Time.utc(2008, 7, 2, 16, 47, 1)
+ end
+
+ uses_mocha 'I18nTimeZoneTest' do
+ def test_time_zone_localization_with_default_format
+ Time.zone.stubs(:now).returns Time.local(2000)
+ assert_equal "Sat, 01 Jan 2000 00:00:00 +0100", I18n.localize(Time.zone.now)
+ end
+ end
+
+ def test_date_localization_should_use_default_format
+ assert_equal "2008-07-02", I18n.localize(@date)
+ end
+
+ def test_date_localization_with_default_format
+ assert_equal "2008-07-02", I18n.localize(@date, nil, :default)
+ end
+
+ def test_date_localization_with_short_format
+ assert_equal "Jul 02", I18n.localize(@date, nil, :short)
+ end
+
+ def test_date_localization_with_long_format
+ assert_equal "July 02, 2008", I18n.localize(@date, nil, :long)
+ end
+
+ def test_time_localization_should_use_default_format
+ assert_equal "Wed, 02 Jul 2008 16:47:01 +0100", I18n.localize(@time)
+ end
+
+ def test_time_localization_with_default_format
+ assert_equal "Wed, 02 Jul 2008 16:47:01 +0100", I18n.localize(@time, nil, :default)
+ end
+
+ def test_time_localization_with_short_format
+ assert_equal "02 Jul 16:47", I18n.localize(@time, nil, :short)
+ end
+
+ def test_time_localization_with_long_format
+ assert_equal "July 02, 2008 16:47", I18n.localize(@time, nil, :long)
+ end
+
+ def test_day_names
+ assert_equal Date::DAYNAMES, I18n.translate(:'date.day_names')
+ end
+
+ def test_abbr_day_names
+ assert_equal Date::ABBR_DAYNAMES, I18n.translate(:'date.abbr_day_names')
+ end
+
+ def test_month_names
+ assert_equal Date::MONTHNAMES, I18n.translate(:'date.month_names')
+ end
+
+ def test_abbr_month_names
+ assert_equal Date::ABBR_MONTHNAMES, I18n.translate(:'date.abbr_month_names')
+ end
+
+ def test_date_order
+ assert_equal [:year, :month, :day], I18n.translate(:'date.order')
+ end
+
+ def test_time_am
+ assert_equal 'am', I18n.translate(:'time.am')
+ end
+
+ def test_time_pm
+ assert_equal 'pm', I18n.translate(:'time.pm')
+ end
+end
--
cgit v1.2.3
From d41e4c1c3d6e6259f1cfc0cdbd4fc30fee0f866a Mon Sep 17 00:00:00 2001
From: Luca Guidi
Date: Thu, 3 Jul 2008 11:50:18 +0200
Subject: Make sure ActionMailer use default charset if no defined by current
locale
---
actionmailer/lib/action_mailer/base.rb | 4 ++--
actionmailer/test/i18n_test.rb | 26 ++++++++++++--------------
2 files changed, 14 insertions(+), 16 deletions(-)
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index f7da90d10f..a1a7f55d2b 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -346,10 +346,10 @@ module ActionMailer #:nodoc:
# Specify the charset to use for the message.
# It performs a lookup, on the specified charset, then on the charset from
- # the current locale, and, finally, on the +default_charset+ specified
+ # the current locale, and, in the end, on the +default_charset+ specified
# for ActionMailer::Base.
def charset(charset = nil)
- @charset ||= charset || I18n.translate(:charset) || @@default_charset
+ @charset ||= charset || I18n.translate(:charset, :default => @@default_charset)
end
attr_writer :charset
diff --git a/actionmailer/test/i18n_test.rb b/actionmailer/test/i18n_test.rb
index 92b128bce6..a775128519 100644
--- a/actionmailer/test/i18n_test.rb
+++ b/actionmailer/test/i18n_test.rb
@@ -59,37 +59,35 @@ class I18nTest < Test::Unit::TestCase
end
def test_should_use_default_charset_if_no_current_locale
- uses_locale nil do
+ with_locale nil do
assert_equal @charset, mail.charset
end
end
def test_mail_headers_should_contains_current_charset
- uses_locale 'de-DE' do
+ with_locale 'de-DE' do
assert_match /iso-8859-1/, mail.header['content-type'].body
end
end
def test_should_use_charset_from_current_locale
- uses_locale 'de-DE' do
+ with_locale 'de-DE' do
assert_equal 'iso-8859-1', mail.charset
end
end
-
- def test_should_raise_exception_if_current_locale_doesnt_specify_a_charset
- assert_raise I18n::MissingTranslationData do
- uses_locale 'en-GB' do
- mail
- end
+
+ def test_should_use_default_charset_if_missing_for_current_locale
+ with_locale 'en-GB' do
+ assert_equal @charset, mail.charset
end
end
-
+
def test_should_use_explicit_charset
assert_equal 'iso-8859-2', mail('use_explicit_charset').charset
end
def test_mail_parts_charsets
- uses_locale 'de-DE' do
+ with_locale 'de-DE' do
charsets = mail('multiparted').parts.map(&:charset)
assert_equal 'iso-8859-1', charsets[0]
assert_equal 'iso-8859-1', charsets[1]
@@ -98,7 +96,7 @@ class I18nTest < Test::Unit::TestCase
end
def test_mail_parts_headers
- uses_locale 'de-DE' do
+ with_locale 'de-DE' do
content_types = mail('multiparted').parts.map(&:header).map do |header|
header['content-type'].body
end
@@ -111,7 +109,7 @@ class I18nTest < Test::Unit::TestCase
# TODO: this case depends on XML Builder,
# should we pass Builder::XmlMarkup.new :encoding => charset_from_i18n ?
def _ignore_test_rxml_template_should_use_current_charset
- uses_locale 'de-DE' do
+ with_locale 'de-DE' do
assert_equal "\n ",
mail('rxml_template').body.strip
end
@@ -122,7 +120,7 @@ class I18nTest < Test::Unit::TestCase
I18nMailer.__send__('create_' + method, @recipient)
end
- def uses_locale(locale, &block)
+ def with_locale(locale, &block)
begin
I18n.locale = locale
yield
--
cgit v1.2.3
From e1a7f83fca862fd7472ef6b80f8b6a8d33849a8e Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Fri, 4 Jul 2008 22:22:20 +0200
Subject: use :default for human_attribute_name
---
activerecord/lib/active_record/validations.rb | 2 +-
activerecord/test/cases/validations_i18n_test.rb | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 5245f65869..8ba09b3992 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -168,7 +168,7 @@ module ActiveRecord
full_messages << message
else
key = :"active_record.human_attribute_names.#{@base.class.name.underscore.to_sym}.#{attr}"
- attr_name = I18n.translate(key, locale, :raise => true) rescue @base.class.human_attribute_name(attr)
+ attr_name = I18n.translate(key, locale, :default => @base.class.human_attribute_name(attr))
full_messages << attr_name + " " + message
end
end
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb
index 840fcd81d0..5be518c547 100644
--- a/activerecord/test/cases/validations_i18n_test.rb
+++ b/activerecord/test/cases/validations_i18n_test.rb
@@ -79,7 +79,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
@topic.errors.instance_variable_set :@errors, { 'title' => 'empty' }
- I18n.expects(:translate).with(:"active_record.human_attribute_names.topic.title", 'en-US', :raise => true).returns('Title')
+ I18n.expects(:translate).with(:"active_record.human_attribute_names.topic.title", 'en-US', :default => 'Title').returns('Title')
@topic.errors.full_messages :locale => 'en-US'
end
end
--
cgit v1.2.3
From 8305d6759abe2b8511ccee35fd0998e6fb0dffd0 Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Fri, 4 Jul 2008 22:26:53 +0200
Subject: Reverting changes to ActionMailer Revert "Make sure ActionMailer use
default charset if no defined by current locale" This reverts commit
d41e4c1c3d6e6259f1cfc0cdbd4fc30fee0f866a.
---
actionmailer/lib/action_mailer/base.rb | 4 ++--
actionmailer/test/i18n_test.rb | 26 ++++++++++++++------------
2 files changed, 16 insertions(+), 14 deletions(-)
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index a1a7f55d2b..f7da90d10f 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -346,10 +346,10 @@ module ActionMailer #:nodoc:
# Specify the charset to use for the message.
# It performs a lookup, on the specified charset, then on the charset from
- # the current locale, and, in the end, on the +default_charset+ specified
+ # the current locale, and, finally, on the +default_charset+ specified
# for ActionMailer::Base.
def charset(charset = nil)
- @charset ||= charset || I18n.translate(:charset, :default => @@default_charset)
+ @charset ||= charset || I18n.translate(:charset) || @@default_charset
end
attr_writer :charset
diff --git a/actionmailer/test/i18n_test.rb b/actionmailer/test/i18n_test.rb
index a775128519..92b128bce6 100644
--- a/actionmailer/test/i18n_test.rb
+++ b/actionmailer/test/i18n_test.rb
@@ -59,35 +59,37 @@ class I18nTest < Test::Unit::TestCase
end
def test_should_use_default_charset_if_no_current_locale
- with_locale nil do
+ uses_locale nil do
assert_equal @charset, mail.charset
end
end
def test_mail_headers_should_contains_current_charset
- with_locale 'de-DE' do
+ uses_locale 'de-DE' do
assert_match /iso-8859-1/, mail.header['content-type'].body
end
end
def test_should_use_charset_from_current_locale
- with_locale 'de-DE' do
+ uses_locale 'de-DE' do
assert_equal 'iso-8859-1', mail.charset
end
end
-
- def test_should_use_default_charset_if_missing_for_current_locale
- with_locale 'en-GB' do
- assert_equal @charset, mail.charset
+
+ def test_should_raise_exception_if_current_locale_doesnt_specify_a_charset
+ assert_raise I18n::MissingTranslationData do
+ uses_locale 'en-GB' do
+ mail
+ end
end
end
-
+
def test_should_use_explicit_charset
assert_equal 'iso-8859-2', mail('use_explicit_charset').charset
end
def test_mail_parts_charsets
- with_locale 'de-DE' do
+ uses_locale 'de-DE' do
charsets = mail('multiparted').parts.map(&:charset)
assert_equal 'iso-8859-1', charsets[0]
assert_equal 'iso-8859-1', charsets[1]
@@ -96,7 +98,7 @@ class I18nTest < Test::Unit::TestCase
end
def test_mail_parts_headers
- with_locale 'de-DE' do
+ uses_locale 'de-DE' do
content_types = mail('multiparted').parts.map(&:header).map do |header|
header['content-type'].body
end
@@ -109,7 +111,7 @@ class I18nTest < Test::Unit::TestCase
# TODO: this case depends on XML Builder,
# should we pass Builder::XmlMarkup.new :encoding => charset_from_i18n ?
def _ignore_test_rxml_template_should_use_current_charset
- with_locale 'de-DE' do
+ uses_locale 'de-DE' do
assert_equal "\n ",
mail('rxml_template').body.strip
end
@@ -120,7 +122,7 @@ class I18nTest < Test::Unit::TestCase
I18nMailer.__send__('create_' + method, @recipient)
end
- def with_locale(locale, &block)
+ def uses_locale(locale, &block)
begin
I18n.locale = locale
yield
--
cgit v1.2.3
From 34bd1e95c7a08c9266fc99281417dad4a19cdf73 Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Fri, 4 Jul 2008 22:34:32 +0200
Subject: Reverting changes to ActionMailer Revert "Experimental I18n charset
support for ActionMailer" This reverts commit
6982acb0793fb6e59f52cab4062344a88e3691ce.
Conflicts:
actionmailer/lib/action_mailer/base.rb
---
actionmailer/lib/action_mailer.rb | 4 -
actionmailer/lib/action_mailer/base.rb | 9 --
actionmailer/lib/action_mailer/locale/en-US.rb | 3 -
actionmailer/test/i18n_test.rb | 133 -------------------------
4 files changed, 149 deletions(-)
delete mode 100644 actionmailer/lib/action_mailer/locale/en-US.rb
delete mode 100644 actionmailer/test/i18n_test.rb
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 806edf1f9e..2e324d4637 100755
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -49,8 +49,4 @@ ActionMailer::Base.class_eval do
helper MailHelper
end
-I18n.backend.populate do
- require 'action_mailer/locale/en-US.rb'
-end
-
silence_warnings { TMail::Encoder.const_set("MAX_LINE_LEN", 200) }
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index f7da90d10f..b1f23583a6 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -344,15 +344,6 @@ module ActionMailer #:nodoc:
# have multiple mailer methods share the same template.
adv_attr_accessor :template
- # Specify the charset to use for the message.
- # It performs a lookup, on the specified charset, then on the charset from
- # the current locale, and, finally, on the +default_charset+ specified
- # for ActionMailer::Base.
- def charset(charset = nil)
- @charset ||= charset || I18n.translate(:charset) || @@default_charset
- end
- attr_writer :charset
-
# Override the mailer name, which defaults to an inflected version of the
# mailer's class name. If you want to use a template in a non-standard
# location, you can use this to specify that location.
diff --git a/actionmailer/lib/action_mailer/locale/en-US.rb b/actionmailer/lib/action_mailer/locale/en-US.rb
deleted file mode 100644
index 369f2d1a1c..0000000000
--- a/actionmailer/lib/action_mailer/locale/en-US.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-I18n.backend.store_translations :'en-US', {
- :charset => 'utf-8'
-}
\ No newline at end of file
diff --git a/actionmailer/test/i18n_test.rb b/actionmailer/test/i18n_test.rb
deleted file mode 100644
index 92b128bce6..0000000000
--- a/actionmailer/test/i18n_test.rb
+++ /dev/null
@@ -1,133 +0,0 @@
-require 'abstract_unit'
-
-class I18nMailer < ActionMailer::Base
- def use_locale_charset(recipient)
- recipients recipient
- subject "using locale charset"
- from "tester@example.com"
- body "x"
- end
-
- def use_explicit_charset(recipient)
- recipients recipient
- subject "using explicit charset"
- from "tester@example.com"
- body "x"
- charset "iso-8859-2"
- end
-
- def multiparted(recipient)
- recipients recipient
- subject "Multiparted"
- from "tester@example.com"
- body "x"
-
- part "text/html" do |p|
- p.body = "multiparted iso-8859-1 html "
- end
-
- part :content_type => "text/plain",
- :body => "multiparted utf-8 text",
- :charset => 'utf-8'
- end
-
- def rxml_template(recipient)
- recipients recipient
- subject "rendering rxml template"
- from "tester@example.com"
- end
-
- def initialize_defaults(method_name)
- super
- mailer_name "test_mailer"
- end
-end
-
-I18n.backend.store_translations :'en-GB', { }
-I18n.backend.store_translations :'de-DE', {
- :charset => 'iso-8859-1'
-}
-
-class I18nTest < Test::Unit::TestCase
- def setup
- @charset = 'utf-8'
- @recipient = 'test@localhost'
- end
-
- def test_should_use_locale_charset
- assert_equal @charset, mail.charset
- end
-
- def test_should_use_default_charset_if_no_current_locale
- uses_locale nil do
- assert_equal @charset, mail.charset
- end
- end
-
- def test_mail_headers_should_contains_current_charset
- uses_locale 'de-DE' do
- assert_match /iso-8859-1/, mail.header['content-type'].body
- end
- end
-
- def test_should_use_charset_from_current_locale
- uses_locale 'de-DE' do
- assert_equal 'iso-8859-1', mail.charset
- end
- end
-
- def test_should_raise_exception_if_current_locale_doesnt_specify_a_charset
- assert_raise I18n::MissingTranslationData do
- uses_locale 'en-GB' do
- mail
- end
- end
- end
-
- def test_should_use_explicit_charset
- assert_equal 'iso-8859-2', mail('use_explicit_charset').charset
- end
-
- def test_mail_parts_charsets
- uses_locale 'de-DE' do
- charsets = mail('multiparted').parts.map(&:charset)
- assert_equal 'iso-8859-1', charsets[0]
- assert_equal 'iso-8859-1', charsets[1]
- assert_equal 'utf-8', charsets[2]
- end
- end
-
- def test_mail_parts_headers
- uses_locale 'de-DE' do
- content_types = mail('multiparted').parts.map(&:header).map do |header|
- header['content-type'].body
- end
- assert_match /iso-8859-1/, content_types[0]
- assert_match /iso-8859-1/, content_types[1]
- assert_match /utf-8/, content_types[2]
- end
- end
-
- # TODO: this case depends on XML Builder,
- # should we pass Builder::XmlMarkup.new :encoding => charset_from_i18n ?
- def _ignore_test_rxml_template_should_use_current_charset
- uses_locale 'de-DE' do
- assert_equal "\n ",
- mail('rxml_template').body.strip
- end
- end
-
- private
- def mail(method = 'use_locale_charset')
- I18nMailer.__send__('create_' + method, @recipient)
- end
-
- def uses_locale(locale, &block)
- begin
- I18n.locale = locale
- yield
- ensure
- I18n.locale = I18n.default_locale
- end
- end
-end
--
cgit v1.2.3
From a865d19516a716762b46ca29212bac668dd7c4a0 Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Fri, 4 Jul 2008 22:45:53 +0200
Subject: reverting changes to ActionMailer
---
actionmailer/lib/action_mailer/base.rb | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index b1f23583a6..1518e23dfe 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -307,6 +307,10 @@ module ActionMailer #:nodoc:
# Specify the CC addresses for the message.
adv_attr_accessor :cc
+ # Specify the charset to use for the message. This defaults to the
+ # +default_charset+ specified for ActionMailer::Base.
+ adv_attr_accessor :charset
+
# Specify the content type for the message. This defaults to text/plain
# in most cases, but can be automatically set in some situations.
adv_attr_accessor :content_type
@@ -513,6 +517,7 @@ module ActionMailer #:nodoc:
# mailer. Subclasses may override this method to provide different
# defaults.
def initialize_defaults(method_name)
+ @charset ||= @@default_charset.dup
@content_type ||= @@default_content_type.dup
@implicit_parts_order ||= @@default_implicit_parts_order.dup
@template ||= method_name
--
cgit v1.2.3
From c9ed2c9bd24b9f4cdfcb692151f87ba900469e71 Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Sun, 6 Jul 2008 19:00:55 +0200
Subject: add a translation helper
---
.../lib/action_view/helpers/translation_helper.rb | 16 +++++++++
.../test/template/translation_helper_test.rb | 42 ++++++++++++++++++++++
2 files changed, 58 insertions(+)
create mode 100644 actionpack/lib/action_view/helpers/translation_helper.rb
create mode 100644 actionpack/test/template/translation_helper_test.rb
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
new file mode 100644
index 0000000000..0bfe6bf771
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -0,0 +1,16 @@
+require 'action_view/helpers/tag_helper'
+
+module ActionView
+ module Helpers
+ module TranslationHelper
+ def translate(*args)
+ key, locale, options = I18n.send :process_translate_arguments, *args
+ I18n.translate key, locale, options.merge(:raise => true)
+
+ rescue I18n::MissingTranslationData => e
+ keys = I18n.send :normalize_translation_keys, locale, key, options[:scope]
+ content_tag('span', keys.join(', '), :class => 'translation_missing')
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb
new file mode 100644
index 0000000000..e97bcdb731
--- /dev/null
+++ b/actionpack/test/template/translation_helper_test.rb
@@ -0,0 +1,42 @@
+require 'abstract_unit'
+
+class TranslationHelperTest < Test::Unit::TestCase
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::TranslationHelper
+
+ attr_reader :request
+ uses_mocha 'translation_helper_test' do
+ def setup
+ end
+
+ def test_delegates_to_i18n_setting_the_raise_option
+ I18n.expects(:translate).with(:foo, 'en-US', :raise => true)
+ translate :foo, 'en-US'
+ end
+
+ def test_returns_missing_translation_message_wrapped_into_span
+ expected = 'en-US, foo '
+ assert_equal expected, translate(:foo)
+ end
+
+ # def test_error_messages_for_given_a_header_message_option_it_does_not_translate_header_message
+ # I18n.expects(:translate).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').never
+ # error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en-US')
+ # end
+ #
+ # def test_error_messages_for_given_no_header_message_option_it_translates_header_message
+ # I18n.expects(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns 'header message'
+ # error_messages_for(:object => @object, :locale => 'en-US')
+ # end
+ #
+ # def test_error_messages_for_given_a_message_option_it_does_not_translate_message
+ # I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).never
+ # error_messages_for(:object => @object, :message => 'message', :locale => 'en-US')
+ # end
+ #
+ # def test_error_messages_for_given_no_message_option_it_translates_message
+ # I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
+ # error_messages_for(:object => @object, :locale => 'en-US')
+ # end
+ end
+end
\ No newline at end of file
--
cgit v1.2.3
From 84816ae981a8598e5e401eb1b9b805de840fefc9 Mon Sep 17 00:00:00 2001
From: Sven Fuchs
Date: Sun, 6 Jul 2008 21:20:02 +0200
Subject: align with changes in i18n
---
actionpack/lib/action_view/helpers/date_helper.rb | 5 ++---
actionpack/lib/action_view/helpers/number_helper.rb | 2 +-
actionpack/lib/action_view/helpers/translation_helper.rb | 6 +++---
actionpack/test/template/date_helper_i18n_test.rb | 8 ++++----
actionpack/test/template/number_helper_i18n_test.rb | 2 +-
activerecord/lib/active_record/validations.rb | 5 ++---
activerecord/test/cases/validations_i18n_test.rb | 6 +++---
.../lib/active_support/core_ext/array/conversions.rb | 5 +----
activesupport/lib/active_support/vendor/i18n-0.0.1 | 2 +-
activesupport/test/i18n_test.rb | 12 ++++++------
10 files changed, 24 insertions(+), 29 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index d306c7a742..61fff42d5a 100755
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -504,7 +504,6 @@ module ActionView
#
def select_month(date, options = {}, html_options = {})
locale = options[:locale]
- locale ||= self.locale if respond_to?(:locale)
val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
if options[:use_hidden]
@@ -513,7 +512,7 @@ module ActionView
month_options = []
month_names = options[:use_month_names] || begin
key = options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
- I18n.translate key, locale
+ I18n.translate key, :locale => locale
end
month_names.unshift(nil) if month_names.size < 13
@@ -633,7 +632,7 @@ module ActionView
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
- order = options[:order] ||= I18n.translate(:'date.order', locale)
+ order = options[:order] ||= I18n.translate(:'date.order', :locale => locale)
# Discard explicit and implicit by not being included in the :order
discard = {}
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 981589437d..6bb8263794 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -70,7 +70,7 @@ module ActionView
# # => 1234567890,50 £
def number_to_currency(number, options = {})
options = options.symbolize_keys
- defaults = I18n.translate(:'currency.format', options[:locale]) || {}
+ defaults = I18n.translate(:'currency.format', :locale => options[:locale]) || {}
precision = options[:precision] || defaults[:precision]
unit = options[:unit] || defaults[:unit]
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index 0bfe6bf771..c13c2dfc04 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -4,11 +4,11 @@ module ActionView
module Helpers
module TranslationHelper
def translate(*args)
- key, locale, options = I18n.send :process_translate_arguments, *args
- I18n.translate key, locale, options.merge(:raise => true)
+ args << args.extract_options!.merge(:raise => true)
+ I18n.translate *args
rescue I18n::MissingTranslationData => e
- keys = I18n.send :normalize_translation_keys, locale, key, options[:scope]
+ keys = I18n.send :normalize_translation_keys, e.locale, e.key, e.options[:scope]
content_tag('span', keys.join(', '), :class => 'translation_missing')
end
end
diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb
index aeb06c55ea..aca3593921 100644
--- a/actionpack/test/template/date_helper_i18n_test.rb
+++ b/actionpack/test/template/date_helper_i18n_test.rb
@@ -56,7 +56,7 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
uses_mocha 'date_helper_select_tags_i18n_tests' do
def setup
- I18n.stubs(:translate).with(:'date.month_names', 'en-US').returns Date::MONTHNAMES
+ I18n.stubs(:translate).with(:'date.month_names', :locale => 'en-US').returns Date::MONTHNAMES
end
# select_month
@@ -67,12 +67,12 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
end
def test_select_month_translates_monthnames
- I18n.expects(:translate).with(:'date.month_names', 'en-US').returns Date::MONTHNAMES
+ I18n.expects(:translate).with(:'date.month_names', :locale => 'en-US').returns Date::MONTHNAMES
select_month(8, :locale => 'en-US')
end
def test_select_month_given_use_short_month_option_translates_abbr_monthnames
- I18n.expects(:translate).with(:'date.abbr_month_names', 'en-US').returns Date::ABBR_MONTHNAMES
+ I18n.expects(:translate).with(:'date.abbr_month_names', :locale => 'en-US').returns Date::ABBR_MONTHNAMES
select_month(8, :locale => 'en-US', :use_short_month => true)
end
@@ -84,7 +84,7 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
end
def test_date_or_time_select_given_no_order_options_translates_order
- I18n.expects(:translate).with(:'date.order', 'en-US').returns [:year, :month, :day]
+ I18n.expects(:translate).with(:'date.order', :locale => 'en-US').returns [:year, :month, :day]
datetime_select('post', 'updated_at', :locale => 'en-US')
end
end
diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb
index be40ddbc88..50c20c3627 100644
--- a/actionpack/test/template/number_helper_i18n_test.rb
+++ b/actionpack/test/template/number_helper_i18n_test.rb
@@ -11,7 +11,7 @@ class NumberHelperI18nTests < Test::Unit::TestCase
end
def test_number_to_currency_translates_currency_formats
- I18n.expects(:translate).with(:'currency.format', 'en-US').returns @defaults
+ I18n.expects(:translate).with(:'currency.format', :locale => 'en-US').returns @defaults
number_to_currency(1, :locale => 'en-US')
end
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 8ba09b3992..a328c4d927 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -70,7 +70,7 @@ module ActiveRecord
msgs << options[:default] if options[:default]
msgs << key
- I18n.t options.merge(:default => msgs, :scope => [:active_record, :error_messages])
+ I18n.t nil, options.merge(:default => msgs, :scope => [:active_record, :error_messages])
end
# Returns true if the specified +attribute+ has errors associated with it.
@@ -158,7 +158,6 @@ module ActiveRecord
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
def full_messages(options = {})
full_messages = []
- locale = options[:locale]
@errors.each_key do |attr|
@errors[attr].each do |message|
@@ -168,7 +167,7 @@ module ActiveRecord
full_messages << message
else
key = :"active_record.human_attribute_names.#{@base.class.name.underscore.to_sym}.#{attr}"
- attr_name = I18n.translate(key, locale, :default => @base.class.human_attribute_name(attr))
+ attr_name = I18n.translate(key, :locale => options[:locale], :default => @base.class.human_attribute_name(attr))
full_messages << attr_name + " " + message
end
end
diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb
index 5be518c547..86834fe920 100644
--- a/activerecord/test/cases/validations_i18n_test.rb
+++ b/activerecord/test/cases/validations_i18n_test.rb
@@ -46,14 +46,14 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
global_scope = [:active_record, :error_messages]
custom_scope = global_scope + [:custom, 'topic', :title]
- I18n.expects(:t).with :scope => [:active_record, :error_messages], :default => [:"custom.topic.title.invalid", 'default from class def', :invalid]
+ I18n.expects(:t).with nil, :scope => [:active_record, :error_messages], :default => [:"custom.topic.title.invalid", 'default from class def', :invalid]
@topic.errors.generate_message :title, :invalid, :default => 'default from class def'
end
def test_errors_generate_message_translates_custom_model_attribute_keys_with_sti
custom_scope = [:active_record, :error_messages, :custom, 'topic', :title]
- I18n.expects(:t).with :scope => [:active_record, :error_messages], :default => [:"custom.reply.title.invalid", :"custom.topic.title.invalid", 'default from class def', :invalid]
+ I18n.expects(:t).with nil, :scope => [:active_record, :error_messages], :default => [:"custom.reply.title.invalid", :"custom.topic.title.invalid", 'default from class def', :invalid]
Reply.new.errors.generate_message :title, :invalid, :default => 'default from class def'
end
@@ -79,7 +79,7 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase
def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
@topic.errors.instance_variable_set :@errors, { 'title' => 'empty' }
- I18n.expects(:translate).with(:"active_record.human_attribute_names.topic.title", 'en-US', :default => 'Title').returns('Title')
+ I18n.expects(:translate).with(:"active_record.human_attribute_names.topic.title", :locale => 'en-US', :default => 'Title').returns('Title')
@topic.errors.full_messages :locale => 'en-US'
end
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 80bf1404de..59dc96754f 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -10,10 +10,7 @@ module ActiveSupport #:nodoc:
def to_sentence(options = {})
options.assert_valid_keys(:connector, :skip_last_comma, :locale)
- locale = options[:locale]
- locale ||= self.locale if respond_to?(:locale)
-
- default = I18n.translate(:'support.array.sentence_connector', locale)
+ default = I18n.translate(:'support.array.sentence_connector', :locale => options[:locale])
options.reverse_merge! :connector => default, :skip_last_comma => false
options[:connector] = "#{options[:connector]} " unless options[:connector].nil? || options[:connector].strip == ''
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1 b/activesupport/lib/active_support/vendor/i18n-0.0.1
index 970bc7ab5f..46aad28993 160000
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1
+++ b/activesupport/lib/active_support/vendor/i18n-0.0.1
@@ -1 +1 @@
-Subproject commit 970bc7ab5faa94e41ee4a56bc8913c144c1cdd19
+Subproject commit 46aad289935eaf059c429acb5f3bfa0946f2d99f
diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb
index e6d90f09c1..17074b6cc6 100644
--- a/activesupport/test/i18n_test.rb
+++ b/activesupport/test/i18n_test.rb
@@ -18,15 +18,15 @@ class I18nTest < Test::Unit::TestCase
end
def test_date_localization_with_default_format
- assert_equal "2008-07-02", I18n.localize(@date, nil, :default)
+ assert_equal "2008-07-02", I18n.localize(@date, :format => :default)
end
def test_date_localization_with_short_format
- assert_equal "Jul 02", I18n.localize(@date, nil, :short)
+ assert_equal "Jul 02", I18n.localize(@date, :format => :short)
end
def test_date_localization_with_long_format
- assert_equal "July 02, 2008", I18n.localize(@date, nil, :long)
+ assert_equal "July 02, 2008", I18n.localize(@date, :format => :long)
end
def test_time_localization_should_use_default_format
@@ -34,15 +34,15 @@ class I18nTest < Test::Unit::TestCase
end
def test_time_localization_with_default_format
- assert_equal "Wed, 02 Jul 2008 16:47:01 +0100", I18n.localize(@time, nil, :default)
+ assert_equal "Wed, 02 Jul 2008 16:47:01 +0100", I18n.localize(@time, :format => :default)
end
def test_time_localization_with_short_format
- assert_equal "02 Jul 16:47", I18n.localize(@time, nil, :short)
+ assert_equal "02 Jul 16:47", I18n.localize(@time, :format => :short)
end
def test_time_localization_with_long_format
- assert_equal "July 02, 2008 16:47", I18n.localize(@time, nil, :long)
+ assert_equal "July 02, 2008 16:47", I18n.localize(@time, :format => :long)
end
def test_day_names
--
cgit v1.2.3
From 826c3db42105518b3a88cf56e348b48c1660f850 Mon Sep 17 00:00:00 2001
From: Luca Guidi
Date: Mon, 7 Jul 2008 22:46:16 +0200
Subject: Updated ActiveRecord::Errors#default_error_messages deprecation
warning according to i18n changes
---
activerecord/lib/active_record/validations.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index a328c4d927..83d55f23ea 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -21,7 +21,7 @@ module ActiveRecord
class << self
def default_error_messages
- ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use 'active_record.error_messages'.t.")
+ ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use I18n.translate('active_record.error_messages').")
I18n.translate 'active_record.error_messages'
end
end
--
cgit v1.2.3
From 2949918b4cea26435d1f0a076fe884c8113b40fa Mon Sep 17 00:00:00 2001
From: Luca Guidi
Date: Tue, 8 Jul 2008 11:53:19 +0200
Subject: Make sure object name is translated in #error_messages_for
---
actionpack/lib/action_view/helpers/active_record_helper.rb | 1 +
actionpack/test/template/active_record_helper_i18n_test.rb | 10 ++++++++++
2 files changed, 11 insertions(+)
diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb
index 4ff16cd70c..a2fee53fb6 100644
--- a/actionpack/lib/action_view/helpers/active_record_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -176,6 +176,7 @@ module ActionView
options[:header_message]
else
object_name = options[:object_name].to_s.gsub('_', ' ')
+ object_name = I18n.t(object_name, :default => object_name) || ''
locale.t :header_message, :count => count, :object_name => object_name
end
message = options.include?(:message) ? options[:message] : locale.t(:message)
diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb
index d78b0e4c0c..d35e79b94a 100644
--- a/actionpack/test/template/active_record_helper_i18n_test.rb
+++ b/actionpack/test/template/active_record_helper_i18n_test.rb
@@ -7,6 +7,7 @@ class ActiveRecordHelperI18nTest < Test::Unit::TestCase
uses_mocha 'active_record_helper_i18n_test' do
def setup
@object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
+ @object_name = 'book'
stubs(:content_tag).returns 'content_tag'
I18n.stubs(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns "1 error prohibited this from being saved"
@@ -20,17 +21,26 @@ class ActiveRecordHelperI18nTest < Test::Unit::TestCase
def test_error_messages_for_given_no_header_message_option_it_translates_header_message
I18n.expects(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns 'header message'
+ I18n.expects(:t).with('', :default => '').once
error_messages_for(:object => @object, :locale => 'en-US')
end
def test_error_messages_for_given_a_message_option_it_does_not_translate_message
I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).never
+ I18n.expects(:t).with('', :default => '').once
error_messages_for(:object => @object, :message => 'message', :locale => 'en-US')
end
def test_error_messages_for_given_no_message_option_it_translates_message
I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
+ I18n.expects(:t).with('', :default => '').once
error_messages_for(:object => @object, :locale => 'en-US')
end
+
+ def test_error_messages_for_given_object_name_it_translates_object_name
+ I18n.expects(:t).with(:header_message, :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => @object_name).returns "1 error prohibited this #{@object_name} from being saved"
+ I18n.expects(:t).with(@object_name, :default => @object_name).once.returns @object_name
+ error_messages_for(:object => @object, :locale => 'en-US', :object_name => @object_name)
+ end
end
end
\ No newline at end of file
--
cgit v1.2.3
From cf5d6ab9a849d19ac683180cc7b603ca94b13ed7 Mon Sep 17 00:00:00 2001
From: Luca Guidi
Date: Tue, 8 Jul 2008 12:37:49 +0200
Subject: Added localize helper method
---
actionpack/lib/action_view/helpers/translation_helper.rb | 4 ++++
actionpack/test/template/translation_helper_test.rb | 6 ++++++
2 files changed, 10 insertions(+)
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index c13c2dfc04..e1010ccf5f 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -11,6 +11,10 @@ module ActionView
keys = I18n.send :normalize_translation_keys, e.locale, e.key, e.options[:scope]
content_tag('span', keys.join(', '), :class => 'translation_missing')
end
+
+ def localize(*args)
+ I18n.l *args
+ end
end
end
end
\ No newline at end of file
diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb
index e97bcdb731..2263d48a65 100644
--- a/actionpack/test/template/translation_helper_test.rb
+++ b/actionpack/test/template/translation_helper_test.rb
@@ -39,4 +39,10 @@ class TranslationHelperTest < Test::Unit::TestCase
# error_messages_for(:object => @object, :locale => 'en-US')
# end
end
+
+ def test_delegates_localize_to_i18n
+ @time = Time.utc(2008, 7, 8, 12, 18, 38)
+ assert_equal "Tue, 08 Jul 2008 12:18:38 +0100", localize(@time)
+ assert_equal "08 Jul 12:18", localize(@time, :format => :short)
+ end
end
\ No newline at end of file
--
cgit v1.2.3
From dc77359c16abc0f693e3847e677c0cad62d0df50 Mon Sep 17 00:00:00 2001
From: Luca Guidi
Date: Tue, 8 Jul 2008 17:41:18 +0200
Subject: Removed unnecessary or condition in #error_messages_for
---
actionpack/lib/action_view/helpers/active_record_helper.rb | 2 +-
actionpack/test/template/active_record_helper_i18n_test.rb | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb
index a2fee53fb6..aa978a33bd 100644
--- a/actionpack/lib/action_view/helpers/active_record_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -176,7 +176,7 @@ module ActionView
options[:header_message]
else
object_name = options[:object_name].to_s.gsub('_', ' ')
- object_name = I18n.t(object_name, :default => object_name) || ''
+ object_name = I18n.t(object_name, :default => object_name)
locale.t :header_message, :count => count, :object_name => object_name
end
message = options.include?(:message) ? options[:message] : locale.t(:message)
diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb
index d35e79b94a..feec64aa30 100644
--- a/actionpack/test/template/active_record_helper_i18n_test.rb
+++ b/actionpack/test/template/active_record_helper_i18n_test.rb
@@ -21,19 +21,19 @@ class ActiveRecordHelperI18nTest < Test::Unit::TestCase
def test_error_messages_for_given_no_header_message_option_it_translates_header_message
I18n.expects(:t).with(:'header_message', :locale => 'en-US', :scope => [:active_record, :error], :count => 1, :object_name => '').returns 'header message'
- I18n.expects(:t).with('', :default => '').once
+ I18n.expects(:t).with('', :default => '').once.returns ''
error_messages_for(:object => @object, :locale => 'en-US')
end
def test_error_messages_for_given_a_message_option_it_does_not_translate_message
I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).never
- I18n.expects(:t).with('', :default => '').once
+ I18n.expects(:t).with('', :default => '').once.returns ''
error_messages_for(:object => @object, :message => 'message', :locale => 'en-US')
end
def test_error_messages_for_given_no_message_option_it_translates_message
I18n.expects(:t).with(:'message', :locale => 'en-US', :scope => [:active_record, :error]).returns 'There were problems with the following fields:'
- I18n.expects(:t).with('', :default => '').once
+ I18n.expects(:t).with('', :default => '').once.returns ''
error_messages_for(:object => @object, :locale => 'en-US')
end
--
cgit v1.2.3
From bb33432b0f5bf644713e696e4dafc7e7d3cc5808 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=BE=E7=94=B0=20=E6=98=8E?=
Date: Tue, 15 Jul 2008 21:30:20 +0900
Subject: Ruby 1.9 compat: call Proc#binding explicitly. [#623 state:resolved]
---
actionpack/lib/action_view/helpers/tag_helper.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index aeafd3906d..e9b6dd6e43 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -115,7 +115,7 @@ module ActionView
# can't take an <% end %> later on, so we have to use <% ... %>
# and implicitly concat.
def block_called_from_erb?(block)
- block && eval(BLOCK_CALLED_FROM_ERB, block)
+ block && eval(BLOCK_CALLED_FROM_ERB, block.binding)
end
def content_tag_string(name, content, options, escape = true)
--
cgit v1.2.3
From 4d76bad387036d96a955eae7c260a9633e915d11 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Tue, 15 Jul 2008 10:40:33 -0700
Subject: Ruby 1.9 compat: account for different String#hash
---
actionpack/test/template/asset_tag_helper_test.rb | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 020e112fd0..3cfc8fa4ed 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -335,8 +335,9 @@ class AssetTagHelperTest < ActionView::TestCase
ActionController::Base.asset_host = 'http://a%d.example.com'
ActionController::Base.perform_caching = true
+ hash = '/javascripts/cache/money.js'.hash % 4
assert_dom_equal(
- %(),
+ %(),
javascript_include_tag(:all, :cache => "cache/money")
)
--
cgit v1.2.3
From 3c282f3a0a7c1d5ab91241674251794ead5fa41d Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Tue, 15 Jul 2008 10:42:50 -0700
Subject: Ruby 1.9 compat: only eval with block.binding in 1.9, uses more
memory than eval with block
---
actionpack/lib/action_view/helpers/tag_helper.rb | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index e9b6dd6e43..5a296da247 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -110,12 +110,18 @@ module ActionView
private
BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template'
- # Check whether we're called from an erb template.
- # We'd return a string in any other case, but erb <%= ... %>
- # can't take an <% end %> later on, so we have to use <% ... %>
- # and implicitly concat.
- def block_called_from_erb?(block)
- block && eval(BLOCK_CALLED_FROM_ERB, block.binding)
+ if RUBY_VERSION < '1.9.0'
+ # Check whether we're called from an erb template.
+ # We'd return a string in any other case, but erb <%= ... %>
+ # can't take an <% end %> later on, so we have to use <% ... %>
+ # and implicitly concat.
+ def block_called_from_erb?(block)
+ block && eval(BLOCK_CALLED_FROM_ERB, block)
+ end
+ else
+ def block_called_from_erb?(block)
+ block && eval(BLOCK_CALLED_FROM_ERB, block.binding)
+ end
end
def content_tag_string(name, content, options, escape = true)
--
cgit v1.2.3
From 24a8ae4e08fcd15a8c3792990d1d0981d004d339 Mon Sep 17 00:00:00 2001
From: Michael Koziarski
Date: Tue, 15 Jul 2008 20:39:36 +0200
Subject: Try to get more useful errors out of the test_line_offset failures
---
actionpack/test/controller/render_test.rb | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index a857810b78..9a94db4b00 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -101,12 +101,7 @@ class TestController < ActionController::Base
end
def render_line_offset
- begin
- render :inline => '<% raise %>', :locals => {:foo => 'bar'}
- rescue RuntimeError => exc
- end
- line = exc.backtrace.first
- render :text => line
+ render :inline => '<% raise %>', :locals => {:foo => 'bar'}
end
def heading
@@ -238,10 +233,15 @@ class RenderTest < Test::Unit::TestCase
end
def test_line_offset
- get :render_line_offset
- line = @response.body
- assert(line =~ %r{:(\d+):})
- assert_equal "1", $1
+ begin
+ get :render_line_offset
+ flunk "the action should have raised an exception"
+ rescue RuntimeError => exc
+ line = exc.backtrace.first
+ assert(line =~ %r{:(\d+):})
+ assert_equal "1", $1,
+ "The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}"
+ end
end
def test_render_with_forward_slash
--
cgit v1.2.3
From 4f72feb84c25b54f66c7192c788b7fd965f2d493 Mon Sep 17 00:00:00 2001
From: Jonathan Viney
Date: Wed, 2 Jul 2008 16:01:26 +1200
Subject: Move the transaction counter to the connection object rather than
maintaining it on the current Thread.
Signed-off-by: Michael Koziarski
[#533 state:resolved]
---
.../connection_adapters/abstract_adapter.rb | 13 +++++++++++
activerecord/lib/active_record/fixtures.rb | 8 +++----
activerecord/lib/active_record/transactions.rb | 17 +++------------
activerecord/test/cases/fixtures_test.rb | 6 +++---
activerecord/test/cases/multiple_db_test.rb | 25 ++++++++++++++++++++++
5 files changed, 48 insertions(+), 21 deletions(-)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index f48b107a2a..47dbf5a5f3 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -118,6 +118,19 @@ module ActiveRecord
@connection
end
+ def open_transactions
+ @open_transactions ||= 0
+ end
+
+ def increment_open_transactions
+ @open_transactions ||= 0
+ @open_transactions += 1
+ end
+
+ def decrement_open_transactions
+ @open_transactions -= 1
+ end
+
def log_info(sql, name, runtime)
if @logger && @logger.debug?
name = "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})"
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 17fb9355c4..622cfc3c3f 100755
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -515,7 +515,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
all_loaded_fixtures.update(fixtures_map)
- connection.transaction(Thread.current['open_transactions'].to_i == 0) do
+ connection.transaction(connection.open_transactions.zero?) do
fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
fixtures.each { |fixture| fixture.insert_fixtures }
@@ -930,7 +930,7 @@ module Test #:nodoc:
load_fixtures
@@already_loaded_fixtures[self.class] = @loaded_fixtures
end
- ActiveRecord::Base.send :increment_open_transactions
+ ActiveRecord::Base.connection.increment_open_transactions
ActiveRecord::Base.connection.begin_db_transaction
# Load fixtures for every test.
else
@@ -951,9 +951,9 @@ module Test #:nodoc:
end
# Rollback changes if a transaction is active.
- if use_transactional_fixtures? && Thread.current['open_transactions'] != 0
+ if use_transactional_fixtures? && ActiveRecord::Base.connection.open_transactions != 0
ActiveRecord::Base.connection.rollback_db_transaction
- Thread.current['open_transactions'] = 0
+ ActiveRecord::Base.connection.decrement_open_transactions
end
ActiveRecord::Base.verify_active_connections!
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 3b6835762c..354a6c83a2 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -73,25 +73,14 @@ module ActiveRecord
# trigger a ROLLBACK when raised, but not be re-raised by the transaction block.
module ClassMethods
def transaction(&block)
- increment_open_transactions
+ connection.increment_open_transactions
begin
- connection.transaction(Thread.current['start_db_transaction'], &block)
+ connection.transaction(connection.open_transactions == 1, &block)
ensure
- decrement_open_transactions
+ connection.decrement_open_transactions
end
end
-
- private
- def increment_open_transactions #:nodoc:
- open = Thread.current['open_transactions'] ||= 0
- Thread.current['start_db_transaction'] = open.zero?
- Thread.current['open_transactions'] = open + 1
- end
-
- def decrement_open_transactions #:nodoc:
- Thread.current['open_transactions'] -= 1
- end
end
def transaction(&block)
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 0ea24868f1..6ba7597f56 100755
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -461,11 +461,11 @@ class FixturesBrokenRollbackTest < ActiveRecord::TestCase
alias_method :teardown, :blank_teardown
def test_no_rollback_in_teardown_unless_transaction_active
- assert_equal 0, Thread.current['open_transactions']
+ assert_equal 0, ActiveRecord::Base.connection.open_transactions
assert_raise(RuntimeError) { ar_setup_fixtures }
- assert_equal 0, Thread.current['open_transactions']
+ assert_equal 0, ActiveRecord::Base.connection.open_transactions
assert_nothing_raised { ar_teardown_fixtures }
- assert_equal 0, Thread.current['open_transactions']
+ assert_equal 0, ActiveRecord::Base.connection.open_transactions
end
private
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index eb3e43c8ac..7c3e0f2ca6 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -57,4 +57,29 @@ class MultipleDbTest < ActiveRecord::TestCase
assert Course.connection
end
+
+ def test_transactions_across_databases
+ c1 = Course.find(1)
+ e1 = Entrant.find(1)
+
+ begin
+ Course.transaction do
+ Entrant.transaction do
+ c1.name = "Typo"
+ e1.name = "Typo"
+ c1.save
+ e1.save
+ raise "No I messed up."
+ end
+ end
+ rescue
+ # Yup caught it
+ end
+
+ assert_equal "Typo", c1.name
+ assert_equal "Typo", e1.name
+
+ assert_equal "Ruby Development", Course.find(1).name
+ assert_equal "Ruby Developer", Entrant.find(1).name
+ end
end
--
cgit v1.2.3
From aca246ab25497bb6787d2e18680e9f73ad13d223 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 15 Jul 2008 14:41:38 -0500
Subject: Get buffer for fragment cache from template's @output_buffer
---
actionpack/CHANGELOG | 2 +
.../lib/action_controller/caching/fragments.rb | 4 +-
actionpack/lib/action_view/helpers/cache_helper.rb | 2 +-
.../lib/action_view/helpers/prototype_helper.rb | 375 +++++++++++----------
actionpack/lib/action_view/template_handler.rb | 4 -
.../lib/action_view/template_handlers/builder.rb | 7 +-
.../lib/action_view/template_handlers/erb.rb | 6 -
.../lib/action_view/template_handlers/rjs.rb | 11 -
actionpack/test/controller/caching_test.rb | 43 +--
actionpack/test/template/javascript_helper_test.rb | 2 +
actionpack/test/template/prototype_helper_test.rb | 2 +-
11 files changed, 198 insertions(+), 260 deletions(-)
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 5b7bfe9c30..52d00a417c 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* Get buffer for fragment cache from template's @output_buffer [Josh Peek]
+
* Set config.action_view.warn_cache_misses = true to receive a warning if you perform an action that results in an expensive disk operation that could be cached [Josh Peek]
* Refactor template preloading. New abstractions include Renderable mixins and a refactored Template class [Josh Peek]
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index 57b31ec9d1..b1f25fdf5c 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -60,10 +60,8 @@ module ActionController #:nodoc:
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end
- def fragment_for(block, name = {}, options = nil) #:nodoc:
+ def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
if perform_caching
- buffer = yield
-
if cache = read_fragment(name, options)
buffer.concat(cache)
else
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index 2cdbae6e40..64d1ad2715 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -32,7 +32,7 @@ module ActionView
# Topics listed alphabetically
# <% end %>
def cache(name = {}, options = nil, &block)
- _last_render.handler.new(@controller).cache_fragment(block, name, options)
+ @controller.fragment_for(output_buffer, name, options, &block)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index d0c281c803..edb43844a4 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -3,25 +3,25 @@ require 'set'
module ActionView
module Helpers
# Prototype[http://www.prototypejs.org/] is a JavaScript library that provides
- # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
+ # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
# Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]
- # functionality, and more traditional object-oriented facilities for JavaScript.
+ # functionality, and more traditional object-oriented facilities for JavaScript.
# This module provides a set of helpers to make it more convenient to call
- # functions from Prototype using Rails, including functionality to call remote
- # Rails methods (that is, making a background request to a Rails action) using Ajax.
- # This means that you can call actions in your controllers without
- # reloading the page, but still update certain parts of it using
+ # functions from Prototype using Rails, including functionality to call remote
+ # Rails methods (that is, making a background request to a Rails action) using Ajax.
+ # This means that you can call actions in your controllers without
+ # reloading the page, but still update certain parts of it using
# injections into the DOM. A common use case is having a form that adds
# a new element to a list without reloading the page or updating a shopping
# cart total when a new item is added.
#
# == Usage
- # To be able to use these helpers, you must first include the Prototype
- # JavaScript framework in your pages.
+ # To be able to use these helpers, you must first include the Prototype
+ # JavaScript framework in your pages.
#
# javascript_include_tag 'prototype'
#
- # (See the documentation for
+ # (See the documentation for
# ActionView::Helpers::JavaScriptHelper for more information on including
# this and other JavaScript files in your Rails templates.)
#
@@ -29,7 +29,7 @@ module ActionView
#
# link_to_remote "Add to cart",
# :url => { :action => "add", :id => product.id },
- # :update => { :success => "cart", :failure => "error" }
+ # :update => { :success => "cart", :failure => "error" }
#
# ...through a form...
#
@@ -50,8 +50,8 @@ module ActionView
# :update => :hits,
# :with => 'query'
# %>
- #
- # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
+ #
+ # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
# are listed here); check out the documentation for each method to find out more about its usage and options.
#
# === Common Options
@@ -63,7 +63,7 @@ module ActionView
# When building your action handlers (that is, the Rails actions that receive your background requests), it's
# important to remember a few things. First, whatever your action would normall return to the browser, it will
# return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause
- # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
+ # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
# You can turn the layout off on particular actions by doing the following:
#
# class SiteController < ActionController::Base
@@ -74,8 +74,8 @@ module ActionView
#
# render :layout => false
#
- # You can tell the type of request from within your action using the request.xhr? (XmlHttpRequest, the
- # method that Ajax uses to make background requests) method.
+ # You can tell the type of request from within your action using the request.xhr? (XmlHttpRequest, the
+ # method that Ajax uses to make background requests) method.
# def name
# # Is this an XmlHttpRequest request?
# if (request.xhr?)
@@ -93,7 +93,7 @@ module ActionView
#
# Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request.
#
- # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
+ # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
# render text output, like this:
#
# render :text => 'Return this from my method!'
@@ -103,7 +103,7 @@ module ActionView
#
# == Updating multiple elements
# See JavaScriptGenerator for information on updating multiple elements
- # on the page in an Ajax response.
+ # on the page in an Ajax response.
module PrototypeHelper
unless const_defined? :CALLBACKS
CALLBACKS = Set.new([ :uninitialized, :loading, :loaded,
@@ -114,64 +114,64 @@ module ActionView
:form, :with, :update, :script ]).merge(CALLBACKS)
end
- # Returns a link to a remote action defined by options[:url]
- # (using the url_for format) that's called in the background using
+ # Returns a link to a remote action defined by options[:url]
+ # (using the url_for format) that's called in the background using
# XMLHttpRequest. The result of that request can then be inserted into a
- # DOM object whose id can be specified with options[:update] .
+ # DOM object whose id can be specified with options[:update] .
# Usually, the result would be a partial prepared by the controller with
- # render :partial.
+ # render :partial.
#
# Examples:
- # # Generates: Delete this post
- # link_to_remote "Delete this post", :update => "posts",
+ # link_to_remote "Delete this post", :update => "posts",
# :url => { :action => "destroy", :id => post.id }
#
- # # Generates:
- # link_to_remote(image_tag("refresh"), :update => "emails",
+ # link_to_remote(image_tag("refresh"), :update => "emails",
# :url => { :action => "list_emails" })
- #
+ #
# You can override the generated HTML options by specifying a hash in
# options[:html] .
- #
+ #
# link_to_remote "Delete this post", :update => "posts",
- # :url => post_url(@post), :method => :delete,
- # :html => { :class => "destructive" }
+ # :url => post_url(@post), :method => :delete,
+ # :html => { :class => "destructive" }
#
# You can also specify a hash for options[:update] to allow for
- # easy redirection of output to an other DOM element if a server-side
+ # easy redirection of output to an other DOM element if a server-side
# error occurs:
#
# Example:
- # # Generates: Delete this post
# link_to_remote "Delete this post",
# :url => { :action => "destroy", :id => post.id },
# :update => { :success => "posts", :failure => "error" }
#
- # Optionally, you can use the options[:position] parameter to
- # influence how the target DOM element is updated. It must be one of
+ # Optionally, you can use the options[:position] parameter to
+ # influence how the target DOM element is updated. It must be one of
# :before , :top , :bottom , or :after .
#
# The method used is by default POST. You can also specify GET or you
# can simulate PUT or DELETE over POST. All specified with options[:method]
#
# Example:
- # # Generates: Destroy
# link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete
#
- # By default, these remote requests are processed asynchronous during
- # which various JavaScript callbacks can be triggered (for progress
- # indicators and the likes). All callbacks get access to the
- # request object, which holds the underlying XMLHttpRequest.
+ # By default, these remote requests are processed asynchronous during
+ # which various JavaScript callbacks can be triggered (for progress
+ # indicators and the likes). All callbacks get access to the
+ # request object, which holds the underlying XMLHttpRequest.
#
# To access the server response, use request.responseText , to
# find out the HTTP status, use request.status .
#
# Example:
- # # Generates: hello
# word = 'hello'
# link_to_remote word,
@@ -180,43 +180,43 @@ module ActionView
#
# The callbacks that may be specified are (in order):
#
- # :loading :: Called when the remote document is being
+ # :loading :: Called when the remote document is being
# loaded with data by the browser.
# :loaded :: Called when the browser has finished loading
# the remote document.
- # :interactive :: Called when the user can interact with the
- # remote document, even though it has not
+ # :interactive :: Called when the user can interact with the
+ # remote document, even though it has not
# finished loading.
# :success :: Called when the XMLHttpRequest is completed,
# and the HTTP status code is in the 2XX range.
# :failure :: Called when the XMLHttpRequest is completed,
# and the HTTP status code is not in the 2XX
# range.
- # :complete :: Called when the XMLHttpRequest is complete
- # (fires after success/failure if they are
+ # :complete :: Called when the XMLHttpRequest is complete
+ # (fires after success/failure if they are
# present).
- #
- # You can further refine :success and :failure by
+ #
+ # You can further refine :success and :failure by
# adding additional callbacks for specific status codes.
#
# Example:
- # # Generates: hello
# link_to_remote word,
# :url => { :action => "action" },
# 404 => "alert('Not found...? Wrong URL...?')",
# :failure => "alert('HTTP Error ' + request.status + '!')"
#
- # A status code callback overrides the success/failure handlers if
+ # A status code callback overrides the success/failure handlers if
# present.
#
# If you for some reason or another need synchronous processing (that'll
- # block the browser while the request is happening), you can specify
+ # block the browser while the request is happening), you can specify
# options[:type] = :synchronous .
#
# You can customize further browser side call logic by passing in
- # JavaScript code snippets via some optional parameters. In their order
+ # JavaScript code snippets via some optional parameters. In their order
# of use these are:
#
# :confirm :: Adds confirmation dialog.
@@ -228,7 +228,7 @@ module ActionView
# :after :: Called immediately after request was
# initiated and before :loading .
# :submit :: Specifies the DOM element ID that's used
- # as the parent of the form elements. By
+ # as the parent of the form elements. By
# default this is the current form, but
# it could just as well be the ID of a
# table row or any other DOM element.
@@ -238,10 +238,10 @@ module ActionView
# URL query string.
#
# Example:
- #
+ #
# :with => "'name=' + $('name').value"
#
- # You can generate a link that uses AJAX in the general case, while
+ # You can generate a link that uses AJAX in the general case, while
# degrading gracefully to plain link behavior in the absence of
# JavaScript by setting html_options[:href] to an alternate URL.
# Note the extra curly braces around the options hash separate
@@ -251,7 +251,7 @@ module ActionView
# link_to_remote "Delete this post",
# { :update => "posts", :url => { :action => "destroy", :id => post.id } },
# :href => url_for(:action => "destroy", :id => post.id)
- def link_to_remote(name, options = {}, html_options = nil)
+ def link_to_remote(name, options = {}, html_options = nil)
link_to_function(name, remote_function(options), html_options || options.delete(:html))
end
@@ -262,15 +262,15 @@ module ActionView
# and defining callbacks is the same as link_to_remote.
# Examples:
# # Call get_averages and put its results in 'avg' every 10 seconds
- # # Generates:
- # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
+ # # Generates:
+ # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
# # {asynchronous:true, evalScripts:true})}, 10)
# periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg')
#
# # Call invoice every 10 seconds with the id of the customer
# # If it succeeds, update the invoice DIV; if it fails, update the error DIV
# # Generates:
- # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
+ # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
# # '/testing/invoice/16', {asynchronous:true, evalScripts:true})}, 10)
# periodically_call_remote(:url => { :action => 'invoice', :id => customer.id },
# :update => { :success => "invoice", :failure => "error" }
@@ -286,11 +286,11 @@ module ActionView
javascript_tag(code)
end
- # Returns a form tag that will submit using XMLHttpRequest in the
- # background instead of the regular reloading POST arrangement. Even
+ # Returns a form tag that will submit using XMLHttpRequest in the
+ # background instead of the regular reloading POST arrangement. Even
# though it's using JavaScript to serialize the form elements, the form
# submission will work just like a regular submission as viewed by the
- # receiving side (all elements available in params ). The options for
+ # receiving side (all elements available in params ). The options for
# specifying the target with :url and defining callbacks is the same as
# +link_to_remote+.
#
@@ -299,21 +299,21 @@ module ActionView
#
# Example:
# # Generates:
- # #
# <% form_remote_tag :url => '/posts' do -%>
@@ -323,19 +323,19 @@ module ActionView
options[:form] = true
options[:html] ||= {}
- options[:html][:onsubmit] =
- (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
+ options[:html][:onsubmit] =
+ (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
"#{remote_function(options)}; return false;"
form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html], &block)
end
- # Creates a form that will submit using XMLHttpRequest in the background
- # instead of the regular reloading POST arrangement and a scope around a
+ # Creates a form that will submit using XMLHttpRequest in the background
+ # instead of the regular reloading POST arrangement and a scope around a
# specific resource that is used as a base for questioning about
- # values for the fields.
+ # values for the fields.
#
- # === Resource
+ # === Resource
#
# Example:
# <% remote_form_for(@post) do |f| %>
@@ -348,7 +348,7 @@ module ActionView
# ...
# <% end %>
#
- # === Nested Resource
+ # === Nested Resource
#
# Example:
# <% remote_form_for([@post, @comment]) do |f| %>
@@ -387,23 +387,23 @@ module ActionView
concat('')
end
alias_method :form_remote_for, :remote_form_for
-
+
# Returns a button input tag with the element name of +name+ and a value (i.e., display text) of +value+
# that will submit form using XMLHttpRequest in the background instead of a regular POST request that
- # reloads the page.
+ # reloads the page.
#
# # Create a button that submits to the create action
- # #
- # # Generates:
# <%= button_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
#
# # Submit to the remote action update and update the DIV succeed or fail based
# # on the success or failure of the request
# #
- # # Generates:
# <%= button_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
# :update => { :success => "succeed", :failure => "fail" }
@@ -423,7 +423,7 @@ module ActionView
tag("input", options[:html], false)
end
alias_method :submit_to_remote, :button_to_remote
-
+
# Returns 'eval(request.responseText) ' which is the JavaScript function
# that +form_remote_tag+ can call in :complete to evaluate a multiple
# update return document using +update_element_function+ calls.
@@ -433,11 +433,11 @@ module ActionView
# Returns the JavaScript needed for a remote function.
# Takes the same arguments as link_to_remote.
- #
+ #
# Example:
- # # Generates:
- # { :action => :update_options }) %>">
# Hello
# World
@@ -455,7 +455,7 @@ module ActionView
update << "'#{options[:update]}'"
end
- function = update.empty? ?
+ function = update.empty? ?
"new Ajax.Request(" :
"new Ajax.Updater(#{update}, "
@@ -476,9 +476,9 @@ module ActionView
# callback when its contents have changed. The default callback is an
# Ajax call. By default the value of the observed field is sent as a
# parameter with the Ajax call.
- #
+ #
# Example:
- # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
+ # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
# # '/testing/find_suggestion', {asynchronous:true, evalScripts:true, parameters:'q=' + value})})
# <%= observe_field :suggest, :url => { :action => :find_suggestion },
# :frequency => 0.25,
@@ -500,14 +500,14 @@ module ActionView
# new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')})
# The element parameter is the DOM element being observed, and the value is its value at the
# time the observer is triggered.
- #
+ #
# Additional options are:
# :frequency :: The frequency (in seconds) at which changes to
# this field will be detected. Not setting this
# option at all or to a value equal to or less than
# zero will use event based observation instead of
# time based observation.
- # :update :: Specifies the DOM ID of the element whose
+ # :update :: Specifies the DOM ID of the element whose
# innerHTML should be updated with the
# XMLHttpRequest response text.
# :with :: A JavaScript expression specifying the parameters
@@ -518,7 +518,7 @@ module ActionView
# variable +value+.
#
# Examples
- #
+ #
# :with => "'my_custom_key=' + value"
# :with => "'person[name]=' + prompt('New name')"
# :with => "Form.Element.serialize('other-field')"
@@ -544,7 +544,7 @@ module ActionView
# observe_field 'book_title',
# :url => 'http://example.com/books/edit/1',
# :with => 'title'
- #
+ #
# # Sends params: {:book_title => 'Title of the book'} when the focus leaves
# # the input field.
# observe_field 'book_title',
@@ -558,7 +558,7 @@ module ActionView
build_observer('Form.Element.EventObserver', field_id, options)
end
end
-
+
# Observes the form with the DOM ID specified by +form_id+ and calls a
# callback when its contents have changed. The default callback is an
# Ajax call. By default all fields of the observed field are sent as
@@ -574,16 +574,17 @@ module ActionView
build_observer('Form.EventObserver', form_id, options)
end
end
-
- # All the methods were moved to GeneratorMethods so that
+
+ # All the methods were moved to GeneratorMethods so that
# #include_helpers_from_context has nothing to overwrite.
class JavaScriptGenerator #:nodoc:
def initialize(context, &block) #:nodoc:
@context, @lines = context, []
+ @context.output_buffer = @lines if @context
include_helpers_from_context
@context.instance_exec(self, &block)
end
-
+
private
def include_helpers_from_context
@context.extended_by.each do |mod|
@@ -591,17 +592,17 @@ module ActionView
end
extend GeneratorMethods
end
-
- # JavaScriptGenerator generates blocks of JavaScript code that allow you
- # to change the content and presentation of multiple DOM elements. Use
+
+ # JavaScriptGenerator generates blocks of JavaScript code that allow you
+ # to change the content and presentation of multiple DOM elements. Use
# this in your Ajax response bodies, either in a \n\n\n\n), javascript_include_tag(:defaults))
--
cgit v1.2.3
From 1e0f94a77c717dd06a86edda97de5a4c4ad919a8 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Thu, 17 Jul 2008 14:45:14 -0700
Subject: Introduce simple internationalization support
---
actionpack/test/i18n_coverage | 9 ---------
activerecord/test/i18n_coverage | 6 ------
railties/CHANGELOG | 2 ++
3 files changed, 2 insertions(+), 15 deletions(-)
delete mode 100755 actionpack/test/i18n_coverage
delete mode 100755 activerecord/test/i18n_coverage
diff --git a/actionpack/test/i18n_coverage b/actionpack/test/i18n_coverage
deleted file mode 100755
index 57b54e9d47..0000000000
--- a/actionpack/test/i18n_coverage
+++ /dev/null
@@ -1,9 +0,0 @@
-rcov -x abstract_unit.rb \
--i action_view/helpers/number_helper.rb,action_view/helpers/date_helper.rb,action_view/helpers/active_record_helper.rb \
-template/number_helper_i18n_test.rb \
-template/date_helper_i18n_test.rb \
-template/active_record_helper_i18n_test.rb \
-
-# template/number_helper_test.rb \
-# template/date_helper_test.rb \
-# template/active_record_helper_test.rb
\ No newline at end of file
diff --git a/activerecord/test/i18n_coverage b/activerecord/test/i18n_coverage
deleted file mode 100755
index 1589a6c06f..0000000000
--- a/activerecord/test/i18n_coverage
+++ /dev/null
@@ -1,6 +0,0 @@
-rcov -I connections/native_mysql \
--x cases/helper,config,connection,models \
--i active_record/validations.rb \
-cases/validations_i18n_test.rb \
-
-# cases/validations_test.rb
\ No newline at end of file
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index b5c5aba460..9c5e5b59c6 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* Introduce simple internationalization support. [Ruby i18n team]
+
* Make script/plugin install -r option work with git based plugins. #257. [Tim Pope Jakub Kuźma]. Example:
script/plugin install git://github.com/mislav/will_paginate.git -r agnostic # Installs 'agnostic' branch
--
cgit v1.2.3
From a1fcbd971d681e44de5ea33e6a8470ff8b8144c0 Mon Sep 17 00:00:00 2001
From: Joachim Garth
Date: Fri, 27 Jun 2008 20:03:51 +0200
Subject: Make sure association preloading works with full STI class name [#465
state:Resolved]
Signed-off-by: Pratik Naik
---
.../lib/active_record/association_preload.rb | 2 +-
.../eager_load_includes_full_sti_class_test.rb | 36 ++++++++++++++++++++++
2 files changed, 37 insertions(+), 1 deletion(-)
create mode 100644 activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 64888f9110..c7594809b7 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -252,7 +252,7 @@ module ActiveRecord
table_name = reflection.klass.quoted_table_name
if interface = reflection.options[:as]
- conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} IN (?) and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.name.demodulize}'"
+ conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} IN (?) and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'"
else
foreign_key = reflection.primary_key_name
conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} IN (?)"
diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
new file mode 100644
index 0000000000..7c470616a5
--- /dev/null
+++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
@@ -0,0 +1,36 @@
+require 'cases/helper'
+require 'models/post'
+require 'models/tagging'
+
+module Namespaced
+ class Post < ActiveRecord::Base
+ set_table_name 'posts'
+ has_one :tagging, :as => :taggable, :class_name => 'Tagging'
+ end
+end
+
+class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase
+
+ def setup
+ generate_test_objects
+ end
+
+ def generate_test_objects
+ post = Namespaced::Post.create( :title => 'Great stuff', :body => 'This is not', :author_id => 1 )
+ tagging = Tagging.create( :taggable => post )
+ end
+
+ def test_class_names
+ old = ActiveRecord::Base.store_full_sti_class
+
+ ActiveRecord::Base.store_full_sti_class = false
+ post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
+ assert_nil post.tagging
+
+ ActiveRecord::Base.store_full_sti_class = true
+ post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
+ assert_equal 'Tagging', post.tagging.class.name
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old
+ end
+end
--
cgit v1.2.3
From 57a2780f14447152ece1b1301fc6c25b2ec43da5 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Wed, 16 Jul 2008 04:32:15 -0700
Subject: etag! and last_modified! conditional GET helpers
---
actionpack/CHANGELOG | 4 +
actionpack/lib/action_controller/base.rb | 15 ++-
actionpack/lib/action_controller/response.rb | 40 +++++--
actionpack/test/controller/render_test.rb | 152 +++++++++++++++++----------
4 files changed, 150 insertions(+), 61 deletions(-)
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index da9fdbfd9d..f6432fe0be 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,9 @@
*Edge*
+* Conditional GET utility methods. [Jeremy Kemper]
+ * etag!([:admin, post, current_user]) sets the ETag response header and returns head(:not_modified) if it matches the If-None-Match request header.
+ * last_modified!(post.updated_at) sets Last-Modified and returns head(:not_modified) if it's no later than If-Modified-Since.
+
* All 2xx requests are considered successful [Josh Peek]
* Fixed that AssetTagHelper#compute_public_path shouldn't cache the asset_host along with the source or per-request proc's won't run [DHH]
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index c56812c2d9..50727c67c4 100755
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -519,6 +519,8 @@ module ActionController #:nodoc:
public
# Extracts the action_name from the request parameters and performs that action.
def process(request, response, method = :perform_action, *arguments) #:nodoc:
+ response.request = request
+
initialize_template_class(response)
assign_shortcuts(request, response)
initialize_current_url
@@ -529,8 +531,6 @@ module ActionController #:nodoc:
send(method, *arguments)
assign_default_content_type_and_charset
-
- response.request = request
response.prepare! unless component_request?
response
ensure
@@ -968,6 +968,17 @@ module ActionController #:nodoc:
render :nothing => true, :status => status
end
+ # Sets the Last-Modified response header. Returns 304 Not Modified if the
+ # If-Modified-Since request header is <= last modified.
+ def last_modified!(utc_time)
+ head(:not_modified) if response.last_modified!(utc_time)
+ end
+
+ # Sets the ETag response header. Returns 304 Not Modified if the
+ # If-None-Match request header matches.
+ def etag!(etag)
+ head(:not_modified) if response.etag!(etag)
+ end
# Clears the rendered results, allowing for another render to be performed.
def erase_render_results #:nodoc:
diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb
index 1d9f6676ba..9955532844 100755
--- a/actionpack/lib/action_controller/response.rb
+++ b/actionpack/lib/action_controller/response.rb
@@ -41,20 +41,48 @@ module ActionController
set_content_length!
end
+ # Sets the Last-Modified response header. Returns whether it's older than
+ # the If-Modified-Since request header.
+ def last_modified!(utc_time)
+ headers['Last-Modified'] ||= utc_time.httpdate
+ if request && since = request.headers['HTTP_IF_MODIFIED_SINCE']
+ utc_time <= Time.rfc2822(since)
+ end
+ end
+
+ # Sets the ETag response header. Returns whether it matches the
+ # If-None-Match request header.
+ def etag!(tag)
+ headers['ETag'] ||= %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(tag))}")
+ if request && request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
+ true
+ end
+ end
private
def handle_conditional_get!
- if body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty?
- self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}")
- self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
+ if nonempty_ok_response?
+ set_conditional_cache_control!
- if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
- self.headers['Status'] = '304 Not Modified'
+ if etag!(body)
+ headers['Status'] = '304 Not Modified'
self.body = ''
end
end
end
+ def nonempty_ok_response?
+ status = headers['Status']
+ ok = !status || status[0..2] == '200'
+ ok && body.is_a?(String) && !body.empty?
+ end
+
+ def set_conditional_cache_control!
+ if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
+ headers['Cache-Control'] = 'private, max-age=0, must-revalidate'
+ end
+ end
+
def convert_content_type!
if content_type = headers.delete("Content-Type")
self.headers["type"] = content_type
@@ -73,4 +101,4 @@ module ActionController
self.headers["Content-Length"] = body.size unless body.respond_to?(:call)
end
end
-end
\ No newline at end of file
+end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 9a94db4b00..041c54c7fd 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -8,14 +8,18 @@ module Fun
end
end
-
-# FIXME: crashes Ruby 1.9
class TestController < ActionController::Base
layout :determine_layout
def hello_world
end
+ def conditional_hello
+ etag! [:foo, 123]
+ last_modified! Time.now.utc.beginning_of_day
+ render :action => 'hello_world' unless performed?
+ end
+
def render_hello_world
render :template => "test/hello_world"
end
@@ -408,6 +412,72 @@ class RenderTest < Test::Unit::TestCase
assert_equal "Goodbye, Local David", @response.body
end
+ def test_should_render_formatted_template
+ get :formatted_html_erb
+ assert_equal 'formatted html erb', @response.body
+ end
+
+ def test_should_render_formatted_xml_erb_template
+ get :formatted_xml_erb, :format => :xml
+ assert_equal 'passed formatted xml erb ', @response.body
+ end
+
+ def test_should_render_formatted_html_erb_template
+ get :formatted_xml_erb
+ assert_equal 'passed formatted html erb ', @response.body
+ end
+
+ def test_should_render_formatted_html_erb_template_with_faulty_accepts_header
+ @request.env["HTTP_ACCEPT"] = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, appliction/x-shockwave-flash, */*"
+ get :formatted_xml_erb
+ assert_equal 'passed formatted html erb ', @response.body
+ end
+
+ def test_should_render_html_formatted_partial
+ get :partial
+ assert_equal 'partial html', @response.body
+ end
+
+ def test_should_render_html_partial_with_dot
+ get :partial_dot_html
+ assert_equal 'partial html', @response.body
+ end
+
+ def test_should_render_html_formatted_partial_with_rjs
+ xhr :get, :partial_as_rjs
+ assert_equal %(Element.replace("foo", "partial html");), @response.body
+ end
+
+ def test_should_render_html_formatted_partial_with_rjs_and_js_format
+ xhr :get, :respond_to_partial_as_rjs
+ assert_equal %(Element.replace("foo", "partial html");), @response.body
+ end
+
+ def test_should_render_js_partial
+ xhr :get, :partial, :format => 'js'
+ assert_equal 'partial js', @response.body
+ end
+
+ def test_should_render_with_alternate_default_render
+ xhr :get, :render_alternate_default
+ assert_equal %(Element.replace("foo", "partial html");), @response.body
+ end
+
+ def test_should_render_xml_but_keep_custom_content_type
+ get :render_xml_with_custom_content_type
+ assert_equal "application/atomsvc+xml", @response.content_type
+ end
+end
+
+class EtagRenderTest < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = TestController.new
+
+ @request.host = "www.nextangle.com"
+ end
+
def test_render_200_should_set_etag
get :render_hello_world_from_variable
assert_equal etag_for("hello david"), @response.headers['ETag']
@@ -460,64 +530,40 @@ class RenderTest < Test::Unit::TestCase
assert_equal etag_for("\n\n Hello
\nThis is grand!
\n\n \n"), @response.headers['ETag']
end
- def test_should_render_formatted_template
- get :formatted_html_erb
- assert_equal 'formatted html erb', @response.body
- end
-
- def test_should_render_formatted_xml_erb_template
- get :formatted_xml_erb, :format => :xml
- assert_equal 'passed formatted xml erb ', @response.body
- end
-
- def test_should_render_formatted_html_erb_template
- get :formatted_xml_erb
- assert_equal 'passed formatted html erb ', @response.body
- end
-
- def test_should_render_formatted_html_erb_template_with_faulty_accepts_header
- @request.env["HTTP_ACCEPT"] = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, appliction/x-shockwave-flash, */*"
- get :formatted_xml_erb
- assert_equal 'passed formatted html erb ', @response.body
- end
-
- def test_should_render_html_formatted_partial
- get :partial
- assert_equal 'partial html', @response.body
- end
-
- def test_should_render_html_partial_with_dot
- get :partial_dot_html
- assert_equal 'partial html', @response.body
- end
+ protected
+ def etag_for(text)
+ %("#{Digest::MD5.hexdigest(text)}")
+ end
+end
- def test_should_render_html_formatted_partial_with_rjs
- xhr :get, :partial_as_rjs
- assert_equal %(Element.replace("foo", "partial html");), @response.body
- end
+class LastModifiedRenderTest < Test::Unit::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = TestController.new
- def test_should_render_html_formatted_partial_with_rjs_and_js_format
- xhr :get, :respond_to_partial_as_rjs
- assert_equal %(Element.replace("foo", "partial html");), @response.body
+ @request.host = "www.nextangle.com"
+ @last_modified = Time.now.utc.beginning_of_day.httpdate
end
- def test_should_render_js_partial
- xhr :get, :partial, :format => 'js'
- assert_equal 'partial js', @response.body
+ def test_responds_with_last_modified
+ get :conditional_hello
+ assert_equal @last_modified, @response.headers['Last-Modified']
end
- def test_should_render_with_alternate_default_render
- xhr :get, :render_alternate_default
- assert_equal %(Element.replace("foo", "partial html");), @response.body
+ def test_request_not_modified
+ @request.headers["HTTP_IF_MODIFIED_SINCE"] = @last_modified
+ get :conditional_hello
+ assert_equal "304 Not Modified", @response.headers['Status']
+ assert @response.body.blank?, @response.body
+ assert_equal @last_modified, @response.headers['Last-Modified']
end
- def test_should_render_xml_but_keep_custom_content_type
- get :render_xml_with_custom_content_type
- assert_equal "application/atomsvc+xml", @response.content_type
+ def test_request_modified
+ @request.headers["HTTP_IF_MODIFIED_SINCE"] = 'Thu, 16 Jul 2008 00:00:00 GMT'
+ get :conditional_hello
+ assert_equal "200 OK", @response.headers['Status']
+ assert !@response.body.blank?
+ assert_equal @last_modified, @response.headers['Last-Modified']
end
-
- protected
- def etag_for(text)
- %("#{Digest::MD5.hexdigest(text)}")
- end
end
--
cgit v1.2.3
From 7430c4168fad07b480dbf80c8ac75ba7db8c634f Mon Sep 17 00:00:00 2001
From: Jeremy Kemper
Date: Wed, 16 Jul 2008 22:27:04 -0700
Subject: Decrease default benchmark runs from 10 to 4
---
activesupport/lib/active_support/testing/performance.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index 5f2027eb3b..71d6f4d9c6 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -11,7 +11,7 @@ module ActiveSupport
DEFAULTS =
if benchmark = ARGV.include?('--benchmark') # HAX for rake test
{ :benchmark => true,
- :runs => 10,
+ :runs => 4,
:metrics => [:process_time, :memory, :objects, :gc_runs, :gc_time],
:output => 'tmp/performance' }
else
--
cgit v1.2.3
From e1f23da53cef20a60e4bf458d959fe2bfe7d52ea Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 18 Jul 2008 11:18:16 -0500
Subject: Allow memoized methods to be reloaded and allow multiple symbols
---
activesupport/lib/active_support/memoizable.rb | 32 +++++++++++----------
activesupport/test/memoizable_test.rb | 39 +++++++++++++++++---------
2 files changed, 43 insertions(+), 28 deletions(-)
diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb
index d06250171a..c41feef4c7 100644
--- a/activesupport/lib/active_support/memoizable.rb
+++ b/activesupport/lib/active_support/memoizable.rb
@@ -5,28 +5,30 @@ module ActiveSupport
end
module ClassMethods
- def memoize(symbol)
- original_method = "_unmemoized_#{symbol}"
- memoized_ivar = "@_memoized_#{symbol}"
- raise "Already memoized #{symbol}" if instance_methods.map(&:to_s).include?(original_method)
+ def memoize(*symbols)
+ symbols.each do |symbol|
+ original_method = "unmemoized_#{symbol}"
+ memoized_ivar = "@#{symbol}"
+ raise "Already memoized #{symbol}" if instance_methods.map(&:to_s).include?(original_method)
- alias_method original_method, symbol
- class_eval <<-EOS, __FILE__, __LINE__
- def #{symbol}
- if defined? #{memoized_ivar}
- #{memoized_ivar}
- else
- #{memoized_ivar} = #{original_method}
+ alias_method original_method, symbol
+ class_eval <<-EOS, __FILE__, __LINE__
+ def #{symbol}(reload = false)
+ if !reload && defined? #{memoized_ivar}
+ #{memoized_ivar}
+ else
+ #{memoized_ivar} = #{original_method}.freeze
+ end
end
- end
- EOS
+ EOS
+ end
end
end
def freeze
methods.each do |method|
- if m = method.to_s.match(/\A_unmemoized_(.*)/)
- send(m[1]).freeze
+ if m = method.to_s.match(/\Aunmemoized_(.*)/)
+ send(m[1])
end
end
super
diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb
index fc24a2942d..b649b31455 100644
--- a/activesupport/test/memoizable_test.rb
+++ b/activesupport/test/memoizable_test.rb
@@ -8,12 +8,16 @@ uses_mocha 'Memoizable' do
def name
fetch_name_from_floppy
end
- memoize :name
def age
nil
end
- memoize :age
+
+ def random
+ rand(0)
+ end
+
+ memoize :name, :age, :random
private
def fetch_name_from_floppy
@@ -21,25 +25,34 @@ uses_mocha 'Memoizable' do
end
end
+ def setup
+ @person = Person.new
+ end
+
def test_memoization
- person = Person.new
- assert_equal "Josh", person.name
+ assert_equal "Josh", @person.name
+
+ @person.expects(:fetch_name_from_floppy).never
+ 2.times { assert_equal "Josh", @person.name }
+ end
- person.expects(:fetch_name_from_floppy).never
- 2.times { assert_equal "Josh", person.name }
+ def test_reloadable
+ random = @person.random
+ assert_equal random, @person.random
+ assert_not_equal random, @person.random(:reload)
end
def test_memoized_methods_are_frozen
- person = Person.new
- person.freeze
- assert_equal "Josh", person.name
- assert_equal true, person.name.frozen?
+ assert_equal true, @person.name.frozen?
+
+ @person.freeze
+ assert_equal "Josh", @person.name
+ assert_equal true, @person.name.frozen?
end
def test_memoization_frozen_with_nil_value
- person = Person.new
- person.freeze
- assert_equal nil, person.age
+ @person.freeze
+ assert_equal nil, @person.age
end
def test_double_memoization
--
cgit v1.2.3
From ef6f6625c91ea789a033799f649e4388e4a71045 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 18 Jul 2008 15:32:28 -0500
Subject: Changed ActiveSupport::Memoizable API to extend since it mainly adds
the memoize class method
---
actionpack/lib/action_view/renderable.rb | 4 +-
actionpack/lib/action_view/renderable_partial.rb | 2 +-
actionpack/lib/action_view/template.rb | 2 +-
activesupport/lib/active_support/memoizable.rb | 55 ++++++++++++------------
activesupport/test/memoizable_test.rb | 42 +++++++++++++++---
5 files changed, 67 insertions(+), 38 deletions(-)
diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb
index 46193670f3..5090d0160a 100644
--- a/actionpack/lib/action_view/renderable.rb
+++ b/actionpack/lib/action_view/renderable.rb
@@ -3,12 +3,12 @@ module ActionView
# NOTE: The template that this mixin is beening include into is frozen
# So you can not set or modify any instance variables
+ extend ActiveSupport::Memoizable
+
def self.included(base)
@@mutex = Mutex.new
end
- include ActiveSupport::Memoizable
-
def filename
'compiled-template'
end
diff --git a/actionpack/lib/action_view/renderable_partial.rb b/actionpack/lib/action_view/renderable_partial.rb
index fdb1a5e6a7..342850f0f0 100644
--- a/actionpack/lib/action_view/renderable_partial.rb
+++ b/actionpack/lib/action_view/renderable_partial.rb
@@ -3,7 +3,7 @@ module ActionView
# NOTE: The template that this mixin is beening include into is frozen
# So you can not set or modify any instance variables
- include ActiveSupport::Memoizable
+ extend ActiveSupport::Memoizable
def variable_name
name.sub(/\A_/, '').to_sym
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 304aec3a4c..eba42518d7 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -1,7 +1,7 @@
module ActionView #:nodoc:
class Template
extend TemplateHandlers
- include ActiveSupport::Memoizable
+ extend ActiveSupport::Memoizable
include Renderable
attr_accessor :filename, :load_path, :base_path, :name, :format, :extension
diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb
index c41feef4c7..59fecbecb1 100644
--- a/activesupport/lib/active_support/memoizable.rb
+++ b/activesupport/lib/active_support/memoizable.rb
@@ -1,37 +1,38 @@
module ActiveSupport
- module Memoizable
- def self.included(base) #:nodoc:
- base.extend(ClassMethods)
- end
-
- module ClassMethods
- def memoize(*symbols)
- symbols.each do |symbol|
- original_method = "unmemoized_#{symbol}"
- memoized_ivar = "@#{symbol}"
- raise "Already memoized #{symbol}" if instance_methods.map(&:to_s).include?(original_method)
-
- alias_method original_method, symbol
- class_eval <<-EOS, __FILE__, __LINE__
- def #{symbol}(reload = false)
- if !reload && defined? #{memoized_ivar}
- #{memoized_ivar}
- else
- #{memoized_ivar} = #{original_method}.freeze
- end
+ module Memoizable #:nodoc:
+ def self.extended(obj)
+ klass = obj.respond_to?(:class_eval) ? obj : obj.metaclass
+ klass.class_eval do
+ def freeze
+ methods.each do |method|
+ if m = method.to_s.match(/^unmemoized_(.*)/)
+ send(m[1])
end
- EOS
+ end
+ super
end
end
end
- def freeze
- methods.each do |method|
- if m = method.to_s.match(/\Aunmemoized_(.*)/)
- send(m[1])
- end
+ def memoize(*symbols)
+ symbols.each do |symbol|
+ original_method = "unmemoized_#{symbol}"
+ memoized_ivar = "@#{symbol}"
+
+ klass = respond_to?(:class_eval) ? self : self.metaclass
+ raise "Already memoized #{symbol}" if klass.instance_methods.map(&:to_s).include?(original_method)
+
+ klass.class_eval <<-EOS, __FILE__, __LINE__
+ alias_method :#{original_method}, :#{symbol}
+ def #{symbol}(reload = false)
+ if !reload && defined? #{memoized_ivar}
+ #{memoized_ivar}
+ else
+ #{memoized_ivar} = #{original_method}.freeze
+ end
+ end
+ EOS
end
- super
end
end
end
diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb
index b649b31455..79769631ad 100644
--- a/activesupport/test/memoizable_test.rb
+++ b/activesupport/test/memoizable_test.rb
@@ -3,21 +3,24 @@ require 'abstract_unit'
uses_mocha 'Memoizable' do
class MemoizableTest < Test::Unit::TestCase
class Person
- include ActiveSupport::Memoizable
+ extend ActiveSupport::Memoizable
def name
fetch_name_from_floppy
end
+ memoize :name
+
def age
nil
end
- def random
- rand(0)
+ def counter
+ @counter ||= 0
+ @counter += 1
end
- memoize :name, :age, :random
+ memoize :age, :counter
private
def fetch_name_from_floppy
@@ -37,9 +40,9 @@ uses_mocha 'Memoizable' do
end
def test_reloadable
- random = @person.random
- assert_equal random, @person.random
- assert_not_equal random, @person.random(:reload)
+ counter = @person.counter
+ assert_equal 1, @person.counter
+ assert_equal 2, @person.counter(:reload)
end
def test_memoized_methods_are_frozen
@@ -58,5 +61,30 @@ uses_mocha 'Memoizable' do
def test_double_memoization
assert_raise(RuntimeError) { Person.memoize :name }
end
+
+ class Company
+ def name
+ lookup_name
+ end
+
+ def lookup_name
+ "37signals"
+ end
+ end
+
+ def test_object_memoization
+ company = Company.new
+ company.extend ActiveSupport::Memoizable
+ company.memoize :name
+
+ assert_equal "37signals", company.name
+ # Mocha doesn't play well with frozen objects
+ company.metaclass.instance_eval { define_method(:lookup_name) { b00m } }
+ assert_equal "37signals", company.name
+
+ assert_equal true, company.name.frozen?
+ company.freeze
+ assert_equal true, company.name.frozen?
+ end
end
end
--
cgit v1.2.3
From d2ccb852d4e1f6f1b01e43f32213053ae3bef408 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 18 Jul 2008 16:00:20 -0500
Subject: Removed lagacy TemplateHandler#render API. Left in a legacy
TemplateHandler and Compilable stub so plugins will not have to change
anything.
---
actionpack/lib/action_view/base.rb | 4 +--
actionpack/lib/action_view/renderable.rb | 6 ++---
actionpack/lib/action_view/template_handler.rb | 23 ++++++-----------
actionpack/lib/action_view/template_handlers.rb | 1 -
.../action_view/template_handlers/compilable.rb | 20 ---------------
actionpack/test/controller/layout_test.rb | 12 ++-------
actionpack/test/template/render_test.rb | 30 ++++------------------
7 files changed, 20 insertions(+), 76 deletions(-)
delete mode 100644 actionpack/lib/action_view/template_handlers/compilable.rb
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index a6872b1a47..ae6b284854 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -379,8 +379,8 @@ module ActionView #:nodoc:
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
end
- def execute(template, local_assigns = {})
- send(template.method(local_assigns), local_assigns) do |*names|
+ def execute(method, local_assigns = {})
+ send(method, local_assigns) do |*names|
instance_variable_get "@content_for_#{names.first || 'layout'}"
end
end
diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb
index 5090d0160a..4f865cbced 100644
--- a/actionpack/lib/action_view/renderable.rb
+++ b/actionpack/lib/action_view/renderable.rb
@@ -19,7 +19,7 @@ module ActionView
memoize :handler
def compiled_source
- handler.new(nil).compile(self) if handler.compilable?
+ handler.call(self)
end
memoize :compiled_source
@@ -27,8 +27,8 @@ module ActionView
view._first_render ||= self
view._last_render = self
view.send(:evaluate_assigns)
- compile(local_assigns) if handler.compilable?
- handler.new(view).render(self, local_assigns)
+ compile(local_assigns)
+ view.send(:execute, method(local_assigns), local_assigns)
end
def method(local_assigns)
diff --git a/actionpack/lib/action_view/template_handler.rb b/actionpack/lib/action_view/template_handler.rb
index e2dd305f93..d7e7c9b199 100644
--- a/actionpack/lib/action_view/template_handler.rb
+++ b/actionpack/lib/action_view/template_handler.rb
@@ -1,21 +1,14 @@
-module ActionView
- class TemplateHandler
- def self.compilable?
- false
- end
-
- def initialize(view)
- @view = view
- end
+# Legacy TemplateHandler stub
- def render(template, local_assigns = {})
- end
-
- def compile(template)
+module ActionView
+ module TemplateHandlers
+ module Compilable
end
+ end
- def compilable?
- self.class.compilable?
+ class TemplateHandler
+ def self.call(template)
+ new.compile(template)
end
end
end
diff --git a/actionpack/lib/action_view/template_handlers.rb b/actionpack/lib/action_view/template_handlers.rb
index 1471e99e01..6c8aa4c2a7 100644
--- a/actionpack/lib/action_view/template_handlers.rb
+++ b/actionpack/lib/action_view/template_handlers.rb
@@ -1,5 +1,4 @@
require 'action_view/template_handler'
-require 'action_view/template_handlers/compilable'
require 'action_view/template_handlers/builder'
require 'action_view/template_handlers/erb'
require 'action_view/template_handlers/rjs'
diff --git a/actionpack/lib/action_view/template_handlers/compilable.rb b/actionpack/lib/action_view/template_handlers/compilable.rb
deleted file mode 100644
index a0ebaefeef..0000000000
--- a/actionpack/lib/action_view/template_handlers/compilable.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module ActionView
- module TemplateHandlers
- module Compilable
- def self.included(base)
- base.extend ClassMethod
- end
-
- module ClassMethod
- # If a handler is mixin this module, set compilable to true
- def compilable?
- true
- end
- end
-
- def render(template, local_assigns = {})
- @view.send(:execute, template, local_assigns)
- end
- end
- end
-end
diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb
index 92b6aa4f2f..72c01a9102 100644
--- a/actionpack/test/controller/layout_test.rb
+++ b/actionpack/test/controller/layout_test.rb
@@ -31,16 +31,8 @@ end
class MultipleExtensions < LayoutTest
end
-class MabView < ActionView::TemplateHandler
- def initialize(view)
- end
-
- def render(template, local_assigns)
- template.source
- end
-end
-
-ActionView::Template::register_template_handler :mab, MabView
+ActionView::Template::register_template_handler :mab,
+ lambda { |template| template.source.inspect }
class LayoutAutoDiscoveryTest < Test::Unit::TestCase
def setup
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index cc5b4900dc..b1af043099 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -94,38 +94,18 @@ class ViewRenderTest < Test::Unit::TestCase
assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :foo)
end
- class CustomHandler < ActionView::TemplateHandler
- def render(template, local_assigns)
- [template.source, local_assigns].inspect
- end
- end
-
- def test_render_inline_with_custom_type
- ActionView::Template.register_template_handler :foo, CustomHandler
- assert_equal '["Hello, World!", {}]', @view.render(:inline => "Hello, World!", :type => :foo)
- end
-
- def test_render_inline_with_locals_and_custom_type
- ActionView::Template.register_template_handler :foo, CustomHandler
- assert_equal '["Hello, <%= name %>!", {:name=>"Josh"}]', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
- end
-
- class CompilableCustomHandler < ActionView::TemplateHandler
- include ActionView::TemplateHandlers::Compilable
-
- def compile(template)
- "@output_buffer = ''\n" +
- "@output_buffer << 'source: #{template.source.inspect}'\n"
- end
+ CustomHandler = lambda do |template|
+ "@output_buffer = ''\n" +
+ "@output_buffer << 'source: #{template.source.inspect}'\n"
end
def test_render_inline_with_compilable_custom_type
- ActionView::Template.register_template_handler :foo, CompilableCustomHandler
+ ActionView::Template.register_template_handler :foo, CustomHandler
assert_equal 'source: "Hello, World!"', @view.render(:inline => "Hello, World!", :type => :foo)
end
def test_render_inline_with_locals_and_compilable_custom_type
- ActionView::Template.register_template_handler :foo, CompilableCustomHandler
+ ActionView::Template.register_template_handler :foo, CustomHandler
assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
end
end
--
cgit v1.2.3
From c3d1fda555c4bd5f8821d830c685ae5d0e7e52d0 Mon Sep 17 00:00:00 2001
From: Tom Ward
Date: Fri, 18 Jul 2008 20:14:12 -0500
Subject: Set the response content type to that of found template if not
explicitly set elsewhere [#444 state:resolved]
Signed-off-by: Joshua Peek
---
actionpack/lib/action_view/base.rb | 6 ++++++
actionpack/lib/action_view/renderable.rb | 5 ++++-
actionpack/lib/action_view/template.rb | 5 +++++
actionpack/lib/action_view/template_handlers/builder.rb | 3 +--
actionpack/test/controller/render_test.rb | 15 ++++++++++-----
.../test/fixtures/test/implicit_content_type.atom.builder | 2 ++
6 files changed, 28 insertions(+), 8 deletions(-)
create mode 100644 actionpack/test/fixtures/test/implicit_content_type.atom.builder
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index ae6b284854..fe51af62e6 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -379,6 +379,12 @@ module ActionView #:nodoc:
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
end
+ def set_controller_content_type(content_type)
+ if controller.respond_to?(:response)
+ controller.response.content_type ||= content_type
+ end
+ end
+
def execute(method, local_assigns = {})
send(method, local_assigns) do |*names|
instance_variable_get "@content_for_#{names.first || 'layout'}"
diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb
index 4f865cbced..2b825ac4e9 100644
--- a/actionpack/lib/action_view/renderable.rb
+++ b/actionpack/lib/action_view/renderable.rb
@@ -24,10 +24,13 @@ module ActionView
memoize :compiled_source
def render(view, local_assigns = {})
+ compile(local_assigns)
+
view._first_render ||= self
view._last_render = self
+
view.send(:evaluate_assigns)
- compile(local_assigns)
+ view.send(:set_controller_content_type, mime_type) if respond_to?(:mime_type)
view.send(:execute, method(local_assigns), local_assigns)
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index eba42518d7..1f528dd900 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -22,6 +22,11 @@ module ActionView #:nodoc:
end
memoize :format_and_extension
+ def mime_type
+ Mime::Type.lookup_by_extension(format) if format
+ end
+ memoize :mime_type
+
def path
[base_path, [name, format, extension].compact.join('.')].compact.join('/')
end
diff --git a/actionpack/lib/action_view/template_handlers/builder.rb b/actionpack/lib/action_view/template_handlers/builder.rb
index 335ec1abb4..7d24a5c423 100644
--- a/actionpack/lib/action_view/template_handlers/builder.rb
+++ b/actionpack/lib/action_view/template_handlers/builder.rb
@@ -6,8 +6,7 @@ module ActionView
include Compilable
def compile(template)
- # ActionMailer does not have a response
- "controller.respond_to?(:response) && controller.response.content_type ||= Mime::XML;" +
+ "set_controller_content_type(Mime::XML);" +
"xml = ::Builder::XmlMarkup.new(:indent => 2);" +
"self.output_buffer = xml.target!;" +
template.source +
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 041c54c7fd..76832f5713 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -197,11 +197,11 @@ class TestController < ActionController::Base
def render_alternate_default
# For this test, the method "default_render" is overridden:
- @alternate_default_render = lambda {
- render :update do |page|
- page.replace :foo, :partial => 'partial'
- end
- }
+ @alternate_default_render = lambda do
+ render :update do |page|
+ page.replace :foo, :partial => 'partial'
+ end
+ end
end
def rescue_action(e) raise end
@@ -467,6 +467,11 @@ class RenderTest < Test::Unit::TestCase
get :render_xml_with_custom_content_type
assert_equal "application/atomsvc+xml", @response.content_type
end
+
+ def test_should_use_implicit_content_type
+ get :implicit_content_type, :format => 'atom'
+ assert_equal Mime::ATOM, @response.content_type
+ end
end
class EtagRenderTest < Test::Unit::TestCase
diff --git a/actionpack/test/fixtures/test/implicit_content_type.atom.builder b/actionpack/test/fixtures/test/implicit_content_type.atom.builder
new file mode 100644
index 0000000000..2fcb32d247
--- /dev/null
+++ b/actionpack/test/fixtures/test/implicit_content_type.atom.builder
@@ -0,0 +1,2 @@
+xml.atom do
+end
--
cgit v1.2.3
From d39485078ec56e25a96e97d44b53498d8a1c7426 Mon Sep 17 00:00:00 2001
From: Tom Ward
Date: Fri, 18 Jul 2008 20:19:03 -0500
Subject: Raise ArgumentError if an invalid method is specified as part of a
route's conditions. Also raise an error if HEAD is specified as the method,
as rails routes all HEAD requests through the equivalent GET, though doesn't
return the response body [#182 state:resolved]
Signed-off-by: Joshua Peek
---
actionpack/lib/action_controller/resources.rb | 9 +++++----
actionpack/lib/action_controller/routing/builder.rb | 15 +++++++++++++++
actionpack/test/controller/resources_test.rb | 20 ++++++++++++++++++++
actionpack/test/controller/routing_test.rb | 16 ++++++++++++++++
4 files changed, 56 insertions(+), 4 deletions(-)
diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb
index b11aa5625b..0614b9a4d9 100644
--- a/actionpack/lib/action_controller/resources.rb
+++ b/actionpack/lib/action_controller/resources.rb
@@ -307,13 +307,13 @@ module ActionController
# map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
#
# You may also use :name_prefix to override the generic named routes in a nested resource:
- #
+ #
# map.resources :articles do |article|
# article.resources :comments, :name_prefix => nil
- # end
- #
+ # end
+ #
# This will yield named resources like so:
- #
+ #
# comments_url(@article)
# comment_url(@article, @comment)
#
@@ -559,6 +559,7 @@ module ActionController
def action_options_for(action, resource, method = nil)
default_options = { :action => action.to_s }
require_id = !resource.kind_of?(SingletonResource)
+
case default_options[:action]
when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb
index b8323847fd..912999d845 100644
--- a/actionpack/lib/action_controller/routing/builder.rb
+++ b/actionpack/lib/action_controller/routing/builder.rb
@@ -76,6 +76,8 @@ module ActionController
defaults = (options.delete(:defaults) || {}).dup
conditions = (options.delete(:conditions) || {}).dup
+ validate_route_conditions(conditions)
+
path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
options.each do |key, value|
hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
@@ -198,6 +200,19 @@ module ActionController
route
end
+
+ private
+ def validate_route_conditions(conditions)
+ if method = conditions[:method]
+ if method == :head
+ raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
+ end
+
+ unless HTTP_METHODS.include?(method.to_sym)
+ raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
+ end
+ end
+ end
end
end
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 0f7924649a..e153b0cc98 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -516,6 +516,26 @@ class ResourcesTest < Test::Unit::TestCase
end
end
+ def test_should_not_allow_invalid_head_method_for_member_routes
+ with_routing do |set|
+ set.draw do |map|
+ assert_raises(ArgumentError) do
+ map.resources :messages, :member => {:something => :head}
+ end
+ end
+ end
+ end
+
+ def test_should_not_allow_invalid_http_methods_for_member_routes
+ with_routing do |set|
+ set.draw do |map|
+ assert_raises(ArgumentError) do
+ map.resources :messages, :member => {:something => :invalid}
+ end
+ end
+ end
+ end
+
def test_resource_action_separator
with_routing do |set|
set.draw do |map|
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index c5ccb71582..079189d7b3 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1801,6 +1801,22 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
end
end
+ def test_route_requirements_with_invalid_http_method_is_invalid
+ assert_raises ArgumentError do
+ set.draw do |map|
+ map.connect 'valid/route', :controller => 'pages', :action => 'show', :conditions => {:method => :invalid}
+ end
+ end
+ end
+
+ def test_route_requirements_with_head_method_condition_is_invalid
+ assert_raises ArgumentError do
+ set.draw do |map|
+ map.connect 'valid/route', :controller => 'pages', :action => 'show', :conditions => {:method => :head}
+ end
+ end
+ end
+
def test_non_path_route_requirements_match_all
set.draw do |map|
map.connect 'page/37s', :controller => 'pages', :action => 'show', :name => /(jamis|david)/
--
cgit v1.2.3
From c609be45966316bb107e0bad2b0935ac4a0d0c41 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Fri, 18 Jul 2008 23:30:36 -0500
Subject: Ruby 1.9: Ensure Memoizable#freeze is only overriden once to avoid an
endless loop
---
activesupport/lib/active_support/memoizable.rb | 26 ++++++++++++--------------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb
index 59fecbecb1..f7cd73d39c 100644
--- a/activesupport/lib/active_support/memoizable.rb
+++ b/activesupport/lib/active_support/memoizable.rb
@@ -1,19 +1,5 @@
module ActiveSupport
module Memoizable #:nodoc:
- def self.extended(obj)
- klass = obj.respond_to?(:class_eval) ? obj : obj.metaclass
- klass.class_eval do
- def freeze
- methods.each do |method|
- if m = method.to_s.match(/^unmemoized_(.*)/)
- send(m[1])
- end
- end
- super
- end
- end
- end
-
def memoize(*symbols)
symbols.each do |symbol|
original_method = "unmemoized_#{symbol}"
@@ -23,6 +9,18 @@ module ActiveSupport
raise "Already memoized #{symbol}" if klass.instance_methods.map(&:to_s).include?(original_method)
klass.class_eval <<-EOS, __FILE__, __LINE__
+ unless instance_methods.map(&:to_s).include?("freeze_without_memoizable")
+ alias_method :freeze_without_memoizable, :freeze
+ def freeze
+ methods.each do |method|
+ if m = method.to_s.match(/^unmemoized_(.*)/)
+ send(m[1])
+ end
+ end
+ freeze_without_memoizable
+ end
+ end
+
alias_method :#{original_method}, :#{symbol}
def #{symbol}(reload = false)
if !reload && defined? #{memoized_ivar}
--
cgit v1.2.3
From 108ed4a5660d4ebdc52563250463c762e3f27988 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 00:24:02 -0500
Subject: Ruby 1.9: Strip encoding from ERB source since you can not change
character encoding during a method
---
actionpack/lib/action_view/template_handlers/erb.rb | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/actionpack/lib/action_view/template_handlers/erb.rb b/actionpack/lib/action_view/template_handlers/erb.rb
index 2f2febaa52..3def949f1e 100644
--- a/actionpack/lib/action_view/template_handlers/erb.rb
+++ b/actionpack/lib/action_view/template_handlers/erb.rb
@@ -48,8 +48,11 @@ module ActionView
self.erb_trim_mode = '-'
def compile(template)
- src = ::ERB.new(template.source, nil, erb_trim_mode, '@output_buffer').src
- "__in_erb_template=true;#{src}"
+ src = ::ERB.new("<% __in_erb_template=true %>#{template.source}", nil, erb_trim_mode, '@output_buffer').src
+
+ # Ruby 1.9 prepends an encoding to the source. However this is
+ # useless because you can only set an encoding on the first line
+ RUBY_VERSION >= '1.9' ? src.sub(/\A#coding:.*\n/, '') : src
end
end
end
--
cgit v1.2.3
From cab168ac9bbe24e5842fb7677d3fac820ddbc18c Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 00:46:02 -0500
Subject: Ruby 1.9: Fixed regexp warning by replacing nested repeat operator +
and ? with '*'
---
actionpack/lib/action_view/helpers/text_helper.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 3e3452b615..9342b38680 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -468,7 +468,7 @@ module ActionView
[-\w]+ # subdomain or domain
(?:\.[-\w]+)* # remaining subdomains or domain
(?::\d+)? # port
- (?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))+)?)* # path
+ (?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$])))*)* # path
(?:\?[\w\+@%&=.;-]+)? # query string
(?:\#[\w\-]*)? # trailing anchor
)
--
cgit v1.2.3
From f2059393481ceb632abc7a9d92670e409020d5bd Mon Sep 17 00:00:00 2001
From: Tom Ward
Date: Sat, 19 Jul 2008 09:58:09 +0100
Subject: Ensure checked value is a string when validating case-sensitive
uniqueness [#361 state:resolved]
Signed-off-by: Pratik Naik
---
activerecord/lib/active_record/validations.rb | 2 +-
activerecord/test/cases/validations_test.rb | 9 +++++++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 3eec1305e4..b957ee3b9e 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -664,7 +664,7 @@ module ActiveRecord
# As MySQL/Postgres don't have case sensitive SELECT queries, we try to find duplicate
# column in ruby when case sensitive option
if configuration[:case_sensitive] && finder_class.columns_hash[attr_name.to_s].text?
- found = results.any? { |a| a[attr_name.to_s] == value }
+ found = results.any? { |a| a[attr_name.to_s] == value.to_s }
end
if found
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 60b00b3e8f..4b2d28c80b 100755
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -477,6 +477,15 @@ class ValidationsTest < ActiveRecord::TestCase
assert_not_equal "has already been taken", t3.errors.on(:title)
end
+ def test_validate_case_sensitive_uniqueness_with_attribute_passed_as_integer
+ Topic.validates_uniqueness_of(:title, :case_sensitve => true)
+ t = Topic.create!('title' => 101)
+
+ t2 = Topic.new('title' => 101)
+ assert !t2.valid?
+ assert t2.errors.on(:title)
+ end
+
def test_validate_uniqueness_with_non_standard_table_names
i1 = WarehouseThing.create(:value => 1000)
assert !i1.valid?, "i1 should not be valid"
--
cgit v1.2.3
From d84d99a8f7dc672b050a6ab891c1680a323a7c97 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 10:52:30 -0500
Subject: Undefine old run method
---
activesupport/lib/active_support/testing/setup_and_teardown.rb | 3 +++
1 file changed, 3 insertions(+)
diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb
index 21d71eb92a..a514b61fea 100644
--- a/activesupport/lib/active_support/testing/setup_and_teardown.rb
+++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb
@@ -15,12 +15,15 @@ module ActiveSupport
define_callbacks :setup, :teardown
if defined?(::Mini)
+ undef_method :run
alias_method :run, :run_with_callbacks_and_miniunit
else
begin
require 'mocha'
+ undef_method :run
alias_method :run, :run_with_callbacks_and_mocha
rescue LoadError
+ undef_method :run
alias_method :run, :run_with_callbacks_and_testunit
end
end
--
cgit v1.2.3
From 8c2e839e5a0fb1662ae867c70114c3fc91850a55 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 11:04:23 -0500
Subject: Fix some warnings in i18n lib
---
activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n.rb | 6 +++---
.../lib/active_support/vendor/i18n-0.0.1/lib/i18n/backend/simple.rb | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n.rb
index 2185194da9..1bb65263a3 100755
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n.rb
@@ -53,7 +53,7 @@ module I18n
# storage. Decoupled for backends like a db backend that persist their
# translations, so the backend can decide whether/when to yield or not.
def populate(&block)
- backend.populate &block
+ backend.populate(&block)
end
# Stores translations for the given locale in the backend.
@@ -173,8 +173,8 @@ module I18n
# keys are Symbols.
def normalize_translation_keys(locale, key, scope)
keys = [locale] + Array(scope) + [key]
- keys = keys.map{|key| key.to_s.split(/\./) }
- keys.flatten.map{|key| key.to_sym}
+ keys = keys.map{|k| k.to_s.split(/\./) }
+ keys.flatten.map{|k| k.to_sym}
end
end
end
diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n/backend/simple.rb
index 284f2bfcbd..b8be1cecfb 100644
--- a/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n/backend/simple.rb
+++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n/backend/simple.rb
@@ -23,7 +23,7 @@ module I18n
def translate(locale, key, options = {})
raise InvalidLocale.new(locale) if locale.nil?
- return key.map{|key| translate locale, key, options } if key.is_a? Array
+ return key.map{|k| translate locale, k, options } if key.is_a? Array
reserved = :scope, :default
count, scope, default = options.values_at(:count, *reserved)
@@ -66,7 +66,7 @@ module I18n
def lookup(locale, key, scope = [])
return unless key
keys = I18n.send :normalize_translation_keys, locale, key, scope
- keys.inject(@@translations){|result, key| result[key.to_sym] or return nil }
+ keys.inject(@@translations){|result, k| result[k.to_sym] or return nil }
end
# Evaluates a default translation.
--
cgit v1.2.3
From b74b97fef5d94f91d6fbf9aec20516c7fe4ce24d Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 11:14:12 -0500
Subject: Update uses_mocha in ActionMailer and ActiveResource
---
actionmailer/test/abstract_unit.rb | 18 +++++++++++++-----
activeresource/test/abstract_unit.rb | 22 +++++++++++++---------
2 files changed, 26 insertions(+), 14 deletions(-)
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 11058a770d..107b2e8bbe 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -30,12 +30,20 @@ class Net::SMTP
end
end
-# Wrap tests that use Mocha and skip if unavailable.
-def uses_mocha(test_name)
- gem 'mocha', ">=0.9.0"
+def uses_gem(gem_name, test_name, version = '> 0')
+ require 'rubygems'
+ gem gem_name.to_s, version
+ require gem_name.to_s
yield
-rescue Gem::LoadError
- $stderr.puts "Skipping #{test_name} tests (Mocha >= 0.5 is required). `gem install mocha` and try again."
+rescue LoadError
+ $stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again."
+end
+
+# Wrap tests that use Mocha and skip if unavailable.
+unless defined? uses_mocha
+ def uses_mocha(test_name, &block)
+ uses_gem('mocha', test_name, '>= 0.5.5', &block)
+ end
end
def set_delivery_method(delivery_method)
diff --git a/activeresource/test/abstract_unit.rb b/activeresource/test/abstract_unit.rb
index 615a6d9222..e612412033 100644
--- a/activeresource/test/abstract_unit.rb
+++ b/activeresource/test/abstract_unit.rb
@@ -9,14 +9,18 @@ require 'setter_trap'
ActiveResource::Base.logger = Logger.new("#{File.dirname(__FILE__)}/debug.log")
+def uses_gem(gem_name, test_name, version = '> 0')
+ require 'rubygems'
+ gem gem_name.to_s, version
+ require gem_name.to_s
+ yield
+rescue LoadError
+ $stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again."
+end
+
# Wrap tests that use Mocha and skip if unavailable.
-def uses_mocha(test_name)
- unless Object.const_defined?(:Mocha)
- require 'mocha'
- require 'stubba'
+unless defined? uses_mocha
+ def uses_mocha(test_name, &block)
+ uses_gem('mocha', test_name, '>= 0.5.5', &block)
end
- yield
-rescue LoadError => load_error
- raise unless load_error.message =~ /mocha/i
- $stderr.puts "Skipping #{test_name} tests. `gem install mocha` and try again."
-end
\ No newline at end of file
+end
--
cgit v1.2.3
From 3fdd1acab61a46ab4823d63ad0bab4879d2bb446 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 11:30:15 -0500
Subject: Dropped SQLite 2 from default test runner
---
activerecord/Rakefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 60b17e02b9..983528aff7 100755
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -30,7 +30,7 @@ desc 'Run mysql, sqlite, and postgresql tests by default'
task :default => :test
desc 'Run mysql, sqlite, and postgresql tests'
-task :test => %w(test_mysql test_sqlite test_sqlite3 test_postgresql)
+task :test => %w(test_mysql test_sqlite3 test_postgresql)
for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase )
Rake::TestTask.new("test_#{adapter}") { |t|
--
cgit v1.2.3
From 576cae004342899b0506a7834edc524a02a7d9ef Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 11:34:32 -0500
Subject: Stub out timestamped_migrations in generator tests
---
railties/test/generators/generator_test_helper.rb | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/railties/test/generators/generator_test_helper.rb b/railties/test/generators/generator_test_helper.rb
index 80d5b145be..0901b215e4 100644
--- a/railties/test/generators/generator_test_helper.rb
+++ b/railties/test/generators/generator_test_helper.rb
@@ -5,9 +5,10 @@ require 'fileutils'
module ActiveRecord
class Base
class << self
- attr_accessor :pluralize_table_names
+ attr_accessor :pluralize_table_names, :timestamped_migrations
end
self.pluralize_table_names = true
+ self.timestamped_migrations = true
end
module ConnectionAdapters
--
cgit v1.2.3
From e0d7bace4ecb9152fac112e809af521e36fbc6a5 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 11:42:27 -0500
Subject: Prefer Mongrel over Thin [#658 state:resolved]
---
railties/lib/commands/server.rb | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/railties/lib/commands/server.rb b/railties/lib/commands/server.rb
index 7306c248fb..15f417b5be 100644
--- a/railties/lib/commands/server.rb
+++ b/railties/lib/commands/server.rb
@@ -23,10 +23,10 @@ server = case ARGV.first
when "lighttpd", "mongrel", "new_mongrel", "webrick", "thin"
ARGV.shift
else
- if defined?(Thin)
- "thin"
- elsif defined?(Mongrel)
+ if defined?(Mongrel)
"mongrel"
+ elsif defined?(Thin)
+ "thin"
elsif RUBY_PLATFORM !~ /(:?mswin|mingw)/ && !silence_stderr { `lighttpd -version` }.blank? && defined?(FCGI)
"lighttpd"
else
--
cgit v1.2.3
From 746122735269b9077c7d5d99d88e8b22d88ad8d5 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 12:23:08 -0500
Subject: Ruby 1.9: Call join on template_root instead of to_s
---
actionmailer/lib/action_mailer/base.rb | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 5a71935009..e4920f0c86 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -529,7 +529,7 @@ module ActionMailer #:nodoc:
end
def render_message(method_name, body)
- render :file => method_name, :body => body, :use_full_path => true
+ render :file => method_name, :body => body
end
def render(opts)
@@ -537,12 +537,11 @@ module ActionMailer #:nodoc:
if opts[:file] && opts[:file] !~ /\//
opts[:file] = "#{mailer_name}/#{opts[:file]}"
end
- opts[:use_full_path] = true
initialize_template_class(body).render(opts)
end
def template_path
- "#{template_root}/#{mailer_name}"
+ "#{template_root.join}/#{mailer_name}"
end
def initialize_template_class(assigns)
--
cgit v1.2.3
From e23156e87bd32206a5ea529fbecc04fdf37d7bc2 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 12:35:42 -0500
Subject: Only create a path for ActionMailer template root instead of a path
set. Better fix than 7461227
---
actionmailer/lib/action_mailer/base.rb | 4 ++--
actionmailer/test/mail_service_test.rb | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index e4920f0c86..bf60e2f3d5 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -426,7 +426,7 @@ module ActionMailer #:nodoc:
end
def template_root=(root)
- write_inheritable_attribute(:template_root, ActionView::PathSet.new(Array(root)))
+ write_inheritable_attribute(:template_root, ActionView::PathSet::Path.new(root))
end
end
@@ -541,7 +541,7 @@ module ActionMailer #:nodoc:
end
def template_path
- "#{template_root.join}/#{mailer_name}"
+ "#{template_root}/#{mailer_name}"
end
def initialize_template_class(assigns)
diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb
index 7f4a8817ca..e5ecb0e254 100755
--- a/actionmailer/test/mail_service_test.rb
+++ b/actionmailer/test/mail_service_test.rb
@@ -942,13 +942,13 @@ end # uses_mocha
class InheritableTemplateRootTest < Test::Unit::TestCase
def test_attr
expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
- assert_equal [expected], FunkyPathMailer.template_root.map(&:to_s)
+ assert_equal expected, FunkyPathMailer.template_root
sub = Class.new(FunkyPathMailer)
sub.template_root = 'test/path'
- assert_equal ['test/path'], sub.template_root.map(&:to_s)
- assert_equal [expected], FunkyPathMailer.template_root.map(&:to_s)
+ assert_equal 'test/path', sub.template_root
+ assert_equal expected, FunkyPathMailer.template_root
end
end
--
cgit v1.2.3
From 938caf4e6b2448b45939d36824794ea0aa5e1804 Mon Sep 17 00:00:00 2001
From: Clemens Kofler
Date: Sat, 19 Jul 2008 12:40:30 -0500
Subject: Removed unused option from FormHelper#fields_for [#641
state:resolved]
Signed-off-by: Joshua Peek
---
actionpack/lib/action_view/helpers/form_helper.rb | 4 ----
1 file changed, 4 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index fa26aa4640..4fa46d9ee3 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -304,10 +304,6 @@ module ActionView
when String, Symbol
object_name = record_or_name_or_array
object = args.first
- when Array
- object = record_or_name_or_array.last
- object_name = ActionController::RecordIdentifier.singular_class_name(object)
- apply_form_for_options!(record_or_name_or_array, options)
else
object = record_or_name_or_array
object_name = ActionController::RecordIdentifier.singular_class_name(object)
--
cgit v1.2.3
From c98692abcfd3576ee5fcde3910330d1eb39a18a5 Mon Sep 17 00:00:00 2001
From: Clemens Kofler
Date: Sat, 19 Jul 2008 13:06:43 -0500
Subject: Removed handling of string parameter in link_to to have all URL
generation done by url_for
Signed-off-by: Joshua Peek
---
actionpack/lib/action_view/helpers/url_helper.rb | 93 +++++++++++++-----------
actionpack/test/template/url_helper_test.rb | 11 ++-
2 files changed, 60 insertions(+), 44 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 94e1f1d33a..f31502d99d 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -3,8 +3,8 @@ require 'action_view/helpers/javascript_helper'
module ActionView
module Helpers #:nodoc:
# Provides a set of methods for making links and getting URLs that
- # depend on the routing subsystem (see ActionController::Routing).
- # This allows you to use the same format for links in views
+ # depend on the routing subsystem (see ActionController::Routing).
+ # This allows you to use the same format for links in views
# and controllers.
module UrlHelper
include JavaScriptHelper
@@ -33,8 +33,8 @@ module ActionView
#
# If you instead of a hash pass a record (like an Active Record or Active Resource) as the options parameter,
# you'll trigger the named route for that record. The lookup will happen on the name of the class. So passing
- # a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as
- # admin_workshop_path you'll have to call that explicitly (it's impossible for url_for to guess that route).
+ # a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as
+ # admin_workshop_path you'll have to call that explicitly (it's impossible for url_for to guess that route).
#
# ==== Examples
# <%= url_for(:action => 'index') %>
@@ -62,19 +62,33 @@ module ActionView
# <%= url_for(@workshop) %>
# # calls @workshop.to_s
# # => /workshops/5
+ #
+ # <%= url_for("http://www.example.com") %>
+ # # => http://www.example.com
+ #
+ # <%= url_for(:back) %>
+ # # if request.env["HTTP_REFERER"] is set to "http://www.example.com"
+ # # => http://www.example.com
+ #
+ # <%= url_for(:back) %>
+ # # if request.env["HTTP_REFERER"] is not set or is blank
+ # # => javascript:history.back()
def url_for(options = {})
options ||= {}
- case options
+ url = case options
+ when String
+ escape = true
+ options
when Hash
options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
escape = options.key?(:escape) ? options.delete(:escape) : true
- url = @controller.send(:url_for, options)
- when String
- escape = true
- url = options
+ @controller.send(:url_for, options)
+ when :back
+ escape = false
+ @controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
else
escape = false
- url = polymorphic_path(options)
+ polymorphic_path(options)
end
escape ? escape_once(url) : url
@@ -116,8 +130,8 @@ module ActionView
#
# Note that if the user has JavaScript disabled, the request will fall back
# to using GET. If :href => '#' is used and the user has JavaScript disabled
- # clicking the link will have no effect. If you are relying on the POST
- # behavior, your should check for it in your controller's action by using the
+ # clicking the link will have no effect. If you are relying on the POST
+ # behavior, your should check for it in your controller's action by using the
# request object's methods for post? , delete? or put? .
#
# You can mix and match the +html_options+ with the exception of
@@ -141,8 +155,8 @@ module ActionView
#
# link_to "Profile", :controller => "profiles", :action => "show", :id => @profile
# # => Profile
- #
- # Similarly,
+ #
+ # Similarly,
#
# link_to "Profiles", profiles_path
# # => Profiles
@@ -197,9 +211,9 @@ module ActionView
# # => View Image
#
# link_to "Delete Image", @image, :confirm => "Are you sure?", :method => :delete
- # # => Delete Image
def link_to(*args, &block)
if block_given?
@@ -211,14 +225,7 @@ module ActionView
options = args.second || {}
html_options = args.third
- url = case options
- when String
- options
- when :back
- @controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
- else
- self.url_for(options)
- end
+ url = url_for(options)
if html_options
html_options = html_options.stringify_keys
@@ -228,7 +235,7 @@ module ActionView
else
tag_options = nil
end
-
+
href_attr = "href=\"#{url}\"" unless href
"#{name || url} "
end
@@ -260,7 +267,7 @@ module ActionView
# * :confirm - This will add a JavaScript confirm
# prompt with the question specified. If the user accepts, the link is
# processed normally, otherwise no action is taken.
- #
+ #
# ==== Examples
# <%= button_to "New", :action => "new" %>
# # => "", button_to("Hello", "http://www.example.com")
@@ -419,7 +429,6 @@ class LinkToUnlessCurrentWithControllerTest < ActionView::TestCase
end
end
-
class Workshop
attr_accessor :id, :new_record
--
cgit v1.2.3
From 706425e154a2a2581195c98309f30a18a0002a58 Mon Sep 17 00:00:00 2001
From: David Heinemeier Hansson
Date: Sat, 19 Jul 2008 13:56:38 -0500
Subject: Update Prototype to 1.6.0.2 (Patrick Joyce) [#599 status:committed]
---
actionpack/CHANGELOG | 2 +
.../action_view/helpers/javascripts/prototype.js | 304 ++++++++++-----------
railties/html/javascripts/prototype.js | 304 ++++++++++-----------
3 files changed, 302 insertions(+), 308 deletions(-)
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index f6432fe0be..ebe4c047b8 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* Update Prototype to 1.6.0.2 #599 [Patrick Joyce]
+
* Conditional GET utility methods. [Jeremy Kemper]
* etag!([:admin, post, current_user]) sets the ETag response header and returns head(:not_modified) if it matches the If-None-Match request header.
* last_modified!(post.updated_at) sets Last-Modified and returns head(:not_modified) if it's no later than If-Modified-Since.
diff --git a/actionpack/lib/action_view/helpers/javascripts/prototype.js b/actionpack/lib/action_view/helpers/javascripts/prototype.js
index 546f9fe449..2c70b8a7e8 100644
--- a/actionpack/lib/action_view/helpers/javascripts/prototype.js
+++ b/actionpack/lib/action_view/helpers/javascripts/prototype.js
@@ -1,5 +1,5 @@
-/* Prototype JavaScript framework, version 1.6.0.1
- * (c) 2005-2007 Sam Stephenson
+/* Prototype JavaScript framework, version 1.6.0.2
+ * (c) 2005-2008 Sam Stephenson
*
* Prototype is freely distributable under the terms of an MIT-style license.
* For details, see the Prototype web site: http://www.prototypejs.org/
@@ -7,7 +7,7 @@
*--------------------------------------------------------------------------*/
var Prototype = {
- Version: '1.6.0.1',
+ Version: '1.6.0.2',
Browser: {
IE: !!(window.attachEvent && !window.opera),
@@ -110,7 +110,7 @@ Object.extend(Object, {
try {
if (Object.isUndefined(object)) return 'undefined';
if (object === null) return 'null';
- return object.inspect ? object.inspect() : object.toString();
+ return object.inspect ? object.inspect() : String(object);
} catch (e) {
if (e instanceof RangeError) return '...';
throw e;
@@ -171,7 +171,8 @@ Object.extend(Object, {
},
isArray: function(object) {
- return object && object.constructor === Array;
+ return object != null && typeof object == "object" &&
+ 'splice' in object && 'join' in object;
},
isHash: function(object) {
@@ -578,7 +579,7 @@ var Template = Class.create({
}
return before + String.interpret(ctx);
- }.bind(this));
+ });
}
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
@@ -806,20 +807,20 @@ Object.extend(Enumerable, {
function $A(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
- var length = iterable.length, results = new Array(length);
+ var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
if (Prototype.Browser.WebKit) {
- function $A(iterable) {
+ $A = function(iterable) {
if (!iterable) return [];
if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
iterable.toArray) return iterable.toArray();
- var length = iterable.length, results = new Array(length);
+ var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
- }
+ };
}
Array.from = $A;
@@ -1298,7 +1299,7 @@ Ajax.Request = Class.create(Ajax.Base, {
var contentType = response.getHeader('Content-type');
if (this.options.evalJS == 'force'
- || (this.options.evalJS && contentType
+ || (this.options.evalJS && this.isSameOrigin() && contentType
&& contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
this.evalResponse();
}
@@ -1316,9 +1317,18 @@ Ajax.Request = Class.create(Ajax.Base, {
}
},
+ isSameOrigin: function() {
+ var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+ return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+ protocol: location.protocol,
+ domain: document.domain,
+ port: location.port ? ':' + location.port : ''
+ }));
+ },
+
getHeader: function(name) {
try {
- return this.transport.getResponseHeader(name);
+ return this.transport.getResponseHeader(name) || null;
} catch (e) { return null }
},
@@ -1391,7 +1401,8 @@ Ajax.Response = Class.create({
if (!json) return null;
json = decodeURIComponent(escape(json));
try {
- return json.evalJSON(this.request.options.sanitizeJSON);
+ return json.evalJSON(this.request.options.sanitizeJSON ||
+ !this.request.isSameOrigin());
} catch (e) {
this.request.dispatchException(e);
}
@@ -1404,7 +1415,8 @@ Ajax.Response = Class.create({
this.responseText.blank())
return null;
try {
- return this.responseText.evalJSON(options.sanitizeJSON);
+ return this.responseText.evalJSON(options.sanitizeJSON ||
+ !this.request.isSameOrigin());
} catch (e) {
this.request.dispatchException(e);
}
@@ -1608,24 +1620,28 @@ Element.Methods = {
Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
insertions = {bottom:insertions};
- var content, t, range;
+ var content, insert, tagName, childNodes;
- for (position in insertions) {
+ for (var position in insertions) {
content = insertions[position];
position = position.toLowerCase();
- t = Element._insertionTranslations[position];
+ insert = Element._insertionTranslations[position];
if (content && content.toElement) content = content.toElement();
if (Object.isElement(content)) {
- t.insert(element, content);
+ insert(element, content);
continue;
}
content = Object.toHTML(content);
- range = element.ownerDocument.createRange();
- t.initializeRange(element, range);
- t.insert(element, range.createContextualFragment(content.stripScripts()));
+ tagName = ((position == 'before' || position == 'after')
+ ? element.parentNode : element).tagName.toUpperCase();
+
+ childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+ if (position == 'top' || position == 'after') childNodes.reverse();
+ childNodes.each(insert.curry(element));
content.evalScripts.bind(content).defer();
}
@@ -1670,7 +1686,7 @@ Element.Methods = {
},
descendants: function(element) {
- return $(element).getElementsBySelector("*");
+ return $(element).select("*");
},
firstDescendant: function(element) {
@@ -1709,32 +1725,31 @@ Element.Methods = {
element = $(element);
if (arguments.length == 1) return $(element.parentNode);
var ancestors = element.ancestors();
- return expression ? Selector.findElement(ancestors, expression, index) :
- ancestors[index || 0];
+ return Object.isNumber(expression) ? ancestors[expression] :
+ Selector.findElement(ancestors, expression, index);
},
down: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return element.firstDescendant();
- var descendants = element.descendants();
- return expression ? Selector.findElement(descendants, expression, index) :
- descendants[index || 0];
+ return Object.isNumber(expression) ? element.descendants()[expression] :
+ element.select(expression)[index || 0];
},
previous: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
var previousSiblings = element.previousSiblings();
- return expression ? Selector.findElement(previousSiblings, expression, index) :
- previousSiblings[index || 0];
+ return Object.isNumber(expression) ? previousSiblings[expression] :
+ Selector.findElement(previousSiblings, expression, index);
},
next: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
var nextSiblings = element.nextSiblings();
- return expression ? Selector.findElement(nextSiblings, expression, index) :
- nextSiblings[index || 0];
+ return Object.isNumber(expression) ? nextSiblings[expression] :
+ Selector.findElement(nextSiblings, expression, index);
},
select: function() {
@@ -1860,7 +1875,8 @@ Element.Methods = {
do { ancestor = ancestor.parentNode; }
while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
}
- if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
+ if (nextAncestor && nextAncestor.sourceIndex)
+ return (e > a && e < nextAncestor.sourceIndex);
}
while (element = element.parentNode)
@@ -2004,7 +2020,7 @@ Element.Methods = {
if (element) {
if (element.tagName == 'BODY') break;
var p = Element.getStyle(element, 'position');
- if (p == 'relative' || p == 'absolute') break;
+ if (p !== 'static') break;
}
} while (element);
return Element._returnOffset(valueL, valueT);
@@ -2153,46 +2169,6 @@ Element._attributeTranslations = {
}
};
-
-if (!document.createRange || Prototype.Browser.Opera) {
- Element.Methods.insert = function(element, insertions) {
- element = $(element);
-
- if (Object.isString(insertions) || Object.isNumber(insertions) ||
- Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
- insertions = { bottom: insertions };
-
- var t = Element._insertionTranslations, content, position, pos, tagName;
-
- for (position in insertions) {
- content = insertions[position];
- position = position.toLowerCase();
- pos = t[position];
-
- if (content && content.toElement) content = content.toElement();
- if (Object.isElement(content)) {
- pos.insert(element, content);
- continue;
- }
-
- content = Object.toHTML(content);
- tagName = ((position == 'before' || position == 'after')
- ? element.parentNode : element).tagName.toUpperCase();
-
- if (t.tags[tagName]) {
- var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
- if (position == 'top' || position == 'after') fragments.reverse();
- fragments.each(pos.insert.curry(element));
- }
- else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
-
- content.evalScripts.bind(content).defer();
- }
-
- return element;
- };
-}
-
if (Prototype.Browser.Opera) {
Element.Methods.getStyle = Element.Methods.getStyle.wrap(
function(proceed, element, style) {
@@ -2237,12 +2213,31 @@ if (Prototype.Browser.Opera) {
}
else if (Prototype.Browser.IE) {
- $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
+ // IE doesn't report offsets correctly for static elements, so we change them
+ // to "relative" to get the values, then change them back.
+ Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
+ function(proceed, element) {
+ element = $(element);
+ var position = element.getStyle('position');
+ if (position !== 'static') return proceed(element);
+ element.setStyle({ position: 'relative' });
+ var value = proceed(element);
+ element.setStyle({ position: position });
+ return value;
+ }
+ );
+
+ $w('positionedOffset viewportOffset').each(function(method) {
Element.Methods[method] = Element.Methods[method].wrap(
function(proceed, element) {
element = $(element);
var position = element.getStyle('position');
- if (position != 'static') return proceed(element);
+ if (position !== 'static') return proceed(element);
+ // Trigger hasLayout on the offset parent so that IE6 reports
+ // accurate offsetTop and offsetLeft values for position: fixed.
+ var offsetParent = element.getOffsetParent();
+ if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+ offsetParent.setStyle({ zoom: 1 });
element.setStyle({ position: 'relative' });
var value = proceed(element);
element.setStyle({ position: position });
@@ -2324,7 +2319,10 @@ else if (Prototype.Browser.IE) {
};
Element._attributeTranslations.write = {
- names: Object.clone(Element._attributeTranslations.read.names),
+ names: Object.extend({
+ cellpadding: 'cellPadding',
+ cellspacing: 'cellSpacing'
+ }, Element._attributeTranslations.read.names),
values: {
checked: function(element, value) {
element.checked = !!value;
@@ -2444,7 +2442,7 @@ if (Prototype.Browser.IE || Prototype.Browser.Opera) {
};
}
-if (document.createElement('div').outerHTML) {
+if ('outerHTML' in document.createElement('div')) {
Element.Methods.replace = function(element, content) {
element = $(element);
@@ -2482,45 +2480,25 @@ Element._returnOffset = function(l, t) {
Element._getContentFromAnonymousElement = function(tagName, html) {
var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
- div.innerHTML = t[0] + html + t[1];
- t[2].times(function() { div = div.firstChild });
+ if (t) {
+ div.innerHTML = t[0] + html + t[1];
+ t[2].times(function() { div = div.firstChild });
+ } else div.innerHTML = html;
return $A(div.childNodes);
};
Element._insertionTranslations = {
- before: {
- adjacency: 'beforeBegin',
- insert: function(element, node) {
- element.parentNode.insertBefore(node, element);
- },
- initializeRange: function(element, range) {
- range.setStartBefore(element);
- }
+ before: function(element, node) {
+ element.parentNode.insertBefore(node, element);
},
- top: {
- adjacency: 'afterBegin',
- insert: function(element, node) {
- element.insertBefore(node, element.firstChild);
- },
- initializeRange: function(element, range) {
- range.selectNodeContents(element);
- range.collapse(true);
- }
+ top: function(element, node) {
+ element.insertBefore(node, element.firstChild);
},
- bottom: {
- adjacency: 'beforeEnd',
- insert: function(element, node) {
- element.appendChild(node);
- }
+ bottom: function(element, node) {
+ element.appendChild(node);
},
- after: {
- adjacency: 'afterEnd',
- insert: function(element, node) {
- element.parentNode.insertBefore(node, element.nextSibling);
- },
- initializeRange: function(element, range) {
- range.setStartAfter(element);
- }
+ after: function(element, node) {
+ element.parentNode.insertBefore(node, element.nextSibling);
},
tags: {
TABLE: ['', 1],
@@ -2532,7 +2510,6 @@ Element._insertionTranslations = {
};
(function() {
- this.bottom.initializeRange = this.top.initializeRange;
Object.extend(this.tags, {
THEAD: this.tags.TBODY,
TFOOT: this.tags.TBODY,
@@ -2716,7 +2693,7 @@ document.viewport = {
window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
}
};
-/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
* part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
* license. Please see http://www.yui-ext.com/ for more information. */
@@ -2959,13 +2936,13 @@ Object.extend(Selector, {
},
criteria: {
- tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
- className: 'n = h.className(n, r, "#{1}", c); c = false;',
- id: 'n = h.id(n, r, "#{1}", c); c = false;',
- attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
+ tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
+ className: 'n = h.className(n, r, "#{1}", c); c = false;',
+ id: 'n = h.id(n, r, "#{1}", c); c = false;',
+ attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
attr: function(m) {
m[3] = (m[5] || m[6]);
- return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
+ return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
},
pseudo: function(m) {
if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
@@ -2989,7 +2966,8 @@ Object.extend(Selector, {
tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
id: /^#([\w\-\*]+)(\b|$)/,
className: /^\.([\w\-\*]+)(\b|$)/,
- pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
+ pseudo:
+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
attrPresence: /^\[([\w]+)\]/,
attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
},
@@ -3014,7 +2992,7 @@ Object.extend(Selector, {
attr: function(element, matches) {
var nodeValue = Element.readAttribute(element, matches[1]);
- return Selector.operators[matches[2]](nodeValue, matches[3]);
+ return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
}
},
@@ -3029,14 +3007,15 @@ Object.extend(Selector, {
// marks an array of nodes for counting
mark: function(nodes) {
+ var _true = Prototype.emptyFunction;
for (var i = 0, node; node = nodes[i]; i++)
- node._counted = true;
+ node._countedByPrototype = _true;
return nodes;
},
unmark: function(nodes) {
for (var i = 0, node; node = nodes[i]; i++)
- node._counted = undefined;
+ node._countedByPrototype = undefined;
return nodes;
},
@@ -3044,15 +3023,15 @@ Object.extend(Selector, {
// "ofType" flag indicates whether we're indexing for nth-of-type
// rather than nth-child
index: function(parentNode, reverse, ofType) {
- parentNode._counted = true;
+ parentNode._countedByPrototype = Prototype.emptyFunction;
if (reverse) {
for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
var node = nodes[i];
- if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+ if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
}
} else {
for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
- if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+ if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
}
},
@@ -3061,8 +3040,8 @@ Object.extend(Selector, {
if (nodes.length == 0) return nodes;
var results = [], n;
for (var i = 0, l = nodes.length; i < l; i++)
- if (!(n = nodes[i])._counted) {
- n._counted = true;
+ if (!(n = nodes[i])._countedByPrototype) {
+ n._countedByPrototype = Prototype.emptyFunction;
results.push(Element.extend(n));
}
return Selector.handlers.unmark(results);
@@ -3114,7 +3093,7 @@ Object.extend(Selector, {
// TOKEN FUNCTIONS
tagName: function(nodes, root, tagName, combinator) {
- tagName = tagName.toUpperCase();
+ var uTagName = tagName.toUpperCase();
var results = [], h = Selector.handlers;
if (nodes) {
if (combinator) {
@@ -3127,7 +3106,7 @@ Object.extend(Selector, {
if (tagName == "*") return nodes;
}
for (var i = 0, node; node = nodes[i]; i++)
- if (node.tagName.toUpperCase() == tagName) results.push(node);
+ if (node.tagName.toUpperCase() === uTagName) results.push(node);
return results;
} else return root.getElementsByTagName(tagName);
},
@@ -3174,16 +3153,18 @@ Object.extend(Selector, {
return results;
},
- attrPresence: function(nodes, root, attr) {
+ attrPresence: function(nodes, root, attr, combinator) {
if (!nodes) nodes = root.getElementsByTagName("*");
+ if (nodes && combinator) nodes = this[combinator](nodes);
var results = [];
for (var i = 0, node; node = nodes[i]; i++)
if (Element.hasAttribute(node, attr)) results.push(node);
return results;
},
- attr: function(nodes, root, attr, value, operator) {
+ attr: function(nodes, root, attr, value, operator, combinator) {
if (!nodes) nodes = root.getElementsByTagName("*");
+ if (nodes && combinator) nodes = this[combinator](nodes);
var handler = Selector.operators[operator], results = [];
for (var i = 0, node; node = nodes[i]; i++) {
var nodeValue = Element.readAttribute(node, attr);
@@ -3262,7 +3243,7 @@ Object.extend(Selector, {
var h = Selector.handlers, results = [], indexed = [], m;
h.mark(nodes);
for (var i = 0, node; node = nodes[i]; i++) {
- if (!node.parentNode._counted) {
+ if (!node.parentNode._countedByPrototype) {
h.index(node.parentNode, reverse, ofType);
indexed.push(node.parentNode);
}
@@ -3300,7 +3281,7 @@ Object.extend(Selector, {
var exclusions = new Selector(selector).findElements(root);
h.mark(exclusions);
for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (!node._counted) results.push(node);
+ if (!node._countedByPrototype) results.push(node);
h.unmark(exclusions);
return results;
},
@@ -3334,11 +3315,19 @@ Object.extend(Selector, {
'|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
},
+ split: function(expression) {
+ var expressions = [];
+ expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+ expressions.push(m[1].strip());
+ });
+ return expressions;
+ },
+
matchElements: function(elements, expression) {
- var matches = new Selector(expression).findElements(), h = Selector.handlers;
+ var matches = $$(expression), h = Selector.handlers;
h.mark(matches);
for (var i = 0, results = [], element; element = elements[i]; i++)
- if (element._counted) results.push(element);
+ if (element._countedByPrototype) results.push(element);
h.unmark(matches);
return results;
},
@@ -3351,11 +3340,7 @@ Object.extend(Selector, {
},
findChildElements: function(element, expressions) {
- var exprs = expressions.join(',');
- expressions = [];
- exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
- expressions.push(m[1].strip());
- });
+ expressions = Selector.split(expressions.join(','));
var results = [], h = Selector.handlers;
for (var i = 0, l = expressions.length, selector; i < l; i++) {
selector = new Selector(expressions[i].strip());
@@ -3366,13 +3351,22 @@ Object.extend(Selector, {
});
if (Prototype.Browser.IE) {
- // IE returns comment nodes on getElementsByTagName("*").
- // Filter them out.
- Selector.handlers.concat = function(a, b) {
- for (var i = 0, node; node = b[i]; i++)
- if (node.tagName !== "!") a.push(node);
- return a;
- };
+ Object.extend(Selector.handlers, {
+ // IE returns comment nodes on getElementsByTagName("*").
+ // Filter them out.
+ concat: function(a, b) {
+ for (var i = 0, node; node = b[i]; i++)
+ if (node.tagName !== "!") a.push(node);
+ return a;
+ },
+
+ // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
+ unmark: function(nodes) {
+ for (var i = 0, node; node = nodes[i]; i++)
+ node.removeAttribute('_countedByPrototype');
+ return nodes;
+ }
+ });
}
function $$() {
@@ -3850,9 +3844,9 @@ Object.extend(Event, (function() {
var cache = Event.cache;
function getEventID(element) {
- if (element._eventID) return element._eventID;
+ if (element._prototypeEventID) return element._prototypeEventID[0];
arguments.callee.id = arguments.callee.id || 1;
- return element._eventID = ++arguments.callee.id;
+ return element._prototypeEventID = [++arguments.callee.id];
}
function getDOMEventName(eventName) {
@@ -3880,7 +3874,7 @@ Object.extend(Event, (function() {
return false;
Event.extend(event);
- handler.call(element, event)
+ handler.call(element, event);
};
wrapper.handler = handler;
@@ -3962,11 +3956,12 @@ Object.extend(Event, (function() {
if (element == document && document.createEvent && !element.dispatchEvent)
element = document.documentElement;
+ var event;
if (document.createEvent) {
- var event = document.createEvent("HTMLEvents");
+ event = document.createEvent("HTMLEvents");
event.initEvent("dataavailable", true, true);
} else {
- var event = document.createEventObject();
+ event = document.createEventObject();
event.eventType = "ondataavailable";
}
@@ -3995,20 +3990,21 @@ Element.addMethods({
Object.extend(document, {
fire: Element.Methods.fire.methodize(),
observe: Element.Methods.observe.methodize(),
- stopObserving: Element.Methods.stopObserving.methodize()
+ stopObserving: Element.Methods.stopObserving.methodize(),
+ loaded: false
});
(function() {
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
Matthias Miller, Dean Edwards and John Resig. */
- var timer, fired = false;
+ var timer;
function fireContentLoadedEvent() {
- if (fired) return;
+ if (document.loaded) return;
if (timer) window.clearInterval(timer);
document.fire("dom:loaded");
- fired = true;
+ document.loaded = true;
}
if (document.addEventListener) {
diff --git a/railties/html/javascripts/prototype.js b/railties/html/javascripts/prototype.js
index 546f9fe449..2c70b8a7e8 100644
--- a/railties/html/javascripts/prototype.js
+++ b/railties/html/javascripts/prototype.js
@@ -1,5 +1,5 @@
-/* Prototype JavaScript framework, version 1.6.0.1
- * (c) 2005-2007 Sam Stephenson
+/* Prototype JavaScript framework, version 1.6.0.2
+ * (c) 2005-2008 Sam Stephenson
*
* Prototype is freely distributable under the terms of an MIT-style license.
* For details, see the Prototype web site: http://www.prototypejs.org/
@@ -7,7 +7,7 @@
*--------------------------------------------------------------------------*/
var Prototype = {
- Version: '1.6.0.1',
+ Version: '1.6.0.2',
Browser: {
IE: !!(window.attachEvent && !window.opera),
@@ -110,7 +110,7 @@ Object.extend(Object, {
try {
if (Object.isUndefined(object)) return 'undefined';
if (object === null) return 'null';
- return object.inspect ? object.inspect() : object.toString();
+ return object.inspect ? object.inspect() : String(object);
} catch (e) {
if (e instanceof RangeError) return '...';
throw e;
@@ -171,7 +171,8 @@ Object.extend(Object, {
},
isArray: function(object) {
- return object && object.constructor === Array;
+ return object != null && typeof object == "object" &&
+ 'splice' in object && 'join' in object;
},
isHash: function(object) {
@@ -578,7 +579,7 @@ var Template = Class.create({
}
return before + String.interpret(ctx);
- }.bind(this));
+ });
}
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
@@ -806,20 +807,20 @@ Object.extend(Enumerable, {
function $A(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
- var length = iterable.length, results = new Array(length);
+ var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
if (Prototype.Browser.WebKit) {
- function $A(iterable) {
+ $A = function(iterable) {
if (!iterable) return [];
if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
iterable.toArray) return iterable.toArray();
- var length = iterable.length, results = new Array(length);
+ var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
- }
+ };
}
Array.from = $A;
@@ -1298,7 +1299,7 @@ Ajax.Request = Class.create(Ajax.Base, {
var contentType = response.getHeader('Content-type');
if (this.options.evalJS == 'force'
- || (this.options.evalJS && contentType
+ || (this.options.evalJS && this.isSameOrigin() && contentType
&& contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
this.evalResponse();
}
@@ -1316,9 +1317,18 @@ Ajax.Request = Class.create(Ajax.Base, {
}
},
+ isSameOrigin: function() {
+ var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+ return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+ protocol: location.protocol,
+ domain: document.domain,
+ port: location.port ? ':' + location.port : ''
+ }));
+ },
+
getHeader: function(name) {
try {
- return this.transport.getResponseHeader(name);
+ return this.transport.getResponseHeader(name) || null;
} catch (e) { return null }
},
@@ -1391,7 +1401,8 @@ Ajax.Response = Class.create({
if (!json) return null;
json = decodeURIComponent(escape(json));
try {
- return json.evalJSON(this.request.options.sanitizeJSON);
+ return json.evalJSON(this.request.options.sanitizeJSON ||
+ !this.request.isSameOrigin());
} catch (e) {
this.request.dispatchException(e);
}
@@ -1404,7 +1415,8 @@ Ajax.Response = Class.create({
this.responseText.blank())
return null;
try {
- return this.responseText.evalJSON(options.sanitizeJSON);
+ return this.responseText.evalJSON(options.sanitizeJSON ||
+ !this.request.isSameOrigin());
} catch (e) {
this.request.dispatchException(e);
}
@@ -1608,24 +1620,28 @@ Element.Methods = {
Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
insertions = {bottom:insertions};
- var content, t, range;
+ var content, insert, tagName, childNodes;
- for (position in insertions) {
+ for (var position in insertions) {
content = insertions[position];
position = position.toLowerCase();
- t = Element._insertionTranslations[position];
+ insert = Element._insertionTranslations[position];
if (content && content.toElement) content = content.toElement();
if (Object.isElement(content)) {
- t.insert(element, content);
+ insert(element, content);
continue;
}
content = Object.toHTML(content);
- range = element.ownerDocument.createRange();
- t.initializeRange(element, range);
- t.insert(element, range.createContextualFragment(content.stripScripts()));
+ tagName = ((position == 'before' || position == 'after')
+ ? element.parentNode : element).tagName.toUpperCase();
+
+ childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+ if (position == 'top' || position == 'after') childNodes.reverse();
+ childNodes.each(insert.curry(element));
content.evalScripts.bind(content).defer();
}
@@ -1670,7 +1686,7 @@ Element.Methods = {
},
descendants: function(element) {
- return $(element).getElementsBySelector("*");
+ return $(element).select("*");
},
firstDescendant: function(element) {
@@ -1709,32 +1725,31 @@ Element.Methods = {
element = $(element);
if (arguments.length == 1) return $(element.parentNode);
var ancestors = element.ancestors();
- return expression ? Selector.findElement(ancestors, expression, index) :
- ancestors[index || 0];
+ return Object.isNumber(expression) ? ancestors[expression] :
+ Selector.findElement(ancestors, expression, index);
},
down: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return element.firstDescendant();
- var descendants = element.descendants();
- return expression ? Selector.findElement(descendants, expression, index) :
- descendants[index || 0];
+ return Object.isNumber(expression) ? element.descendants()[expression] :
+ element.select(expression)[index || 0];
},
previous: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
var previousSiblings = element.previousSiblings();
- return expression ? Selector.findElement(previousSiblings, expression, index) :
- previousSiblings[index || 0];
+ return Object.isNumber(expression) ? previousSiblings[expression] :
+ Selector.findElement(previousSiblings, expression, index);
},
next: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
var nextSiblings = element.nextSiblings();
- return expression ? Selector.findElement(nextSiblings, expression, index) :
- nextSiblings[index || 0];
+ return Object.isNumber(expression) ? nextSiblings[expression] :
+ Selector.findElement(nextSiblings, expression, index);
},
select: function() {
@@ -1860,7 +1875,8 @@ Element.Methods = {
do { ancestor = ancestor.parentNode; }
while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
}
- if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
+ if (nextAncestor && nextAncestor.sourceIndex)
+ return (e > a && e < nextAncestor.sourceIndex);
}
while (element = element.parentNode)
@@ -2004,7 +2020,7 @@ Element.Methods = {
if (element) {
if (element.tagName == 'BODY') break;
var p = Element.getStyle(element, 'position');
- if (p == 'relative' || p == 'absolute') break;
+ if (p !== 'static') break;
}
} while (element);
return Element._returnOffset(valueL, valueT);
@@ -2153,46 +2169,6 @@ Element._attributeTranslations = {
}
};
-
-if (!document.createRange || Prototype.Browser.Opera) {
- Element.Methods.insert = function(element, insertions) {
- element = $(element);
-
- if (Object.isString(insertions) || Object.isNumber(insertions) ||
- Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
- insertions = { bottom: insertions };
-
- var t = Element._insertionTranslations, content, position, pos, tagName;
-
- for (position in insertions) {
- content = insertions[position];
- position = position.toLowerCase();
- pos = t[position];
-
- if (content && content.toElement) content = content.toElement();
- if (Object.isElement(content)) {
- pos.insert(element, content);
- continue;
- }
-
- content = Object.toHTML(content);
- tagName = ((position == 'before' || position == 'after')
- ? element.parentNode : element).tagName.toUpperCase();
-
- if (t.tags[tagName]) {
- var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
- if (position == 'top' || position == 'after') fragments.reverse();
- fragments.each(pos.insert.curry(element));
- }
- else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
-
- content.evalScripts.bind(content).defer();
- }
-
- return element;
- };
-}
-
if (Prototype.Browser.Opera) {
Element.Methods.getStyle = Element.Methods.getStyle.wrap(
function(proceed, element, style) {
@@ -2237,12 +2213,31 @@ if (Prototype.Browser.Opera) {
}
else if (Prototype.Browser.IE) {
- $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
+ // IE doesn't report offsets correctly for static elements, so we change them
+ // to "relative" to get the values, then change them back.
+ Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
+ function(proceed, element) {
+ element = $(element);
+ var position = element.getStyle('position');
+ if (position !== 'static') return proceed(element);
+ element.setStyle({ position: 'relative' });
+ var value = proceed(element);
+ element.setStyle({ position: position });
+ return value;
+ }
+ );
+
+ $w('positionedOffset viewportOffset').each(function(method) {
Element.Methods[method] = Element.Methods[method].wrap(
function(proceed, element) {
element = $(element);
var position = element.getStyle('position');
- if (position != 'static') return proceed(element);
+ if (position !== 'static') return proceed(element);
+ // Trigger hasLayout on the offset parent so that IE6 reports
+ // accurate offsetTop and offsetLeft values for position: fixed.
+ var offsetParent = element.getOffsetParent();
+ if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+ offsetParent.setStyle({ zoom: 1 });
element.setStyle({ position: 'relative' });
var value = proceed(element);
element.setStyle({ position: position });
@@ -2324,7 +2319,10 @@ else if (Prototype.Browser.IE) {
};
Element._attributeTranslations.write = {
- names: Object.clone(Element._attributeTranslations.read.names),
+ names: Object.extend({
+ cellpadding: 'cellPadding',
+ cellspacing: 'cellSpacing'
+ }, Element._attributeTranslations.read.names),
values: {
checked: function(element, value) {
element.checked = !!value;
@@ -2444,7 +2442,7 @@ if (Prototype.Browser.IE || Prototype.Browser.Opera) {
};
}
-if (document.createElement('div').outerHTML) {
+if ('outerHTML' in document.createElement('div')) {
Element.Methods.replace = function(element, content) {
element = $(element);
@@ -2482,45 +2480,25 @@ Element._returnOffset = function(l, t) {
Element._getContentFromAnonymousElement = function(tagName, html) {
var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
- div.innerHTML = t[0] + html + t[1];
- t[2].times(function() { div = div.firstChild });
+ if (t) {
+ div.innerHTML = t[0] + html + t[1];
+ t[2].times(function() { div = div.firstChild });
+ } else div.innerHTML = html;
return $A(div.childNodes);
};
Element._insertionTranslations = {
- before: {
- adjacency: 'beforeBegin',
- insert: function(element, node) {
- element.parentNode.insertBefore(node, element);
- },
- initializeRange: function(element, range) {
- range.setStartBefore(element);
- }
+ before: function(element, node) {
+ element.parentNode.insertBefore(node, element);
},
- top: {
- adjacency: 'afterBegin',
- insert: function(element, node) {
- element.insertBefore(node, element.firstChild);
- },
- initializeRange: function(element, range) {
- range.selectNodeContents(element);
- range.collapse(true);
- }
+ top: function(element, node) {
+ element.insertBefore(node, element.firstChild);
},
- bottom: {
- adjacency: 'beforeEnd',
- insert: function(element, node) {
- element.appendChild(node);
- }
+ bottom: function(element, node) {
+ element.appendChild(node);
},
- after: {
- adjacency: 'afterEnd',
- insert: function(element, node) {
- element.parentNode.insertBefore(node, element.nextSibling);
- },
- initializeRange: function(element, range) {
- range.setStartAfter(element);
- }
+ after: function(element, node) {
+ element.parentNode.insertBefore(node, element.nextSibling);
},
tags: {
TABLE: ['', 1],
@@ -2532,7 +2510,6 @@ Element._insertionTranslations = {
};
(function() {
- this.bottom.initializeRange = this.top.initializeRange;
Object.extend(this.tags, {
THEAD: this.tags.TBODY,
TFOOT: this.tags.TBODY,
@@ -2716,7 +2693,7 @@ document.viewport = {
window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
}
};
-/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
* part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
* license. Please see http://www.yui-ext.com/ for more information. */
@@ -2959,13 +2936,13 @@ Object.extend(Selector, {
},
criteria: {
- tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
- className: 'n = h.className(n, r, "#{1}", c); c = false;',
- id: 'n = h.id(n, r, "#{1}", c); c = false;',
- attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
+ tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
+ className: 'n = h.className(n, r, "#{1}", c); c = false;',
+ id: 'n = h.id(n, r, "#{1}", c); c = false;',
+ attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
attr: function(m) {
m[3] = (m[5] || m[6]);
- return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
+ return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
},
pseudo: function(m) {
if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
@@ -2989,7 +2966,8 @@ Object.extend(Selector, {
tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
id: /^#([\w\-\*]+)(\b|$)/,
className: /^\.([\w\-\*]+)(\b|$)/,
- pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
+ pseudo:
+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
attrPresence: /^\[([\w]+)\]/,
attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
},
@@ -3014,7 +2992,7 @@ Object.extend(Selector, {
attr: function(element, matches) {
var nodeValue = Element.readAttribute(element, matches[1]);
- return Selector.operators[matches[2]](nodeValue, matches[3]);
+ return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
}
},
@@ -3029,14 +3007,15 @@ Object.extend(Selector, {
// marks an array of nodes for counting
mark: function(nodes) {
+ var _true = Prototype.emptyFunction;
for (var i = 0, node; node = nodes[i]; i++)
- node._counted = true;
+ node._countedByPrototype = _true;
return nodes;
},
unmark: function(nodes) {
for (var i = 0, node; node = nodes[i]; i++)
- node._counted = undefined;
+ node._countedByPrototype = undefined;
return nodes;
},
@@ -3044,15 +3023,15 @@ Object.extend(Selector, {
// "ofType" flag indicates whether we're indexing for nth-of-type
// rather than nth-child
index: function(parentNode, reverse, ofType) {
- parentNode._counted = true;
+ parentNode._countedByPrototype = Prototype.emptyFunction;
if (reverse) {
for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
var node = nodes[i];
- if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+ if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
}
} else {
for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
- if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+ if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
}
},
@@ -3061,8 +3040,8 @@ Object.extend(Selector, {
if (nodes.length == 0) return nodes;
var results = [], n;
for (var i = 0, l = nodes.length; i < l; i++)
- if (!(n = nodes[i])._counted) {
- n._counted = true;
+ if (!(n = nodes[i])._countedByPrototype) {
+ n._countedByPrototype = Prototype.emptyFunction;
results.push(Element.extend(n));
}
return Selector.handlers.unmark(results);
@@ -3114,7 +3093,7 @@ Object.extend(Selector, {
// TOKEN FUNCTIONS
tagName: function(nodes, root, tagName, combinator) {
- tagName = tagName.toUpperCase();
+ var uTagName = tagName.toUpperCase();
var results = [], h = Selector.handlers;
if (nodes) {
if (combinator) {
@@ -3127,7 +3106,7 @@ Object.extend(Selector, {
if (tagName == "*") return nodes;
}
for (var i = 0, node; node = nodes[i]; i++)
- if (node.tagName.toUpperCase() == tagName) results.push(node);
+ if (node.tagName.toUpperCase() === uTagName) results.push(node);
return results;
} else return root.getElementsByTagName(tagName);
},
@@ -3174,16 +3153,18 @@ Object.extend(Selector, {
return results;
},
- attrPresence: function(nodes, root, attr) {
+ attrPresence: function(nodes, root, attr, combinator) {
if (!nodes) nodes = root.getElementsByTagName("*");
+ if (nodes && combinator) nodes = this[combinator](nodes);
var results = [];
for (var i = 0, node; node = nodes[i]; i++)
if (Element.hasAttribute(node, attr)) results.push(node);
return results;
},
- attr: function(nodes, root, attr, value, operator) {
+ attr: function(nodes, root, attr, value, operator, combinator) {
if (!nodes) nodes = root.getElementsByTagName("*");
+ if (nodes && combinator) nodes = this[combinator](nodes);
var handler = Selector.operators[operator], results = [];
for (var i = 0, node; node = nodes[i]; i++) {
var nodeValue = Element.readAttribute(node, attr);
@@ -3262,7 +3243,7 @@ Object.extend(Selector, {
var h = Selector.handlers, results = [], indexed = [], m;
h.mark(nodes);
for (var i = 0, node; node = nodes[i]; i++) {
- if (!node.parentNode._counted) {
+ if (!node.parentNode._countedByPrototype) {
h.index(node.parentNode, reverse, ofType);
indexed.push(node.parentNode);
}
@@ -3300,7 +3281,7 @@ Object.extend(Selector, {
var exclusions = new Selector(selector).findElements(root);
h.mark(exclusions);
for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (!node._counted) results.push(node);
+ if (!node._countedByPrototype) results.push(node);
h.unmark(exclusions);
return results;
},
@@ -3334,11 +3315,19 @@ Object.extend(Selector, {
'|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
},
+ split: function(expression) {
+ var expressions = [];
+ expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+ expressions.push(m[1].strip());
+ });
+ return expressions;
+ },
+
matchElements: function(elements, expression) {
- var matches = new Selector(expression).findElements(), h = Selector.handlers;
+ var matches = $$(expression), h = Selector.handlers;
h.mark(matches);
for (var i = 0, results = [], element; element = elements[i]; i++)
- if (element._counted) results.push(element);
+ if (element._countedByPrototype) results.push(element);
h.unmark(matches);
return results;
},
@@ -3351,11 +3340,7 @@ Object.extend(Selector, {
},
findChildElements: function(element, expressions) {
- var exprs = expressions.join(',');
- expressions = [];
- exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
- expressions.push(m[1].strip());
- });
+ expressions = Selector.split(expressions.join(','));
var results = [], h = Selector.handlers;
for (var i = 0, l = expressions.length, selector; i < l; i++) {
selector = new Selector(expressions[i].strip());
@@ -3366,13 +3351,22 @@ Object.extend(Selector, {
});
if (Prototype.Browser.IE) {
- // IE returns comment nodes on getElementsByTagName("*").
- // Filter them out.
- Selector.handlers.concat = function(a, b) {
- for (var i = 0, node; node = b[i]; i++)
- if (node.tagName !== "!") a.push(node);
- return a;
- };
+ Object.extend(Selector.handlers, {
+ // IE returns comment nodes on getElementsByTagName("*").
+ // Filter them out.
+ concat: function(a, b) {
+ for (var i = 0, node; node = b[i]; i++)
+ if (node.tagName !== "!") a.push(node);
+ return a;
+ },
+
+ // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
+ unmark: function(nodes) {
+ for (var i = 0, node; node = nodes[i]; i++)
+ node.removeAttribute('_countedByPrototype');
+ return nodes;
+ }
+ });
}
function $$() {
@@ -3850,9 +3844,9 @@ Object.extend(Event, (function() {
var cache = Event.cache;
function getEventID(element) {
- if (element._eventID) return element._eventID;
+ if (element._prototypeEventID) return element._prototypeEventID[0];
arguments.callee.id = arguments.callee.id || 1;
- return element._eventID = ++arguments.callee.id;
+ return element._prototypeEventID = [++arguments.callee.id];
}
function getDOMEventName(eventName) {
@@ -3880,7 +3874,7 @@ Object.extend(Event, (function() {
return false;
Event.extend(event);
- handler.call(element, event)
+ handler.call(element, event);
};
wrapper.handler = handler;
@@ -3962,11 +3956,12 @@ Object.extend(Event, (function() {
if (element == document && document.createEvent && !element.dispatchEvent)
element = document.documentElement;
+ var event;
if (document.createEvent) {
- var event = document.createEvent("HTMLEvents");
+ event = document.createEvent("HTMLEvents");
event.initEvent("dataavailable", true, true);
} else {
- var event = document.createEventObject();
+ event = document.createEventObject();
event.eventType = "ondataavailable";
}
@@ -3995,20 +3990,21 @@ Element.addMethods({
Object.extend(document, {
fire: Element.Methods.fire.methodize(),
observe: Element.Methods.observe.methodize(),
- stopObserving: Element.Methods.stopObserving.methodize()
+ stopObserving: Element.Methods.stopObserving.methodize(),
+ loaded: false
});
(function() {
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
Matthias Miller, Dean Edwards and John Resig. */
- var timer, fired = false;
+ var timer;
function fireContentLoadedEvent() {
- if (fired) return;
+ if (document.loaded) return;
if (timer) window.clearInterval(timer);
document.fire("dom:loaded");
- fired = true;
+ document.loaded = true;
}
if (document.addEventListener) {
--
cgit v1.2.3
From 1b4b1aa725a4f44c3473ae99b36d7cededba2bea Mon Sep 17 00:00:00 2001
From: Kevin Glowacz
Date: Sat, 19 Jul 2008 15:08:53 -0500
Subject: Fixed index and auto index for nested fields_for [#327
state:resolved]
Signed-off-by: Joshua Peek
---
actionpack/lib/action_view/helpers/form_helper.rb | 30 ++++--
actionpack/test/template/form_helper_test.rb | 116 +++++++++++++++++++++-
2 files changed, 136 insertions(+), 10 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 4fa46d9ee3..ada6fa2ea8 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -528,10 +528,10 @@ module ActionView
def initialize(object_name, method_name, template_object, object = nil)
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
- @template_object= template_object
+ @template_object = template_object
@object = object
- if @object_name.sub!(/\[\]$/,"")
- if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
+ if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
+ if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
@auto_index = object.to_param
else
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
@@ -708,7 +708,7 @@ module ActionView
end
def sanitized_object_name
- @sanitized_object_name ||= @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
+ @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
end
def sanitized_method_name
@@ -726,6 +726,13 @@ module ActionView
def initialize(object_name, object, template, options, proc)
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
@default_options = @options ? @options.slice(:index) : {}
+ if @object_name.to_s.match(/\[\]$/)
+ if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
+ @auto_index = object.to_param
+ else
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
+ end
+ end
end
(field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
@@ -738,16 +745,25 @@ module ActionView
end
def fields_for(record_or_name_or_array, *args, &block)
+ if options.has_key?(:index)
+ index = "[#{options[:index]}]"
+ elsif defined?(@auto_index)
+ self.object_name = @object_name.to_s.sub(/\[\]$/,"")
+ index = "[#{@auto_index}]"
+ else
+ index = ""
+ end
+
case record_or_name_or_array
when String, Symbol
- name = "#{object_name}[#{record_or_name_or_array}]"
+ name = "#{object_name}#{index}[#{record_or_name_or_array}]"
when Array
object = record_or_name_or_array.last
- name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
+ name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
args.unshift(object)
else
object = record_or_name_or_array
- name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
+ name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
args.unshift(object)
end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 39649c3622..52e8bf376a 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -6,7 +6,7 @@ silence_warnings do
alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast)
alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast)
alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast)
- alias_method :secret?, :secret
+ alias_method :secret?, :secret
def new_record=(boolean)
@new_record = boolean
@@ -22,6 +22,7 @@ silence_warnings do
attr_reader :post_id
def save; @id = 1; @post_id = 1 end
def new_record?; @id.nil? end
+ def to_param; @id; end
def name
@id.nil? ? 'new comment' : "comment ##{@id}"
end
@@ -30,7 +31,6 @@ end
class Comment::Nested < Comment; end
-
class FormHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormHelper
@@ -447,6 +447,117 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_nested_fields_for_with_nested_collections
+ form_for('post[]', @post) do |f|
+ concat f.text_field(:title)
+ f.fields_for('comment[]', @comment) do |c|
+ concat c.text_field(:name)
+ end
+ end
+
+ expected = ""
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_for_with_index
+ form_for('post', @post, :index => 1) do |c|
+ concat c.text_field(:title)
+ c.fields_for('comment', @comment, :index => 1) do |r|
+ concat r.text_field(:name)
+ end
+ end
+
+ expected = ""
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_for_with_index
+ form_for(:post, @post, :index => 1) do |f|
+ f.fields_for(:comment, @post) do |c|
+ concat c.text_field(:title)
+ end
+ end
+
+ expected = ""
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_for_with_index_on_both
+ form_for(:post, @post, :index => 1) do |f|
+ f.fields_for(:comment, @post, :index => 5) do |c|
+ concat c.text_field(:title)
+ end
+ end
+
+ expected = ""
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_for_with_auto_index
+ form_for("post[]", @post) do |f|
+ f.fields_for(:comment, @post) do |c|
+ concat c.text_field(:title)
+ end
+ end
+
+ expected = ""
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_for_with_auto_index_on_both
+ form_for("post[]", @post) do |f|
+ f.fields_for("comment[]", @post) do |c|
+ concat c.text_field(:title)
+ end
+ end
+
+ expected = ""
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_nested_fields_for_with_index_and_auto_index
+ form_for("post[]", @post) do |f|
+ f.fields_for(:comment, @post, :index => 5) do |c|
+ concat c.text_field(:title)
+ end
+ end
+
+ form_for(:post, @post, :index => 1) do |f|
+ f.fields_for("comment[]", @post) do |c|
+ concat c.text_field(:title)
+ end
+ end
+
+ expected = "" +
+ ""
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_fields_for
fields_for(:post, @post) do |f|
concat f.text_field(:title)
@@ -831,7 +942,6 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
-
protected
def comments_path(post)
"/posts/#{post.id}/comments"
--
cgit v1.2.3
From 55bfe6be52da1130abb1c148f10d8e9a11368ffd Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 15:27:55 -0500
Subject: Ensure ActionView::PathSet::Path is not initialized with a
precompiled path
---
actionmailer/lib/action_mailer/base.rb | 3 ++-
actionpack/lib/action_view/paths.rb | 2 ++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index bf60e2f3d5..a43296461b 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -426,7 +426,8 @@ module ActionMailer #:nodoc:
end
def template_root=(root)
- write_inheritable_attribute(:template_root, ActionView::PathSet::Path.new(root))
+ root = ActionView::PathSet::Path.new(root) if root.is_a?(String)
+ write_inheritable_attribute(:template_root, root)
end
end
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index c7a5df762f..78548d4aa2 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -28,6 +28,8 @@ module ActionView #:nodoc:
delegate :to_s, :to_str, :inspect, :to => :path
def initialize(path)
+ raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
+
@path = path.freeze
reload!
end
--
cgit v1.2.3
From 598b4546fd505efd98c4955f4881b4fd5b953588 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 15:43:39 -0500
Subject: Fixed spelling mistake in deprecation warning [#381 state:resolved]
---
actionpack/lib/action_view/base.rb | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index fe51af62e6..619a4270f8 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -172,13 +172,15 @@ module ActionView #:nodoc:
end
def self.cache_template_loading=(*args)
- ActiveSupport::Deprecation.warn("config.action_view.cache_template_loading option has been deprecated and has no affect. " <<
- "Please remove it from your config files.", caller)
+ ActiveSupport::Deprecation.warn(
+ "config.action_view.cache_template_loading option has been deprecated" +
+ "and has no effect. Please remove it from your config files.", caller)
end
def self.cache_template_extensions=(*args)
- ActiveSupport::Deprecation.warn("config.action_view.cache_template_extensions option has been deprecated and has no effect. " <<
- "Please remove it from your config files.", caller)
+ ActiveSupport::Deprecation.warn(
+ "config.action_view.cache_template_extensions option has been" +
+ "deprecated and has no effect. Please remove it from your config files.", caller)
end
# Specify whether RJS responses should be wrapped in a try/catch block
--
cgit v1.2.3
From da85251f85420d3a906a1eb949b73bb286122ea9 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 19 Jul 2008 17:32:22 -0500
Subject: Refactor ActiveSupport::Dependencies injector so it would be possible
to disable it
---
activesupport/lib/active_support/dependencies.rb | 218 ++++++++++++-----------
1 file changed, 114 insertions(+), 104 deletions(-)
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 2f3fa72bb4..e3d4f3d7eb 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -1,8 +1,3 @@
-require 'set'
-require 'active_support/core_ext/module/attribute_accessors'
-require 'active_support/core_ext/load_error'
-require 'active_support/core_ext/kernel'
-
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
extend self
@@ -52,6 +47,119 @@ module ActiveSupport #:nodoc:
mattr_accessor :constant_watch_stack
self.constant_watch_stack = []
+ # Module includes this module
+ module ModuleConstMissing #:nodoc:
+ def self.included(base) #:nodoc:
+ base.class_eval do
+ # Rename the original handler so we can chain it to the new one
+ alias_method :rails_original_const_missing, :const_missing
+
+ # Use const_missing to autoload associations so we don't have to
+ # require_association when using single-table inheritance.
+ def const_missing(class_id)
+ ActiveSupport::Dependencies.load_missing_constant self, class_id
+ end
+ end
+ end
+
+ def unloadable(const_desc = self)
+ super(const_desc)
+ end
+ end
+
+ # Class includes this module
+ module ClassConstMissing #:nodoc:
+ def const_missing(const_name)
+ if [Object, Kernel].include?(self) || parent == self
+ super
+ else
+ begin
+ begin
+ Dependencies.load_missing_constant self, const_name
+ rescue NameError
+ parent.send :const_missing, const_name
+ end
+ rescue NameError => e
+ # Make sure that the name we are missing is the one that caused the error
+ parent_qualified_name = Dependencies.qualified_name_for parent, const_name
+ raise unless e.missing_name? parent_qualified_name
+ qualified_name = Dependencies.qualified_name_for self, const_name
+ raise NameError.new("uninitialized constant #{qualified_name}").copy_blame!(e)
+ end
+ end
+ end
+ end
+
+ # Object includes this module
+ module Loadable #:nodoc:
+ def load(file, *extras) #:nodoc:
+ Dependencies.new_constants_in(Object) { super }
+ rescue Exception => exception # errors from loading file
+ exception.blame_file! file
+ raise
+ end
+
+ def require(file, *extras) #:nodoc:
+ Dependencies.new_constants_in(Object) { super }
+ rescue Exception => exception # errors from required file
+ exception.blame_file! file
+ raise
+ end
+
+ # Mark the given constant as unloadable. Unloadable constants are removed each
+ # time dependencies are cleared.
+ #
+ # Note that marking a constant for unloading need only be done once. Setup
+ # or init scripts may list each unloadable constant that may need unloading;
+ # each constant will be removed for every subsequent clear, as opposed to for
+ # the first clear.
+ #
+ # The provided constant descriptor may be a (non-anonymous) module or class,
+ # or a qualified constant name as a string or symbol.
+ #
+ # Returns true if the constant was not previously marked for unloading, false
+ # otherwise.
+ def unloadable(const_desc)
+ Dependencies.mark_for_unload const_desc
+ end
+ end
+
+ # Exception file-blaming
+ module Blamable #:nodoc:
+ def blame_file!(file)
+ (@blamed_files ||= []).unshift file
+ end
+
+ def blamed_files
+ @blamed_files ||= []
+ end
+
+ def describe_blame
+ return nil if blamed_files.empty?
+ "This error occurred while loading the following files:\n #{blamed_files.join "\n "}"
+ end
+
+ def copy_blame!(exc)
+ @blamed_files = exc.blamed_files.clone
+ self
+ end
+ end
+
+ def inject!
+ Object.instance_eval do
+ define_method(:require_or_load) { |file_name| Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load)
+ define_method(:require_dependency) { |file_name| Dependencies.depend_on(file_name) } unless Object.respond_to?(:require_dependency)
+ define_method(:require_association) { |file_name| Dependencies.associate_with(file_name) } unless Object.respond_to?(:require_association)
+
+ alias_method :load_without_new_constant_marking, :load
+ include Loadable
+ end
+
+ Module.instance_eval { include ModuleConstMissing }
+ Class.instance_eval { include ClassConstMissing }
+ Exception.instance_eval { include Blamable }
+ end
+
def load?
mechanism == :load
end
@@ -452,102 +560,4 @@ module ActiveSupport #:nodoc:
end
end
-Object.instance_eval do
- define_method(:require_or_load) { |file_name| ActiveSupport::Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load)
- define_method(:require_dependency) { |file_name| ActiveSupport::Dependencies.depend_on(file_name) } unless Object.respond_to?(:require_dependency)
- define_method(:require_association) { |file_name| ActiveSupport::Dependencies.associate_with(file_name) } unless Object.respond_to?(:require_association)
-end
-
-class Module #:nodoc:
- # Rename the original handler so we can chain it to the new one
- alias :rails_original_const_missing :const_missing
-
- # Use const_missing to autoload associations so we don't have to
- # require_association when using single-table inheritance.
- def const_missing(class_id)
- ActiveSupport::Dependencies.load_missing_constant self, class_id
- end
-
- def unloadable(const_desc = self)
- super(const_desc)
- end
-
-end
-
-class Class
- def const_missing(const_name)
- if [Object, Kernel].include?(self) || parent == self
- super
- else
- begin
- begin
- ActiveSupport::Dependencies.load_missing_constant self, const_name
- rescue NameError
- parent.send :const_missing, const_name
- end
- rescue NameError => e
- # Make sure that the name we are missing is the one that caused the error
- parent_qualified_name = ActiveSupport::Dependencies.qualified_name_for parent, const_name
- raise unless e.missing_name? parent_qualified_name
- qualified_name = ActiveSupport::Dependencies.qualified_name_for self, const_name
- raise NameError.new("uninitialized constant #{qualified_name}").copy_blame!(e)
- end
- end
- end
-end
-
-class Object
- alias_method :load_without_new_constant_marking, :load
-
- def load(file, *extras) #:nodoc:
- ActiveSupport::Dependencies.new_constants_in(Object) { super }
- rescue Exception => exception # errors from loading file
- exception.blame_file! file
- raise
- end
-
- def require(file, *extras) #:nodoc:
- ActiveSupport::Dependencies.new_constants_in(Object) { super }
- rescue Exception => exception # errors from required file
- exception.blame_file! file
- raise
- end
-
- # Mark the given constant as unloadable. Unloadable constants are removed each
- # time dependencies are cleared.
- #
- # Note that marking a constant for unloading need only be done once. Setup
- # or init scripts may list each unloadable constant that may need unloading;
- # each constant will be removed for every subsequent clear, as opposed to for
- # the first clear.
- #
- # The provided constant descriptor may be a (non-anonymous) module or class,
- # or a qualified constant name as a string or symbol.
- #
- # Returns true if the constant was not previously marked for unloading, false
- # otherwise.
- def unloadable(const_desc)
- ActiveSupport::Dependencies.mark_for_unload const_desc
- end
-end
-
-# Add file-blaming to exceptions
-class Exception #:nodoc:
- def blame_file!(file)
- (@blamed_files ||= []).unshift file
- end
-
- def blamed_files
- @blamed_files ||= []
- end
-
- def describe_blame
- return nil if blamed_files.empty?
- "This error occurred while loading the following files:\n #{blamed_files.join "\n "}"
- end
-
- def copy_blame!(exc)
- @blamed_files = exc.blamed_files.clone
- self
- end
-end
+ActiveSupport::Dependencies.inject!
--
cgit v1.2.3
From 006cbb8fde3f20a684eabcfd11c53ae762cf8435 Mon Sep 17 00:00:00 2001
From: Michael Koziarski
Date: Sun, 20 Jul 2008 21:23:18 +0200
Subject: Handle the case where 64bit time_t won't overflow.
---
activesupport/test/core_ext/time_ext_test.rb | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 17a0968c0e..14309f4e2d 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -528,7 +528,12 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
assert_equal Time.time_with_datetime_fallback(:utc, 2005), Time.utc(2005)
assert_equal Time.time_with_datetime_fallback(:utc, 2039), DateTime.civil(2039, 1, 1, 0, 0, 0, 0, 0)
assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30, 1), Time.utc(2005, 2, 21, 17, 44, 30, 1) #with usec
- assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1), DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0)
+ # This won't overflow on 64bit linux
+ expected_to_overflow = Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1)
+ unless expected_to_overflow.is_a?(Time)
+ assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1),
+ DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0)
+ end
assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value
end
--
cgit v1.2.3
From 5bec5848b22527ee77c007565f7eea336e5c864f Mon Sep 17 00:00:00 2001
From: Michael Koziarski
Date: Sun, 20 Jul 2008 21:25:31 +0200
Subject: If it doesn't overflow, it won't have a .start method. So don't run
the second assertion either
---
activesupport/test/core_ext/time_ext_test.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 14309f4e2d..8740497b3d 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -533,8 +533,8 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
unless expected_to_overflow.is_a?(Time)
assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1),
DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0)
+ assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value
end
- assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value
end
def test_utc_time
--
cgit v1.2.3
From ff9f6fcc75526d9fd89be834982dec8624c909c5 Mon Sep 17 00:00:00 2001
From: Clemens Kofler
Date: Mon, 21 Jul 2008 12:57:15 -0500
Subject: Refactor DateHelper and improve test coverage [#665 state:resolved]
Signed-off-by: Joshua Peek
---
actionpack/lib/action_view/helpers/date_helper.rb | 298 +++++++++++-----------
actionpack/test/template/date_helper_test.rb | 144 +++++++----
2 files changed, 238 insertions(+), 204 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 2cdb9a224e..c7a1d40ff2 100755
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -3,14 +3,15 @@ require 'action_view/helpers/tag_helper'
module ActionView
module Helpers
- # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the select-type methods
- # share a number of common options that are as follows:
+ # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the
+ # select-type methods share a number of common options that are as follows:
#
- # * :prefix - overwrites the default prefix of "date" used for the select names. So specifying "birthday" would give
- # birthday[month] instead of date[month] if passed to the select_month method.
+ # * :prefix - overwrites the default prefix of "date" used for the select names. So specifying "birthday"
+ # would give birthday[month] instead of date[month] if passed to the select_month method.
# * :include_blank - set to true if it should be possible to set an empty date.
- # * :discard_type - set to true if you want to discard the type part of the select name. If set to true, the select_month
- # method would use simply "date" (which can be overwritten using :prefix ) instead of "date[month]".
+ # * :discard_type - set to true if you want to discard the type part of the select name. If set to true,
+ # the select_month method would use simply "date" (which can be overwritten using :prefix ) instead of
+ # "date[month]".
module DateHelper
include ActionView::Helpers::TagHelper
DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX')
@@ -67,7 +68,7 @@ module ActionView
I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
case distance_in_minutes
when 0..1
- return distance_in_minutes == 0 ?
+ return distance_in_minutes == 0 ?
locale.t(:less_than_x_minutes, :count => 1) :
locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds
@@ -91,7 +92,7 @@ module ActionView
else locale.t :over_x_years, :count => (distance_in_minutes / 525600).round
end
end
- end
+ end
# Like distance_of_time_in_words, but where to_time is fixed to Time.now .
#
@@ -107,15 +108,18 @@ module ActionView
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
- # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by
- # +method+) on an object assigned to the template (identified by +object+). It's possible to tailor the selects through the +options+ hash,
- # which accepts all the keys that each of the individual select builders do (like :use_month_numbers for select_month) as well as a range of
- # discard options. The discard options are :discard_year , :discard_month and :discard_day . Set to true, they'll
- # drop the respective select. Discarding the month select will also automatically discard the day select. It's also possible to explicitly
- # set the order of the tags using the :order option with an array of symbols :year , :month and :day in
- # the desired order. Symbols may be omitted and the respective select is not included.
+ # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based
+ # attribute (identified by +method+) on an object assigned to the template (identified by +object+). It's
+ # possible to tailor the selects through the +options+ hash, which accepts all the keys that each of the
+ # individual select builders do (like :use_month_numbers for select_month) as well as a range of discard
+ # options. The discard options are :discard_year , :discard_month and :discard_day . Set
+ # to true, they'll drop the respective select. Discarding the month select will also automatically discard the
+ # day select. It's also possible to explicitly set the order of the tags using the :order option with an
+ # array of symbols :year , :month and :day in the desired order. Symbols may be omitted
+ # and the respective select is not included.
#
- # Pass the :default option to set the default date. Use a Time object or a Hash of :year , :month , :day , :hour , :minute , and :second .
+ # Pass the :default option to set the default date. Use a Time object or a Hash of :year ,
+ # :month , :day , :hour , :minute , and :second .
#
# Passing :disabled => true as part of the +options+ will make elements inaccessible for change.
#
@@ -133,7 +137,7 @@ module ActionView
#
# # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute,
# # with the year in the year drop down box starting at 1995, numbers used for months instead of words,
- # # and without a day select box.
+ # # and without a day select box.
# date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true,
# :discard_day => true, :include_blank => true)
#
@@ -155,8 +159,8 @@ module ActionView
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
- # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
- # choices are valid.
+ # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
+ # all month choices are valid.
def date_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
end
@@ -180,12 +184,12 @@ module ActionView
# # Creates a time select tag that, when POSTed, will be stored in the mail variable in the sent_at attribute
# time_select("mail", "sent_at")
#
- # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in
- # # the sunrise attribute.
+ # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in
+ # # the sunrise attribute.
# time_select("post", "start_time", :include_seconds => true)
#
- # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in
- # # the submission_time attribute.
+ # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in
+ # # the submission_time attribute.
# time_select("entry", "submission_time", :include_seconds => true)
#
# # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45.
@@ -193,14 +197,15 @@ module ActionView
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
#
- # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
- # choices are valid.
+ # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
+ # all month choices are valid.
def time_select(object_name, method, options = {}, html_options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options)
end
- # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
- # attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples:
+ # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a
+ # specified datetime-based attribute (identified by +method+) on an object assigned to the template (identified
+ # by +object+). Examples:
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
@@ -208,16 +213,16 @@ module ActionView
# # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on attribute
# datetime_select("post", "written_on")
#
- # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
+ # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
# # post variable in the written_on attribute.
# datetime_select("post", "written_on", :start_year => 1995)
#
- # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will be stored in the
- # # trip variable in the departing attribute.
+ # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will
+ # # be stored in the trip variable in the departing attribute.
# datetime_select("trip", "departing", :default => 3.days.from_now)
#
- # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable as the written_on
- # # attribute.
+ # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable
+ # # as the written_on attribute.
# datetime_select("post", "written_on", :discard_type => true)
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
@@ -227,9 +232,10 @@ module ActionView
# Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
# It's also possible to explicitly set the order of the tags using the :order option with an array of
- # symbols :year , :month and :day in the desired order. If you do not supply a Symbol, it
- # will be appended onto the :order passed in. You can also add :date_separator and :time_separator
- # keys to the +options+ to control visual display of the elements.
+ # symbols :year , :month and :day in the desired order. If you do not supply a Symbol,
+ # it will be appended onto the :order passed in. You can also add :date_separator ,
+ # :datetime_separator and :time_separator keys to the +options+ to control visual display of
+ # the elements.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
@@ -250,7 +256,12 @@ module ActionView
# # with a '/' between each date field.
# select_datetime(my_date_time, :date_separator => '/')
#
- # # Generates a datetime select that discards the type of the field and defaults to the datetime in
+ # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
+ # # with a date fields separated by '/', time fields separated by '' and the date and time fields
+ # # separated by a comma (',').
+ # select_datetime(my_date_time, :date_separator => '/', :time_separator => '', :datetime_separator => ',')
+ #
+ # # Generates a datetime select that discards the type of the field and defaults to the datetime in
# # my_date_time (four days after today)
# select_datetime(my_date_time, :discard_type => true)
#
@@ -261,7 +272,7 @@ module ActionView
def select_datetime(datetime = Time.current, options = {}, html_options = {})
separator = options[:datetime_separator] || ''
select_date(datetime, options, html_options) + separator + select_time(datetime, options, html_options)
- end
+ end
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
# It's possible to explicitly set the order of the tags using the :order option with an array of
@@ -283,27 +294,29 @@ module ActionView
# # with the fields ordered year, month, day rather than month, day, year.
# select_date(my_date, :order => [:year, :month, :day])
#
- # # Generates a date select that discards the type of the field and defaults to the date in
+ # # Generates a date select that discards the type of the field and defaults to the date in
# # my_date (six days after today)
# select_date(my_date, :discard_type => true)
#
+ # # Generates a date select that defaults to the date in my_date,
+ # # which has fields separated by '/'
+ # select_date(my_date, :date_separator => '/')
+ #
# # Generates a date select that defaults to the datetime in my_date (six days after today)
# # prefixed with 'payday' rather than 'date'
# select_date(my_date, :prefix => 'payday')
#
def select_date(date = Date.current, options = {}, html_options = {})
- options[:order] ||= []
+ options.reverse_merge!(:order => [], :date_separator => '')
[:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) }
- select_date = ''
- options[:order].each do |o|
- select_date << self.send("select_#{o}", date, options, html_options)
- end
- select_date
+ options[:order].inject([]) { |s, o|
+ s << self.send("select_#{o}", date, options, html_options)
+ }.join(options[:date_separator])
end
# Returns a set of html select-tags (one for hour and minute)
- # You can set :time_separator key to format the output, and
+ # You can set :time_separator key to format the output, and
# the :include_seconds option to include an input for seconds.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
@@ -318,7 +331,7 @@ module ActionView
# select_time()
#
# # Generates a time select that defaults to the time in my_time,
- # # which has fields separated by ':'
+ # # which has fields separated by ':'
# select_time(my_time, :time_separator => ':')
#
# # Generates a time select that defaults to the time in my_time,
@@ -331,7 +344,8 @@ module ActionView
#
def select_time(datetime = Time.current, options = {}, html_options = {})
separator = options[:time_separator] || ''
- select_hour(datetime, options, html_options) + separator + select_minute(datetime, options, html_options) + (options[:include_seconds] ? separator + select_second(datetime, options, html_options) : '')
+ select_hour(datetime, options, html_options) + separator + select_minute(datetime, options, html_options) +
+ (options[:include_seconds] ? separator + select_second(datetime, options, html_options) : '')
end
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
@@ -346,26 +360,16 @@ module ActionView
#
# # Generates a select field for seconds that defaults to the number given
# select_second(33)
- #
+ #
# # Generates a select field for seconds that defaults to the seconds for the time in my_time
# # that is named 'interval' rather than 'second'
# select_second(my_time, :field_name => 'interval')
#
def select_second(datetime, options = {}, html_options = {})
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : ''
- if options[:use_hidden]
- options[:include_seconds] ? hidden_html(options[:field_name] || 'second', val, options) : ''
- else
- second_options = []
- 0.upto(59) do |second|
- second_options << ((val == second) ?
- content_tag(:option, leading_zero_on_single_digits(second), :value => leading_zero_on_single_digits(second), :selected => "selected") :
- content_tag(:option, leading_zero_on_single_digits(second), :value => leading_zero_on_single_digits(second))
- )
- second_options << "\n"
- end
- select_html(options[:field_name] || 'second', second_options.join, options, html_options)
- end
+ options[:use_hidden] ?
+ (options[:include_seconds] ? _date_hidden_html(options[:field_name] || 'second', val, options) : '') :
+ _date_select_html(options[:field_name] || 'second', _date_build_options(val), options, html_options)
end
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
@@ -381,26 +385,17 @@ module ActionView
#
# # Generates a select field for minutes that defaults to the number given
# select_minute(14)
- #
+ #
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
# # that is named 'stride' rather than 'second'
# select_minute(my_time, :field_name => 'stride')
#
def select_minute(datetime, options = {}, html_options = {})
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : ''
- if options[:use_hidden]
- hidden_html(options[:field_name] || 'minute', val, options)
- else
- minute_options = []
- 0.step(59, options[:minute_step] || 1) do |minute|
- minute_options << ((val == minute) ?
- content_tag(:option, leading_zero_on_single_digits(minute), :value => leading_zero_on_single_digits(minute), :selected => "selected") :
- content_tag(:option, leading_zero_on_single_digits(minute), :value => leading_zero_on_single_digits(minute))
- )
- minute_options << "\n"
- end
- select_html(options[:field_name] || 'minute', minute_options.join, options, html_options)
- end
+ options[:use_hidden] ?
+ _date_hidden_html(options[:field_name] || 'minute', val, options) :
+ _date_select_html(options[:field_name] || 'minute',
+ _date_build_options(val, :step => options[:minute_step]), options, html_options)
end
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
@@ -415,26 +410,15 @@ module ActionView
#
# # Generates a select field for minutes that defaults to the number given
# select_minute(14)
- #
+ #
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
# # that is named 'stride' rather than 'second'
# select_minute(my_time, :field_name => 'stride')
#
def select_hour(datetime, options = {}, html_options = {})
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : ''
- if options[:use_hidden]
- hidden_html(options[:field_name] || 'hour', val, options)
- else
- hour_options = []
- 0.upto(23) do |hour|
- hour_options << ((val == hour) ?
- content_tag(:option, leading_zero_on_single_digits(hour), :value => leading_zero_on_single_digits(hour), :selected => "selected") :
- content_tag(:option, leading_zero_on_single_digits(hour), :value => leading_zero_on_single_digits(hour))
- )
- hour_options << "\n"
- end
- select_html(options[:field_name] || 'hour', hour_options.join, options, html_options)
- end
+ options[:use_hidden] ? _date_hidden_html(options[:field_name] || 'hour', val, options) :
+ _date_select_html(options[:field_name] || 'hour', _date_build_options(val, :end => 23), options, html_options)
end
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
@@ -449,36 +433,27 @@ module ActionView
#
# # Generates a select field for days that defaults to the number given
# select_day(5)
- #
+ #
# # Generates a select field for days that defaults to the day for the date in my_date
# # that is named 'due' rather than 'day'
# select_day(my_time, :field_name => 'due')
#
def select_day(date, options = {}, html_options = {})
val = date ? (date.kind_of?(Fixnum) ? date : date.day) : ''
- if options[:use_hidden]
- hidden_html(options[:field_name] || 'day', val, options)
- else
- day_options = []
- 1.upto(31) do |day|
- day_options << ((val == day) ?
- content_tag(:option, day, :value => day, :selected => "selected") :
- content_tag(:option, day, :value => day)
- )
- day_options << "\n"
- end
- select_html(options[:field_name] || 'day', day_options.join, options, html_options)
- end
+ options[:use_hidden] ? _date_hidden_html(options[:field_name] || 'day', val, options) :
+ _date_select_html(options[:field_name] || 'day',
+ _date_build_options(val, :start => 1, :end => 31, :leading_zeros => false),
+ options, html_options)
end
- # Returns a select tag with options for each of the months January through December with the current month selected.
- # The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
- # (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
- # set the :use_month_numbers key in +options+ to true for this to happen. If you want both numbers and names,
- # set the :add_month_numbers key in +options+ to true. If you would prefer to show month names as abbreviations,
- # set the :use_short_month key in +options+ to true. If you want to use your own month names, set the
- # :use_month_names key in +options+ to an array of 12 month names. Override the field name using the
- # :field_name option, 'month' by default.
+ # Returns a select tag with options for each of the months January through December with the current month
+ # selected. The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are
+ # used as values (what's submitted to the server). It's also possible to use month numbers for the presentation
+ # instead of names -- set the :use_month_numbers key in +options+ to true for this to happen. If you
+ # want both numbers and names, set the :add_month_numbers key in +options+ to true. If you would prefer
+ # to show month names as abbreviations, set the :use_short_month key in +options+ to true. If you want
+ # to use your own month names, set the :use_month_names key in +options+ to an array of 12 month names.
+ # Override the field name using the :field_name option, 'month' by default.
#
# ==== Examples
# # Generates a select field for months that defaults to the current month that
@@ -490,7 +465,7 @@ module ActionView
# select_month(Date.today, :field_name => 'start')
#
# # Generates a select field for months that defaults to the current month that
- # # will use keys like "1", "3".
+ # # will use keys like "1", "3".
# select_month(Date.today, :use_month_numbers => true)
#
# # Generates a select field for months that defaults to the current month that
@@ -506,11 +481,11 @@ module ActionView
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
#
def select_month(date, options = {}, html_options = {})
- locale = options[:locale]
+ locale = options[:locale]
val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
if options[:use_hidden]
- hidden_html(options[:field_name] || 'month', val, options)
+ _date_hidden_html(options[:field_name] || 'month', val, options)
else
month_options = []
month_names = options[:use_month_names] || begin
@@ -534,14 +509,15 @@ module ActionView
)
month_options << "\n"
end
- select_html(options[:field_name] || 'month', month_options.join, options, html_options)
+ _date_select_html(options[:field_name] || 'month', month_options.join, options, html_options)
end
- end
+ end
- # Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
- # can be changed using the :start_year and :end_year keys in the +options+. Both ascending and descending year
- # lists are supported by making :start_year less than or greater than :end_year . The date can also be
- # substituted for a year given as a number. Override the field name using the :field_name option, 'year' by default.
+ # Returns a select tag with options for each of the five years on each side of the current, which is selected.
+ # The five year radius can be changed using the :start_year and :end_year keys in the
+ # +options+. Both ascending and descending year lists are supported by making :start_year less than or
+ # greater than :end_year . The date can also be substituted for a year given as a number.
+ # Override the field name using the :field_name option, 'year' by default.
#
# ==== Examples
# # Generates a select field for years that defaults to the current year that
@@ -562,38 +538,48 @@ module ActionView
#
def select_year(date, options = {}, html_options = {})
if !date || date == 0
- value = ''
+ val = ''
middle_year = Date.today.year
elsif date.kind_of?(Fixnum)
- value = middle_year = date
+ val = middle_year = date
else
- value = middle_year = date.year
+ val = middle_year = date.year
end
if options[:use_hidden]
- hidden_html(options[:field_name] || 'year', value, options)
+ _date_hidden_html(options[:field_name] || 'year', val, options)
else
- year_options = ''
- start_year = options[:start_year] || middle_year - 5
- end_year = options[:end_year] || middle_year + 5
- step_val = start_year < end_year ? 1 : -1
-
- start_year.step(end_year, step_val) do |year|
- if value == year
- year_options << content_tag(:option, year, :value => year, :selected => "selected")
- else
- year_options << content_tag(:option, year, :value => year)
- end
- year_options << "\n"
- end
- select_html(options[:field_name] || 'year', year_options, options, html_options)
+ options[:start_year] ||= middle_year - 5
+ options[:end_year] ||= middle_year + 5
+ step = options[:start_year] < options[:end_year] ? 1 : -1
+
+ _date_select_html(options[:field_name] || 'year',
+ _date_build_options(val,
+ :start => options[:start_year],
+ :end => options[:end_year],
+ :step => step,
+ :leading_zeros => false
+ ), options, html_options)
end
end
private
+ def _date_build_options(selected, options={})
+ options.reverse_merge!(:start => 0, :end => 59, :step => 1, :leading_zeros => true)
+
+ select_options = []
+ (options[:start] || 0).step((options[:end] || 59), options[:step] || 1) do |i|
+ value = options[:leading_zeros] ? sprintf("%02d", i) : i
+ tag_options = { :value => value }
+ tag_options[:selected] = "selected" if selected == i
+
+ select_options << content_tag(:option, value, tag_options)
+ end
+ select_options.join("\n") + "\n"
+ end
- def select_html(type, html_options, options, select_tag_options = {})
- name_and_id_from_options(options, type)
+ def _date_select_html(type, html_options, options, select_tag_options = {})
+ _date_name_and_id_from_options(options, type)
select_options = {:id => options[:id], :name => options[:name]}
select_options.merge!(:disabled => 'disabled') if options[:disabled]
select_options.merge!(select_tag_options) unless select_tag_options.empty?
@@ -603,19 +589,15 @@ module ActionView
content_tag(:select, select_html, select_options) + "\n"
end
- def hidden_html(type, value, options)
- name_and_id_from_options(options, type)
+ def _date_hidden_html(type, value, options)
+ _date_name_and_id_from_options(options, type)
hidden_html = tag(:input, :type => "hidden", :id => options[:id], :name => options[:name], :value => value) + "\n"
end
- def name_and_id_from_options(options, type)
+ def _date_name_and_id_from_options(options, type)
options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
end
-
- def leading_zero_on_single_digits(number)
- number > 9 ? number : "0#{number}"
- end
end
class InstanceTag #:nodoc:
@@ -641,11 +623,11 @@ module ActionView
options = defaults.merge(options)
datetime = value(object)
datetime ||= default_time_from_options(options[:default]) unless options[:include_blank]
-
+
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
order = options[:order] ||= I18n.translate(:'date.order', :locale => locale)
-
+
# Discard explicit and implicit by not being included in the :order
discard = {}
discard[:year] = true if options[:discard_year] or !order.include?(:year)
@@ -654,26 +636,30 @@ module ActionView
discard[:hour] = true if options[:discard_hour]
discard[:minute] = true if options[:discard_minute] or discard[:hour]
discard[:second] = true unless options[:include_seconds] && !discard[:minute]
-
+
# If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are valid
# (otherwise it could be 31 and february wouldn't be a valid date)
if datetime && discard[:day] && !discard[:month]
datetime = datetime.change(:day => 1)
end
-
+
# Maintain valid dates by including hidden fields for discarded elements
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
-
+
# Ensure proper ordering of :hour, :minute and :second
[:hour, :minute, :second].each { |o| order.delete(o); order.push(o) }
-
+
date_or_time_select = ''
order.reverse.each do |param|
# Send hidden fields for discarded elements once output has started
# This ensures AR can reconstruct valid dates using ParseDate
next if discard[param] && (date_or_time_select.empty? || options[:ignore_date])
- date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param])), html_options))
+ date_or_time_select.insert(0,
+ self.send("select_#{param}",
+ datetime,
+ options_with_prefix(position[param], options.merge(:use_hidden => discard[param])),
+ html_options))
date_or_time_select.insert(0,
case param
when :hour then (discard[:year] && discard[:day] ? "" : " — ")
@@ -682,7 +668,7 @@ module ActionView
else ""
end)
end
-
+
date_or_time_select
end
@@ -708,7 +694,7 @@ module ActionView
default[:sec] ||= default[:second]
time = Time.current
-
+
[:year, :month, :day, :hour, :min, :sec].each do |key|
default[key] ||= time.send(key)
end
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 8b4e94c67f..d8c07e731b 100755
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -17,7 +17,7 @@ class DateHelperTest < ActionView::TestCase
end
end
end
-
+
def assert_distance_of_time_in_words(from, to=nil)
to ||= from
@@ -86,13 +86,13 @@ class DateHelperTest < ActionView::TestCase
from = Time.mktime(2004, 6, 6, 21, 45, 0)
assert_distance_of_time_in_words(from)
end
-
+
def test_distance_in_words_with_time_zones
from = Time.mktime(2004, 6, 6, 21, 45, 0)
assert_distance_of_time_in_words(from.in_time_zone('Alaska'))
assert_distance_of_time_in_words(from.in_time_zone('Hawaii'))
end
-
+
def test_distance_in_words_with_different_time_zones
from = Time.mktime(2004, 6, 6, 21, 45, 0)
assert_distance_of_time_in_words(
@@ -100,13 +100,13 @@ class DateHelperTest < ActionView::TestCase
from.in_time_zone('Hawaii')
)
end
-
+
def test_distance_in_words_with_dates
start_date = Date.new 1975, 1, 31
end_date = Date.new 1977, 1, 31
assert_equal("over 2 years", distance_of_time_in_words(start_date, end_date))
end
-
+
def test_distance_in_words_with_integers
assert_equal "less than a minute", distance_of_time_in_words(59)
assert_equal "about 1 hour", distance_of_time_in_words(60*60)
@@ -757,6 +757,26 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]"}, :class => "selector")
end
+ def test_select_date_with_separator
+ expected = %(\n)
+ expected << %(2003 \n2004 \n2005 \n)
+ expected << " \n"
+
+ expected << " / "
+
+ expected << %(\n)
+ expected << %(January \nFebruary \nMarch \nApril \nMay \nJune \nJuly \nAugust \nSeptember \nOctober \nNovember \nDecember \n)
+ expected << " \n"
+
+ expected << " / "
+
+ expected << %(\n)
+ expected << %(1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n)
+ expected << " \n"
+
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :start_year => 2003, :end_year => 2005, :prefix => "date[first]"})
+ end
+
def test_select_datetime
expected = %(\n)
expected << %(2003 \n2004 \n2005 \n)
@@ -857,6 +877,38 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]"}, :class => 'selector')
end
+ def test_select_datetime_with_all_separators
+ expected = %(\n)
+ expected << %(2003 \n2004 \n2005 \n)
+ expected << " \n"
+
+ expected << "/"
+
+ expected << %(\n)
+ expected << %(January \nFebruary \nMarch \nApril \nMay \nJune \nJuly \nAugust \nSeptember \nOctober \nNovember \nDecember \n)
+ expected << " \n"
+
+ expected << "/"
+
+ expected << %(\n)
+ expected << %(1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n)
+ expected << " \n"
+
+ expected << "—"
+
+ expected << %(\n)
+ expected << %(00 \n01 \n02 \n03 \n04 \n05 \n06 \n07 \n08 \n09 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n)
+ expected << " \n"
+
+ expected << ":"
+
+ expected << %(\n)
+ expected << %(00 \n01 \n02 \n03 \n04 \n05 \n06 \n07 \n08 \n09 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n32 \n33 \n34 \n35 \n36 \n37 \n38 \n39 \n40 \n41 \n42 \n43 \n44 \n45 \n46 \n47 \n48 \n49 \n50 \n51 \n52 \n53 \n54 \n55 \n56 \n57 \n58 \n59 \n)
+ expected << " \n"
+
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), { :datetime_separator => "—", :date_separator => "/", :time_separator => ":", :start_year => 2003, :end_year => 2005, :prefix => "date[first]"}, :class => 'selector')
+ end
+
def test_select_time
expected = %(\n)
expected << %(00 \n01 \n02 \n03 \n04 \n05 \n06 \n07 \n08 \n09 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n)
@@ -933,7 +985,7 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector')
assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {:include_seconds => false}, :class => 'selector')
end
-
+
uses_mocha 'TestDatetimeAndTimeSelectUseTimeCurrentAsDefault' do
def test_select_datetime_uses_time_current_as_default
time = stub(:year => 2004, :month => 6, :day => 15, :hour => 16, :min => 35, :sec => 0)
@@ -942,7 +994,7 @@ class DateHelperTest < ActionView::TestCase
expects(:select_time).with(time, anything, anything).returns('')
select_datetime
end
-
+
def test_select_time_uses_time_current_as_default
time = stub(:year => 2004, :month => 6, :day => 15, :hour => 16, :min => 35, :sec => 0)
Time.expects(:current).returns time
@@ -950,7 +1002,7 @@ class DateHelperTest < ActionView::TestCase
expects(:select_minute).with(time, anything, anything).returns('')
select_time
end
-
+
def test_select_date_uses_date_current_as_default
date = stub(:year => 2004, :month => 6, :day => 15)
Date.expects(:current).returns date
@@ -1188,11 +1240,11 @@ class DateHelperTest < ActionView::TestCase
expected << %{ \n}
expected << %(\n)
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %(\n)
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, time_select("post", "written_on")
@@ -1203,11 +1255,11 @@ class DateHelperTest < ActionView::TestCase
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
expected = %(\n)
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %(\n)
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, time_select("post", "written_on", :ignore_date => true)
@@ -1222,15 +1274,15 @@ class DateHelperTest < ActionView::TestCase
expected << %{ \n}
expected << %(\n)
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %(\n)
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %(\n)
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, time_select("post", "written_on", :include_seconds => true)
@@ -1245,11 +1297,11 @@ class DateHelperTest < ActionView::TestCase
expected << %{ \n}
expected << %(\n)
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %(\n)
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, time_select("post", "written_on", {}, :class => 'selector')
@@ -1268,11 +1320,11 @@ class DateHelperTest < ActionView::TestCase
expected << %{ \n}
expected << %(\n)
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %(\n)
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, output_buffer
@@ -1306,7 +1358,7 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, datetime_select("post", "updated_at")
end
-
+
uses_mocha 'TestDatetimeSelectDefaultsToTimeZoneNowWhenConfigTimeZoneIsSet' do
def test_datetime_select_defaults_to_time_zone_now_when_config_time_zone_is_set
time = stub(:year => 2004, :month => 6, :day => 15, :hour => 16, :min => 35, :sec => 0)
@@ -1370,8 +1422,7 @@ class DateHelperTest < ActionView::TestCase
expected << " \n"
expected << %(\n)
- expected <<
-%(1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n)
+ expected << %(1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n)
expected << " \n"
assert_dom_equal expected, select_date(0, :end_year => Date.today.year+1, :prefix => "date[first]")
@@ -1388,8 +1439,7 @@ class DateHelperTest < ActionView::TestCase
expected << " \n"
expected << %(\n)
- expected <<
-%(1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n)
+ expected << %(1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n)
expected << " \n"
assert_dom_equal expected, select_date(0, :start_year => 2003, :prefix => "date[first]")
@@ -1405,8 +1455,7 @@ class DateHelperTest < ActionView::TestCase
expected << " \n"
expected << %(\n)
- expected <<
-%(1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n)
+ expected << %(1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n)
expected << " \n"
assert_dom_equal expected, select_date(0, :prefix => "date[first]")
@@ -1422,8 +1471,7 @@ class DateHelperTest < ActionView::TestCase
expected << " \n"
expected << %(\n)
- expected <<
-%(1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n)
+ expected << %(1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n)
expected << " \n"
assert_dom_equal expected, select_date(nil, :prefix => "date[first]")
@@ -1530,15 +1578,15 @@ class DateHelperTest < ActionView::TestCase
expected << " — "
expected << %{\n}
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %{\n}
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %{\n}
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, datetime_select("post", "updated_at", :include_seconds => true)
@@ -1559,11 +1607,11 @@ class DateHelperTest < ActionView::TestCase
expected << " — "
expected << %{\n}
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %{\n}
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, datetime_select("post", "updated_at", :discard_year => true)
@@ -1582,11 +1630,11 @@ class DateHelperTest < ActionView::TestCase
expected << " — "
expected << %{\n}
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %{\n}
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, datetime_select("post", "updated_at", :discard_month => true)
@@ -1601,11 +1649,11 @@ class DateHelperTest < ActionView::TestCase
expected << %{ \n}
expected << %{\n}
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %{\n}
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, datetime_select("post", "updated_at", :discard_year => true, :discard_month => true)
@@ -1628,11 +1676,11 @@ class DateHelperTest < ActionView::TestCase
expected << " — "
expected << %{\n}
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %{\n}
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, datetime_select("post", "updated_at", :order => [:minute, :day, :hour, :month, :year, :second])
@@ -1653,11 +1701,11 @@ class DateHelperTest < ActionView::TestCase
expected << " — "
expected << %{\n}
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %{\n}
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, datetime_select("post", "updated_at", :order => [:day, :month])
@@ -1680,11 +1728,11 @@ class DateHelperTest < ActionView::TestCase
expected << " — "
expected << %{\n}
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %{\n}
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, datetime_select("post", "updated_at", :default => Time.local(2006, 9, 19, 15, 16, 35))
@@ -1727,11 +1775,11 @@ class DateHelperTest < ActionView::TestCase
expected << " — "
expected << %{\n}
- 0.upto(23) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(23) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
expected << " : "
expected << %{\n}
- 0.upto(59) { |i| expected << %(#{leading_zero_on_single_digits(i)} \n) }
+ 0.upto(59) { |i| expected << %(#{sprintf("%02d", i)} \n) }
expected << " \n"
assert_dom_equal expected, datetime_select("post", "updated_at", :default => { :month => 10, :minute => 42, :hour => 9 })
@@ -1780,19 +1828,19 @@ class DateHelperTest < ActionView::TestCase
assert_equal 2, dummy_instance_tag.send!(:default_time_from_options, :hour => 2).hour
end
end
-
+
def test_instance_tag_default_time_from_options_handles_far_future_date
dummy_instance_tag = ActionView::Helpers::InstanceTag.new(1,2,3)
time = dummy_instance_tag.send!(:default_time_from_options, :year => 2050, :month => 2, :day => 10, :hour => 15, :min => 30, :sec => 45)
assert_equal 2050, time.year
end
end
-
+
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
yield
ensure
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
+ end
end
--
cgit v1.2.3
From 0f43de644ea48c1ad11d4bc73307af066bb52870 Mon Sep 17 00:00:00 2001
From: Clemens Kofler
Date: Mon, 21 Jul 2008 13:05:27 -0500
Subject: Refactored NumberHelper API to accept arguments as an options hash
[#666 state:resolved]
Signed-off-by: Joshua Peek
---
.../lib/action_view/helpers/number_helper.rb | 98 +++++++++++++++-------
actionpack/test/template/number_helper_test.rb | 28 ++++++-
2 files changed, 93 insertions(+), 33 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 6bb8263794..c4ba7ccc8e 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -22,7 +22,7 @@ module ActionView
# number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234
#
# number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
- # => +1.123.555.1234 x 1343
+ # => +1.123.555.1234 x 1343
def number_to_phone(number, options = {})
number = number.to_s.strip unless number.nil?
options = options.stringify_keys
@@ -71,14 +71,14 @@ module ActionView
def number_to_currency(number, options = {})
options = options.symbolize_keys
defaults = I18n.translate(:'currency.format', :locale => options[:locale]) || {}
-
+
precision = options[:precision] || defaults[:precision]
unit = options[:unit] || defaults[:unit]
separator = options[:separator] || defaults[:separator]
delimiter = options[:delimiter] || defaults[:delimiter]
format = options[:format] || defaults[:format]
separator = '' if precision == 0
-
+
begin
parts = number_with_precision(number, precision).split('.')
format.gsub(/%n/, number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s).gsub(/%u/, unit)
@@ -118,49 +118,72 @@ module ActionView
end
end
- # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You
- # can customize the format using optional delimiter and separator parameters.
+ # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can
+ # customize the format in the +options+ hash.
#
# ==== Options
- # * delimiter - Sets the thousands delimiter (defaults to ",").
- # * separator - Sets the separator between the units (defaults to ".").
+ # * :delimiter - Sets the thousands delimiter (defaults to ",").
+ # * :separator - Sets the separator between the units (defaults to ".").
#
# ==== Examples
- # number_with_delimiter(12345678) # => 12,345,678
- # number_with_delimiter(12345678.05) # => 12,345,678.05
- # number_with_delimiter(12345678, ".") # => 12.345.678
- #
- # number_with_delimiter(98765432.98, " ", ",")
+ # number_with_delimiter(12345678) # => 12,345,678
+ # number_with_delimiter(12345678.05) # => 12,345,678.05
+ # number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678
+ # number_with_delimiter(12345678, :seperator => ",") # => 12,345,678
+ # number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",")
# # => 98 765 432,98
- def number_with_delimiter(number, delimiter=",", separator=".")
+ #
+ # You can still use number_with_delimiter with the old API that accepts the
+ # +delimiter+ as its optional second and the +separator+ as its
+ # optional third parameter:
+ # number_with_delimiter(12345678, " ") # => 12 345.678
+ # number_with_delimiter(12345678.05, ".", ",") # => 12.345.678,05
+ def number_with_delimiter(number, *args)
+ options = args.extract_options!
+ unless args.empty?
+ options[:delimiter] = args[0] || ","
+ options[:separator] = args[1] || "."
+ end
+ options.reverse_merge!(:delimiter => ",", :separator => ".")
+
begin
parts = number.to_s.split('.')
- parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
- parts.join separator
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
+ parts.join options[:separator]
rescue
number
end
end
- # Formats a +number+ with the specified level of +precision+ (e.g., 112.32 has a precision of 2). The default
- # level of precision is 3.
+ # Formats a +number+ with the specified level of :precision (e.g., 112.32 has a precision of 2).
+ # The default level of precision is 3.
#
# ==== Examples
- # number_with_precision(111.2345) # => 111.235
- # number_with_precision(111.2345, 2) # => 111.23
- # number_with_precision(13, 5) # => 13.00000
- # number_with_precision(389.32314, 0) # => 389
- def number_with_precision(number, precision=3)
- "%01.#{precision}f" % ((Float(number) * (10 ** precision)).round.to_f / 10 ** precision)
+ # number_with_precision(111.2345) # => 111.235
+ # number_with_precision(111.2345, :precision => 2) # => 111.23
+ # number_with_precision(13, :precision => 5) # => 13.00000
+ # number_with_precision(389.32314, :precision => 0) # => 389
+ #
+ # You can still use number_with_precision with the old API that accepts the
+ # +precision+ as its optional second parameter:
+ # number_with_precision(number_with_precision(111.2345, 2) # => 111.23
+ def number_with_precision(number, *args)
+ options = args.extract_options!
+ unless args.empty?
+ options[:precision] = args[0] || 3
+ end
+ options.reverse_merge!(:precision => 3)
+ "%01.#{options[:precision]}f" %
+ ((Float(number) * (10 ** options[:precision])).round.to_f / 10 ** options[:precision])
rescue
number
end
# Formats the bytes in +size+ into a more understandable representation
- # (e.g., giving it 1500 yields 1.5 KB). This method is useful for
+ # (e.g., giving it 1500 yields 1.5 KB). This method is useful for
# reporting file sizes to users. This method returns nil if
# +size+ cannot be converted into a number. You can change the default
- # precision of 1 using the precision parameter +precision+.
+ # precision of 1 using the precision parameter :precision .
#
# ==== Examples
# number_to_human_size(123) # => 123 Bytes
@@ -169,17 +192,28 @@ module ActionView
# number_to_human_size(1234567) # => 1.2 MB
# number_to_human_size(1234567890) # => 1.1 GB
# number_to_human_size(1234567890123) # => 1.1 TB
+ # number_to_human_size(1234567, :precision => 2) # => 1.18 MB
+ # number_to_human_size(483989, :precision => 0) # => 473 KB
+ #
+ # You can still use number_to_human_size with the old API that accepts the
+ # +precision+ as its optional second parameter:
# number_to_human_size(1234567, 2) # => 1.18 MB
- # number_to_human_size(483989, 0) # => 4 MB
- def number_to_human_size(size, precision=1)
- size = Kernel.Float(size)
+ # number_to_human_size(483989, 0) # => 473 KB
+ def number_to_human_size(size, *args)
+ options = args.extract_options!
+ unless args.empty?
+ options[:precision] = args[0] || 1
+ end
+ options.reverse_merge!(:precision => 1)
+
+ size = Float(size)
case
when size.to_i == 1; "1 Byte"
when size < 1.kilobyte; "%d Bytes" % size
- when size < 1.megabyte; "%.#{precision}f KB" % (size / 1.0.kilobyte)
- when size < 1.gigabyte; "%.#{precision}f MB" % (size / 1.0.megabyte)
- when size < 1.terabyte; "%.#{precision}f GB" % (size / 1.0.gigabyte)
- else "%.#{precision}f TB" % (size / 1.0.terabyte)
+ when size < 1.megabyte; "%.#{options[:precision]}f KB" % (size / 1.0.kilobyte)
+ when size < 1.gigabyte; "%.#{options[:precision]}f MB" % (size / 1.0.megabyte)
+ when size < 1.terabyte; "%.#{options[:precision]}f GB" % (size / 1.0.gigabyte)
+ else "%.#{options[:precision]}f TB" % (size / 1.0.terabyte)
end.sub(/([0-9]\.\d*?)0+ /, '\1 ' ).sub(/\. /,' ')
rescue
nil
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 4a8d09b544..bff349a754 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -54,9 +54,16 @@ class NumberHelperTest < ActionView::TestCase
assert_nil number_with_delimiter(nil)
end
+ def test_number_with_delimiter_with_options_hash
+ assert_equal '12 345 678', number_with_delimiter(12345678, :delimiter => ' ')
+ assert_equal '12,345,678-05', number_with_delimiter(12345678.05, :separator => '-')
+ assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :separator => ',', :delimiter => '.')
+ assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :delimiter => '.', :separator => ',')
+ end
+
def test_number_with_precision
assert_equal("111.235", number_with_precision(111.2346))
- assert_equal("31.83", number_with_precision(31.825, 2))
+ assert_equal("31.83", number_with_precision(31.825, 2))
assert_equal("111.23", number_with_precision(111.2346, 2))
assert_equal("111.00", number_with_precision(111, 2))
assert_equal("111.235", number_with_precision("111.2346"))
@@ -69,6 +76,17 @@ class NumberHelperTest < ActionView::TestCase
assert_nil number_with_precision(nil)
end
+ def test_number_with_precision_with_options_hash
+ assert_equal '111.235', number_with_precision(111.2346)
+ assert_equal '31.83', number_with_precision(31.825, :precision => 2)
+ assert_equal '111.23', number_with_precision(111.2346, :precision => 2)
+ assert_equal '111.00', number_with_precision(111, :precision => 2)
+ assert_equal '111.235', number_with_precision("111.2346")
+ assert_equal '31.83', number_with_precision("31.825", :precision => 2)
+ assert_equal '112', number_with_precision(111.50, :precision => 0)
+ assert_equal '1234567892', number_with_precision(1234567891.50, :precision => 0)
+ end
+
def test_number_to_human_size
assert_equal '0 Bytes', number_to_human_size(0)
assert_equal '1 Byte', number_to_human_size(1)
@@ -94,4 +112,12 @@ class NumberHelperTest < ActionView::TestCase
assert_nil number_to_human_size('x')
assert_nil number_to_human_size(nil)
end
+
+ def test_number_to_human_size_with_options_hash
+ assert_equal '1.18 MB', number_to_human_size(1234567, :precision => 2)
+ assert_equal '3 Bytes', number_to_human_size(3.14159265, :precision => 4)
+ assert_equal '1.01 KB', number_to_human_size(1.0123.kilobytes, :precision => 2)
+ assert_equal '1.01 KB', number_to_human_size(1.0100.kilobytes, :precision => 4)
+ assert_equal '10 KB', number_to_human_size(10.000.kilobytes, :precision => 4)
+ end
end
--
cgit v1.2.3
From 3bd34b6ffe017dd81fd26743aab052fc4324eb0f Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sat, 14 Jun 2008 16:24:23 -0500
Subject: Preload application classes. Uses same strategy as phusion passenger.
---
railties/lib/initializer.rb | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index b8b071d4c9..3be95de8d3 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -168,6 +168,9 @@ module Rails
# Observers are loaded after plugins in case Observers or observed models are modified by plugins.
load_observers
+ # load application classes
+ load_application_classes
+
# Flag initialized
Rails.initialized = true
end
@@ -330,6 +333,14 @@ Run `rake gems:install` to install the missing gems.
end
end
+ def load_application_classes
+ require_dependency 'application'
+
+ Dir.glob('app/{models,controllers,helpers}/*.rb').each do |file|
+ require_dependency file
+ end
+ end
+
# For Ruby 1.8, this initialization sets $KCODE to 'u' to enable the
# multibyte safe operations. Plugin authors supporting other encodings
# should override this behaviour and set the relevant +default_charset+
--
cgit v1.2.3
From 89ec72c2818a592323fe4ec3277638d379f1ac2a Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Mon, 21 Jul 2008 13:42:34 -0500
Subject: Added configurable eager load paths. Defaults to app/models,
app/controllers, and app/helpers
---
railties/CHANGELOG | 2 ++
railties/lib/initializer.rb | 25 +++++++++++++++++++++----
2 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 9c5e5b59c6..5ff1867568 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* Added configurable eager load paths. Defaults to app/models, app/controllers, and app/helpers [Josh Peek]
+
* Introduce simple internationalization support. [Ruby i18n team]
* Make script/plugin install -r option work with git based plugins. #257. [Tim Pope Jakub Kuźma]. Example:
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index 3be95de8d3..828d688475 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -333,11 +333,14 @@ Run `rake gems:install` to install the missing gems.
end
end
+ # Eager load application classes
def load_application_classes
- require_dependency 'application'
-
- Dir.glob('app/{models,controllers,helpers}/*.rb').each do |file|
- require_dependency file
+ if configuration.cache_classes
+ configuration.eager_load_paths.each do |load_path|
+ Dir.glob("#{load_path}/*.rb").each do |file|
+ require_dependency file
+ end
+ end
end
end
@@ -578,6 +581,11 @@ Run `rake gems:install` to install the missing gems.
# All elements of this array must also be in +load_paths+.
attr_accessor :load_once_paths
+ # An array of paths from which Rails will eager load on boot if cache
+ # classes is enabled. All elements of this array must also be in
+ # +load_paths+.
+ attr_accessor :eager_load_paths
+
# The log level to use for the default Rails logger. In production mode,
# this defaults to :info . In development mode, it defaults to
# :debug .
@@ -686,6 +694,7 @@ Run `rake gems:install` to install the missing gems.
self.frameworks = default_frameworks
self.load_paths = default_load_paths
self.load_once_paths = default_load_once_paths
+ self.eager_load_paths = default_eager_load_paths
self.log_path = default_log_path
self.log_level = default_log_level
self.view_path = default_view_path
@@ -826,6 +835,14 @@ Run `rake gems:install` to install the missing gems.
[]
end
+ def default_eager_load_paths
+ %w(
+ app/models
+ app/controllers
+ app/helpers
+ ).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) }
+ end
+
def default_log_path
File.join(root_path, 'log', "#{environment}.log")
end
--
cgit v1.2.3
From c67713a2fe78d6f2db49b09771841f5022995703 Mon Sep 17 00:00:00 2001
From: Daniel Guettler
Date: Mon, 21 Jul 2008 15:21:13 -0400
Subject: Use klass.sti_name to make sure associations take
store_full_sti_class into account. [#671 state:resolved]
Signed-off-by: Pratik Naik
---
activerecord/lib/active_record/associations.rb | 4 ++--
.../cases/associations/has_many_associations_test.rb | 18 ++++++++++++++++++
activerecord/test/models/company.rb | 7 +++++++
3 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index fd9a443eb9..d916275ab9 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1884,7 +1884,7 @@ module ActiveRecord
jt_sti_extra = " AND %s.%s = %s" % [
connection.quote_table_name(aliased_join_table_name),
connection.quote_column_name(through_reflection.active_record.inheritance_column),
- through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
+ through_reflection.klass.quote_value(through_reflection.klass.sti_name)]
end
when :belongs_to
first_key = primary_key
@@ -1952,7 +1952,7 @@ module ActiveRecord
join << %(AND %s.%s = %s ) % [
connection.quote_table_name(aliased_table_name),
connection.quote_column_name(klass.inheritance_column),
- klass.quote_value(klass.name.demodulize)] unless klass.descends_from_active_record?
+ klass.quote_value(klass.sti_name)] unless klass.descends_from_active_record?
[through_reflection, reflection].each do |ref|
join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions]
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index b9c7ec6377..f8b8b1f96d 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -999,4 +999,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert firm.clients.loaded?
end
+ def test_joins_with_namespaced_model_should_use_correct_type
+ old = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = true
+
+ firm = Namespaced::Firm.create({ :name => 'Some Company' })
+ firm.clients.create({ :name => 'Some Client' })
+
+ stats = Namespaced::Firm.find(firm.id, {
+ :select => "#{Namespaced::Firm.table_name}.*, COUNT(#{Namespaced::Client.table_name}.id) AS num_clients",
+ :joins => :clients,
+ :group => "#{Namespaced::Firm.table_name}.id"
+ })
+ assert_equal 1, stats.num_clients.to_i
+
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old
+ end
+
end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index e6aa810146..cd435948a1 100755
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -18,6 +18,13 @@ end
module Namespaced
class Company < ::Company
end
+
+ class Firm < ::Company
+ has_many :clients, :class_name => 'Namespaced::Client'
+ end
+
+ class Client < ::Company
+ end
end
class Firm < Company
--
cgit v1.2.3
From 92f944818eece9fe4bc62ffb39accdb71ebc32be Mon Sep 17 00:00:00 2001
From: Miles Georgi
Date: Sat, 19 Jul 2008 16:04:35 -0700
Subject: Make script/plugin work with svn+ssh urls. [#662 state:resolved]
Signed-off-by: Pratik Naik
---
railties/lib/commands/plugin.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/railties/lib/commands/plugin.rb b/railties/lib/commands/plugin.rb
index 0256090d16..980244a71b 100644
--- a/railties/lib/commands/plugin.rb
+++ b/railties/lib/commands/plugin.rb
@@ -907,7 +907,7 @@ class RecursiveHTTPFetcher
def ls
@urls_to_fetch.collect do |url|
- if url =~ /^svn:\/\/.*/
+ if url =~ /^svn(\+ssh)?:\/\/.*/
`svn ls #{url}`.split("\n").map {|entry| "/#{entry}"} rescue nil
else
open(url) do |stream|
--
cgit v1.2.3
From 8b858782fa693e89a47fc3dd5ae38d842ede6d04 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Mon, 21 Jul 2008 22:41:38 -0500
Subject: Ensure adapater specific code is loaded on
ActiveRecord::Base.establish_connection
---
.../connection_adapters/abstract/connection_specification.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index 2a8807fb78..07b122efd1 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -211,6 +211,7 @@ module ActiveRecord
clear_active_connection_name
@active_connection_name = name
@@defined_connections[name] = spec
+ connection
when Symbol, String
if configuration = configurations[spec.to_s]
establish_connection(configuration)
--
cgit v1.2.3
From 8a87d8a6c2c6dfb423bcaf61c750010d80993b16 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 22 Jul 2008 10:26:44 -0500
Subject: Improved Memoizable test coverage and added support for multiple
arguments
---
.../active_support/core_ext/object/metaclass.rb | 5 +
activesupport/lib/active_support/memoizable.rb | 55 ++++---
activesupport/test/memoizable_test.rb | 166 ++++++++++++++++-----
3 files changed, 165 insertions(+), 61 deletions(-)
diff --git a/activesupport/lib/active_support/core_ext/object/metaclass.rb b/activesupport/lib/active_support/core_ext/object/metaclass.rb
index 169a76dfb7..93fb0ad594 100644
--- a/activesupport/lib/active_support/core_ext/object/metaclass.rb
+++ b/activesupport/lib/active_support/core_ext/object/metaclass.rb
@@ -5,4 +5,9 @@ class Object
self
end
end
+
+ # If class_eval is called on an object, add those methods to its metaclass
+ def class_eval(*args, &block)
+ metaclass.class_eval(*args, &block)
+ end
end
diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb
index f7cd73d39c..21636b8af4 100644
--- a/activesupport/lib/active_support/memoizable.rb
+++ b/activesupport/lib/active_support/memoizable.rb
@@ -1,32 +1,43 @@
module ActiveSupport
- module Memoizable #:nodoc:
+ module Memoizable
+ module Freezable
+ def self.included(base)
+ base.class_eval do
+ unless base.method_defined?(:freeze_without_memoizable)
+ alias_method_chain :freeze, :memoizable
+ end
+ end
+ end
+
+ def freeze_with_memoizable
+ methods.each do |method|
+ if m = method.to_s.match(/^_unmemoized_(.*)/)
+ send(m[1])
+ end
+ end
+ freeze_without_memoizable
+ end
+ end
+
def memoize(*symbols)
symbols.each do |symbol|
- original_method = "unmemoized_#{symbol}"
- memoized_ivar = "@#{symbol}"
+ original_method = "_unmemoized_#{symbol}"
+ memoized_ivar = "@_memoized_#{symbol}"
- klass = respond_to?(:class_eval) ? self : self.metaclass
- raise "Already memoized #{symbol}" if klass.instance_methods.map(&:to_s).include?(original_method)
+ class_eval <<-EOS, __FILE__, __LINE__
+ include Freezable
- klass.class_eval <<-EOS, __FILE__, __LINE__
- unless instance_methods.map(&:to_s).include?("freeze_without_memoizable")
- alias_method :freeze_without_memoizable, :freeze
- def freeze
- methods.each do |method|
- if m = method.to_s.match(/^unmemoized_(.*)/)
- send(m[1])
- end
- end
- freeze_without_memoizable
- end
- end
+ raise "Already memoized #{symbol}" if method_defined?(:#{original_method})
+ alias #{original_method} #{symbol}
+
+ def #{symbol}(*args)
+ #{memoized_ivar} ||= {}
+ reload = args.pop if args.last == true || args.last == :reload
- alias_method :#{original_method}, :#{symbol}
- def #{symbol}(reload = false)
- if !reload && defined? #{memoized_ivar}
- #{memoized_ivar}
+ if !reload && #{memoized_ivar} && #{memoized_ivar}.has_key?(args)
+ #{memoized_ivar}[args]
else
- #{memoized_ivar} = #{original_method}.freeze
+ #{memoized_ivar}[args] = #{original_method}(*args).freeze
end
end
EOS
diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb
index 79769631ad..cd84dcda53 100644
--- a/activesupport/test/memoizable_test.rb
+++ b/activesupport/test/memoizable_test.rb
@@ -5,86 +5,174 @@ uses_mocha 'Memoizable' do
class Person
extend ActiveSupport::Memoizable
- def name
- fetch_name_from_floppy
+ attr_reader :name_calls, :age_calls
+ def initialize
+ @name_calls = 0
+ @age_calls = 0
end
- memoize :name
+ def name
+ @name_calls += 1
+ "Josh"
+ end
def age
+ @age_calls += 1
nil
end
- def counter
- @counter ||= 0
- @counter += 1
+ memoize :name, :age
+ end
+
+ class Company
+ attr_reader :name_calls
+ def initialize
+ @name_calls = 0
end
- memoize :age, :counter
+ def name
+ @name_calls += 1
+ "37signals"
+ end
+ end
+
+ module Rates
+ extend ActiveSupport::Memoizable
- private
- def fetch_name_from_floppy
- "Josh"
+ attr_reader :sales_tax_calls
+ def sales_tax(price)
+ @sales_tax_calls ||= 0
+ @sales_tax_calls += 1
+ price * 0.1025
+ end
+ memoize :sales_tax
+ end
+
+ class Calculator
+ extend ActiveSupport::Memoizable
+ include Rates
+
+ attr_reader :fib_calls
+ def initialize
+ @fib_calls = 0
+ end
+
+ def fib(n)
+ @fib_calls += 1
+
+ if n == 0 || n == 1
+ n
+ else
+ fib(n - 1) + fib(n - 2)
end
+ end
+ memoize :fib
+
+ def counter
+ @count ||= 0
+ @count += 1
+ end
+ memoize :counter
end
def setup
@person = Person.new
+ @calculator = Calculator.new
end
def test_memoization
assert_equal "Josh", @person.name
+ assert_equal 1, @person.name_calls
- @person.expects(:fetch_name_from_floppy).never
- 2.times { assert_equal "Josh", @person.name }
+ 3.times { assert_equal "Josh", @person.name }
+ assert_equal 1, @person.name_calls
+ end
+
+ def test_memoization_with_nil_value
+ assert_equal nil, @person.age
+ assert_equal 1, @person.age_calls
+
+ 3.times { assert_equal nil, @person.age }
+ assert_equal 1, @person.age_calls
end
def test_reloadable
- counter = @person.counter
- assert_equal 1, @person.counter
- assert_equal 2, @person.counter(:reload)
+ counter = @calculator.counter
+ assert_equal 1, @calculator.counter
+ assert_equal 2, @calculator.counter(:reload)
+ assert_equal 2, @calculator.counter
+ assert_equal 3, @calculator.counter(true)
+ assert_equal 3, @calculator.counter
end
- def test_memoized_methods_are_frozen
- assert_equal true, @person.name.frozen?
+ def test_memoization_cache_is_different_for_each_instance
+ assert_equal 1, @calculator.counter
+ assert_equal 2, @calculator.counter(:reload)
+ assert_equal 1, Calculator.new.counter
+ end
+ def test_memoized_is_not_affected_by_freeze
@person.freeze
assert_equal "Josh", @person.name
- assert_equal true, @person.name.frozen?
end
- def test_memoization_frozen_with_nil_value
- @person.freeze
- assert_equal nil, @person.age
+ def test_memoization_with_args
+ assert_equal 55, @calculator.fib(10)
+ assert_equal 11, @calculator.fib_calls
end
- def test_double_memoization
- assert_raise(RuntimeError) { Person.memoize :name }
+ def test_reloadable_with_args
+ assert_equal 55, @calculator.fib(10)
+ assert_equal 11, @calculator.fib_calls
+ assert_equal 55, @calculator.fib(10, :reload)
+ assert_equal 12, @calculator.fib_calls
+ assert_equal 55, @calculator.fib(10, true)
+ assert_equal 13, @calculator.fib_calls
end
- class Company
- def name
- lookup_name
+ def test_object_memoization
+ [Company.new, Company.new, Company.new].each do |company|
+ company.extend ActiveSupport::Memoizable
+ company.memoize :name
+
+ assert_equal "37signals", company.name
+ assert_equal 1, company.name_calls
+ assert_equal "37signals", company.name
+ assert_equal 1, company.name_calls
end
+ end
- def lookup_name
- "37signals"
- end
+ def test_memoized_module_methods
+ assert_equal 1.025, @calculator.sales_tax(10)
+ assert_equal 1, @calculator.sales_tax_calls
+ assert_equal 1.025, @calculator.sales_tax(10)
+ assert_equal 1, @calculator.sales_tax_calls
+ assert_equal 2.5625, @calculator.sales_tax(25)
+ assert_equal 2, @calculator.sales_tax_calls
end
- def test_object_memoization
+ def test_object_memoized_module_methods
company = Company.new
- company.extend ActiveSupport::Memoizable
- company.memoize :name
+ company.extend(Rates)
+
+ assert_equal 1.025, company.sales_tax(10)
+ assert_equal 1, company.sales_tax_calls
+ assert_equal 1.025, company.sales_tax(10)
+ assert_equal 1, company.sales_tax_calls
+ assert_equal 2.5625, company.sales_tax(25)
+ assert_equal 2, company.sales_tax_calls
+ end
- assert_equal "37signals", company.name
- # Mocha doesn't play well with frozen objects
- company.metaclass.instance_eval { define_method(:lookup_name) { b00m } }
- assert_equal "37signals", company.name
+ def test_double_memoization
+ assert_raise(RuntimeError) { Person.memoize :name }
+ person = Person.new
+ person.extend ActiveSupport::Memoizable
+ assert_raise(RuntimeError) { person.memoize :name }
- assert_equal true, company.name.frozen?
- company.freeze
- assert_equal true, company.name.frozen?
+ company = Company.new
+ company.extend ActiveSupport::Memoizable
+ company.memoize :name
+ assert_raise(RuntimeError) { company.memoize :name }
end
end
end
--
cgit v1.2.3
From bc5896e708bf8070835bebe61de03b701fa5e6f7 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 22 Jul 2008 10:27:32 -0500
Subject: Memoize ActionView::Base pick_template and find_partial_path for
rendering duration
---
actionpack/lib/action_controller/base.rb | 2 ++
actionpack/lib/action_view/base.rb | 7 +++++--
actionpack/lib/action_view/partials.rb | 9 +++++----
actionpack/lib/action_view/template.rb | 2 +-
.../test/template/compiled_templates_test.rb | 23 ++++++++++++----------
5 files changed, 26 insertions(+), 17 deletions(-)
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 50727c67c4..4dabff637b 100755
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -1261,6 +1261,8 @@ module ActionController #:nodoc:
def template_exempt_from_layout?(template_name = default_template_name)
template_name = @template.pick_template(template_name).to_s if @template
@@exempt_from_layout.any? { |ext| template_name =~ ext }
+ rescue ActionView::MissingTemplate
+ false
end
def default_template_name(action_name = self.action_name)
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 619a4270f8..bdcb1dc246 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -323,8 +323,8 @@ module ActionView #:nodoc:
if self.class.warn_cache_misses && logger = ActionController::Base.logger
logger.debug "[PERFORMANCE] Rendering a template that was " +
"not found in view path. Templates outside the view path are " +
- "not cached and result in expensive disk operations. Move this " +
- "file into #{view_paths.join(':')} or add the folder to your " +
+ "not cached and result in expensive disk operations. Move this " +
+ "file into #{view_paths.join(':')} or add the folder to your " +
"view path list"
end
@@ -332,6 +332,9 @@ module ActionView #:nodoc:
end
end
+ extend ActiveSupport::Memoizable
+ memoize :pick_template
+
private
# Renders the template present at template_path . The hash in local_assigns
# is made available as local variables.
diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb
index 5aa4c83009..eb74d4a4c7 100644
--- a/actionpack/lib/action_view/partials.rb
+++ b/actionpack/lib/action_view/partials.rb
@@ -102,6 +102,8 @@ module ActionView
#
# As you can see, the :locals hash is shared between both the partial and its layout.
module Partials
+ extend ActiveSupport::Memoizable
+
private
def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc:
local_assigns ||= {}
@@ -129,14 +131,12 @@ module ActionView
local_assigns = local_assigns ? local_assigns.clone : {}
spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : ''
- _paths = {}
- _templates = {}
index = 0
collection.map do |object|
_partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path)
- path = _paths[_partial_path] ||= find_partial_path(_partial_path)
- template = _templates[path] ||= pick_template(path)
+ path = find_partial_path(_partial_path)
+ template = pick_template(path)
local_assigns[template.counter_name] = index
result = template.render_partial(self, object, local_assigns, as)
index += 1
@@ -153,5 +153,6 @@ module ActionView
"_#{partial_path}"
end
end
+ memoize :find_partial_path
end
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 1f528dd900..3fcd9a2d01 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -75,7 +75,7 @@ module ActionView #:nodoc:
load_paths = Array(load_paths) + [nil]
load_paths.each do |load_path|
file = [load_path, path].compact.join('/')
- return load_path, file if File.exist?(file)
+ return load_path, file if File.exist?(file) && File.file?(file)
end
raise MissingTemplate.new(load_paths, path)
end
diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb
index 4b34827f91..52996c7fcb 100644
--- a/actionpack/test/template/compiled_templates_test.rb
+++ b/actionpack/test/template/compiled_templates_test.rb
@@ -4,7 +4,6 @@ require 'controller/fake_models'
uses_mocha 'TestTemplateRecompilation' do
class CompiledTemplatesTest < Test::Unit::TestCase
def setup
- @view = ActionView::Base.new(ActionController::Base.view_paths, {})
@compiled_templates = ActionView::Base::CompiledTemplates
@compiled_templates.instance_methods.each do |m|
@compiled_templates.send(:remove_method, m) if m =~ /^_run_/
@@ -13,29 +12,33 @@ uses_mocha 'TestTemplateRecompilation' do
def test_template_gets_compiled
assert_equal 0, @compiled_templates.instance_methods.size
- assert_equal "Hello world!", @view.render("test/hello_world.erb")
+ assert_equal "Hello world!", render("test/hello_world.erb")
assert_equal 1, @compiled_templates.instance_methods.size
end
def test_template_gets_recompiled_when_using_different_keys_in_local_assigns
assert_equal 0, @compiled_templates.instance_methods.size
- assert_equal "Hello world!", @view.render("test/hello_world.erb")
- assert_equal "Hello world!", @view.render("test/hello_world.erb", {:foo => "bar"})
+ assert_equal "Hello world!", render("test/hello_world.erb")
+ assert_equal "Hello world!", render("test/hello_world.erb", {:foo => "bar"})
assert_equal 2, @compiled_templates.instance_methods.size
end
def test_compiled_template_will_not_be_recompiled_when_rendered_with_identical_local_assigns
assert_equal 0, @compiled_templates.instance_methods.size
- assert_equal "Hello world!", @view.render("test/hello_world.erb")
+ assert_equal "Hello world!", render("test/hello_world.erb")
ActionView::Template.any_instance.expects(:compile!).never
- assert_equal "Hello world!", @view.render("test/hello_world.erb")
+ assert_equal "Hello world!", render("test/hello_world.erb")
end
- def test_compiled_template_will_always_be_recompiled_when_rendered_if_template_is_outside_cache
+ def test_compiled_template_will_be_recompiled_when_rendered_if_template_is_outside_cache
assert_equal 0, @compiled_templates.instance_methods.size
- assert_equal "Hello world!", @view.render("#{FIXTURE_LOAD_PATH}/test/hello_world.erb")
- ActionView::Template.any_instance.expects(:compile!).times(3)
- 3.times { assert_equal "Hello world!", @view.render("#{FIXTURE_LOAD_PATH}/test/hello_world.erb") }
+ assert_equal "Hello world!", render("#{FIXTURE_LOAD_PATH}/test/hello_world.erb")
+ assert_equal 1, @compiled_templates.instance_methods.size
end
+
+ private
+ def render(*args)
+ ActionView::Base.new(ActionController::Base.view_paths, {}).render(*args)
+ end
end
end
--
cgit v1.2.3
From 2681685450631238511cfc3c2f0fa044c1f8033a Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Tue, 22 Jul 2008 11:12:16 -0500
Subject: Extract ActiveSupport::TypedArray class to ensure an array is all of
the same type [#673 state:resolved]
---
actionpack/lib/action_view/paths.rb | 31 +++------------
actionpack/test/controller/view_paths_test.rb | 10 +----
activesupport/lib/active_support.rb | 1 +
activesupport/lib/active_support/typed_array.rb | 31 +++++++++++++++
activesupport/test/typed_array_test.rb | 51 +++++++++++++++++++++++++
5 files changed, 91 insertions(+), 33 deletions(-)
create mode 100644 activesupport/lib/active_support/typed_array.rb
create mode 100644 activesupport/test/typed_array_test.rb
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index 78548d4aa2..9cb50ab4f8 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -1,5 +1,5 @@
module ActionView #:nodoc:
- class PathSet < Array #:nodoc:
+ class PathSet < ActiveSupport::TypedArray #:nodoc:
def self.type_cast(obj)
if obj.is_a?(String)
if Base.warn_cache_misses && defined?(Rails) && Rails.initialized?
@@ -25,7 +25,7 @@ module ActionView #:nodoc:
end
attr_reader :path, :paths
- delegate :to_s, :to_str, :inspect, :to => :path
+ delegate :to_s, :to_str, :hash, :inspect, :to => :path
def initialize(path)
raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
@@ -38,6 +38,10 @@ module ActionView #:nodoc:
to_str == path.to_str
end
+ def eql?(path)
+ to_str == path.to_str
+ end
+
def [](path)
@paths[path]
end
@@ -67,28 +71,10 @@ module ActionView #:nodoc:
end
end
- def initialize(*args)
- super(*args).map! { |obj| self.class.type_cast(obj) }
- end
-
def reload!
each { |path| path.reload! }
end
- def <<(obj)
- super(self.class.type_cast(obj))
- end
-
- def push(*objs)
- delete_paths!(objs)
- super(*objs.map { |obj| self.class.type_cast(obj) })
- end
-
- def unshift(*objs)
- delete_paths!(objs)
- super(*objs.map { |obj| self.class.type_cast(obj) })
- end
-
def [](template_path)
each do |path|
if template = path[template_path]
@@ -97,10 +83,5 @@ module ActionView #:nodoc:
end
nil
end
-
- private
- def delete_paths!(paths)
- paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } }
- end
end
end
diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb
index 85fa58a45b..b859a92cbd 100644
--- a/actionpack/test/controller/view_paths_test.rb
+++ b/actionpack/test/controller/view_paths_test.rb
@@ -54,10 +54,7 @@ class ViewLoadPathsTest < Test::Unit::TestCase
assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths
@controller.append_view_path(FIXTURE_LOAD_PATH)
- assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
-
- @controller.append_view_path([FIXTURE_LOAD_PATH])
- assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
+ assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
end
def test_controller_prepends_view_path_correctly
@@ -68,10 +65,7 @@ class ViewLoadPathsTest < Test::Unit::TestCase
assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
@controller.prepend_view_path(FIXTURE_LOAD_PATH)
- assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths
-
- @controller.prepend_view_path([FIXTURE_LOAD_PATH])
- assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths
+ assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
end
def test_template_appends_view_path_correctly
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 1df911a3f2..51067e910e 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -39,6 +39,7 @@ require 'active_support/cache'
require 'active_support/dependencies'
require 'active_support/deprecation'
+require 'active_support/typed_array'
require 'active_support/ordered_hash'
require 'active_support/ordered_options'
require 'active_support/option_merger'
diff --git a/activesupport/lib/active_support/typed_array.rb b/activesupport/lib/active_support/typed_array.rb
new file mode 100644
index 0000000000..1a4d8a8faf
--- /dev/null
+++ b/activesupport/lib/active_support/typed_array.rb
@@ -0,0 +1,31 @@
+module ActiveSupport
+ class TypedArray < Array
+ def self.type_cast(obj)
+ obj
+ end
+
+ def initialize(*args)
+ super(*args).map! { |obj| self.class.type_cast(obj) }
+ end
+
+ def <<(obj)
+ super(self.class.type_cast(obj))
+ end
+
+ def concat(array)
+ super(array.map! { |obj| self.class.type_cast(obj) })
+ end
+
+ def insert(index, obj)
+ super(index, self.class.type_cast(obj))
+ end
+
+ def push(*objs)
+ super(*objs.map { |obj| self.class.type_cast(obj) })
+ end
+
+ def unshift(*objs)
+ super(*objs.map { |obj| self.class.type_cast(obj) })
+ end
+ end
+end
diff --git a/activesupport/test/typed_array_test.rb b/activesupport/test/typed_array_test.rb
new file mode 100644
index 0000000000..023f3a1b84
--- /dev/null
+++ b/activesupport/test/typed_array_test.rb
@@ -0,0 +1,51 @@
+require 'abstract_unit'
+
+class TypedArrayTest < Test::Unit::TestCase
+ class StringArray < ActiveSupport::TypedArray
+ def self.type_cast(obj)
+ obj.to_s
+ end
+ end
+
+ def setup
+ @array = StringArray.new
+ end
+
+ def test_string_array_initialize
+ assert_equal ["1", "2", "3"], StringArray.new([1, "2", :"3"])
+ end
+
+ def test_string_array_append
+ @array << 1
+ @array << "2"
+ @array << :"3"
+ assert_equal ["1", "2", "3"], @array
+ end
+
+ def test_string_array_concat
+ @array.concat([1, "2"])
+ @array.concat([:"3"])
+ assert_equal ["1", "2", "3"], @array
+ end
+
+ def test_string_array_insert
+ @array.insert(0, 1)
+ @array.insert(1, "2")
+ @array.insert(2, :"3")
+ assert_equal ["1", "2", "3"], @array
+ end
+
+ def test_string_array_push
+ @array.push(1)
+ @array.push("2")
+ @array.push(:"3")
+ assert_equal ["1", "2", "3"], @array
+ end
+
+ def test_string_array_unshift
+ @array.unshift(:"3")
+ @array.unshift("2")
+ @array.unshift(1)
+ assert_equal ["1", "2", "3"], @array
+ end
+end
--
cgit v1.2.3
From 93e10f9911fb2a096681ee0a0bc82487a9a06c44 Mon Sep 17 00:00:00 2001
From: Jan De Poorter
Date: Wed, 23 Jul 2008 12:50:16 +0200
Subject: Ensure NamedScope#any? uses COUNT query wherever possible. [#680
state:resolved]
Signed-off-by: Pratik Naik
---
activerecord/lib/active_record/named_scope.rb | 10 +++++++++-
activerecord/test/cases/named_scope_test.rb | 22 ++++++++++++++++++++++
2 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index 080e3d0f5e..d5a1c5fe08 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -103,7 +103,7 @@ module ActiveRecord
attr_reader :proxy_scope, :proxy_options
[].methods.each do |m|
- unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?)/
+ unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?|any?)/
delegate m, :to => :proxy_found
end
end
@@ -140,6 +140,14 @@ module ActiveRecord
@found ? @found.empty? : count.zero?
end
+ def any?
+ if block_given?
+ proxy_found.any? { |*block_args| yield(*block_args) }
+ else
+ !empty?
+ end
+ end
+
protected
def proxy_found
@found || load_found
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 0c1eb23428..e21ffbbdba 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -184,6 +184,28 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
+ def test_any_should_not_load_results
+ topics = Topic.base
+ assert_queries(1) do
+ topics.expects(:empty?).returns(true)
+ assert !topics.any?
+ end
+ end
+
+ def test_any_should_call_proxy_found_if_using_a_block
+ topics = Topic.base
+ assert_queries(1) do
+ topics.expects(:empty?).never
+ topics.any? { true }
+ end
+ end
+
+ def test_any_should_not_fire_query_if_named_scope_loaded
+ topics = Topic.base
+ topics.collect # force load
+ assert_no_queries { assert topics.any? }
+ end
+
def test_should_build_with_proxy_options
topic = Topic.approved.build({})
assert topic.approved
--
cgit v1.2.3
From db1bac796e2d53fac4b51a3f560010b8f663fb54 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Wed, 23 Jul 2008 10:24:47 -0500
Subject: Just file?
---
actionpack/lib/action_view/template.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 3fcd9a2d01..b281ff61f2 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -75,7 +75,7 @@ module ActionView #:nodoc:
load_paths = Array(load_paths) + [nil]
load_paths.each do |load_path|
file = [load_path, path].compact.join('/')
- return load_path, file if File.exist?(file) && File.file?(file)
+ return load_path, file if File.file?(file)
end
raise MissingTemplate.new(load_paths, path)
end
--
cgit v1.2.3
From 97a954bf1dd05e79a873bffc94fcf5420b807371 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Wed, 23 Jul 2008 10:41:28 -0500
Subject: Load view path cache after plugins and gems.
---
actionpack/lib/action_view/paths.rb | 18 +++++++++++++++---
railties/lib/initializer.rb | 13 ++++++++++---
2 files changed, 25 insertions(+), 6 deletions(-)
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index 9cb50ab4f8..a37706faee 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -27,11 +27,10 @@ module ActionView #:nodoc:
attr_reader :path, :paths
delegate :to_s, :to_str, :hash, :inspect, :to => :path
- def initialize(path)
+ def initialize(path, load = true)
raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
-
@path = path.freeze
- reload!
+ reload! if load
end
def ==(path)
@@ -46,6 +45,14 @@ module ActionView #:nodoc:
@paths[path]
end
+ def loaded?
+ @loaded ? true : false
+ end
+
+ def load
+ reload! unless loaded?
+ end
+
# Rebuild load path directory cache
def reload!
@paths = {}
@@ -59,6 +66,7 @@ module ActionView #:nodoc:
end
@paths.freeze
+ @loaded = true
end
private
@@ -71,6 +79,10 @@ module ActionView #:nodoc:
end
end
+ def load
+ each { |path| path.load }
+ end
+
def reload!
each { |path| path.reload! }
end
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index 828d688475..97bb81a3c8 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -168,6 +168,9 @@ module Rails
# Observers are loaded after plugins in case Observers or observed models are modified by plugins.
load_observers
+ # Load view path cache
+ load_view_paths
+
# load application classes
load_application_classes
@@ -333,6 +336,12 @@ Run `rake gems:install` to install the missing gems.
end
end
+ def load_view_paths
+ ActionView::PathSet::Path.eager_load_templates! if configuration.cache_classes
+ ActionMailer::Base.template_root.load
+ ActionController::Base.view_paths.load
+ end
+
# Eager load application classes
def load_application_classes
if configuration.cache_classes
@@ -428,9 +437,7 @@ Run `rake gems:install` to install the missing gems.
# paths have already been set, it is not changed, otherwise it is
# set to use Configuration#view_path.
def initialize_framework_views
- ActionView::PathSet::Path.eager_load_templates! if configuration.cache_classes
- view_path = ActionView::PathSet::Path.new(configuration.view_path)
-
+ view_path = ActionView::PathSet::Path.new(configuration.view_path, false)
ActionMailer::Base.template_root ||= view_path if configuration.frameworks.include?(:action_mailer)
ActionController::Base.view_paths = view_path if configuration.frameworks.include?(:action_controller) && ActionController::Base.view_paths.empty?
end
--
cgit v1.2.3
From e0db925be04ab3e9c3db67dd0daa8caf3680dd21 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Wed, 23 Jul 2008 11:23:25 -0500
Subject: Revert 'bc5896e'
---
actionpack/lib/action_view/base.rb | 3 ---
actionpack/lib/action_view/partials.rb | 9 ++++-----
2 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index bdcb1dc246..c769013d42 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -332,9 +332,6 @@ module ActionView #:nodoc:
end
end
- extend ActiveSupport::Memoizable
- memoize :pick_template
-
private
# Renders the template present at template_path . The hash in local_assigns
# is made available as local variables.
diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb
index eb74d4a4c7..5aa4c83009 100644
--- a/actionpack/lib/action_view/partials.rb
+++ b/actionpack/lib/action_view/partials.rb
@@ -102,8 +102,6 @@ module ActionView
#
# As you can see, the :locals hash is shared between both the partial and its layout.
module Partials
- extend ActiveSupport::Memoizable
-
private
def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc:
local_assigns ||= {}
@@ -131,12 +129,14 @@ module ActionView
local_assigns = local_assigns ? local_assigns.clone : {}
spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : ''
+ _paths = {}
+ _templates = {}
index = 0
collection.map do |object|
_partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path)
- path = find_partial_path(_partial_path)
- template = pick_template(path)
+ path = _paths[_partial_path] ||= find_partial_path(_partial_path)
+ template = _templates[path] ||= pick_template(path)
local_assigns[template.counter_name] = index
result = template.render_partial(self, object, local_assigns, as)
index += 1
@@ -153,6 +153,5 @@ module ActionView
"_#{partial_path}"
end
end
- memoize :find_partial_path
end
end
--
cgit v1.2.3
From 55adaa2efc08c892bf7be55d79ac571848068256 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Wed, 23 Jul 2008 13:47:30 -0500
Subject: Fixed bc5896e, and added test case for the caching bug it originally
introduced.
---
actionpack/lib/action_view/base.rb | 3 +++
actionpack/lib/action_view/partials.rb | 9 +++++----
actionpack/lib/action_view/renderable.rb | 2 +-
actionpack/test/template/compiled_templates_test.rb | 5 ++++-
4 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index c769013d42..bdcb1dc246 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -332,6 +332,9 @@ module ActionView #:nodoc:
end
end
+ extend ActiveSupport::Memoizable
+ memoize :pick_template
+
private
# Renders the template present at template_path . The hash in local_assigns
# is made available as local variables.
diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb
index 5aa4c83009..eb74d4a4c7 100644
--- a/actionpack/lib/action_view/partials.rb
+++ b/actionpack/lib/action_view/partials.rb
@@ -102,6 +102,8 @@ module ActionView
#
# As you can see, the :locals hash is shared between both the partial and its layout.
module Partials
+ extend ActiveSupport::Memoizable
+
private
def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc:
local_assigns ||= {}
@@ -129,14 +131,12 @@ module ActionView
local_assigns = local_assigns ? local_assigns.clone : {}
spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : ''
- _paths = {}
- _templates = {}
index = 0
collection.map do |object|
_partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path)
- path = _paths[_partial_path] ||= find_partial_path(_partial_path)
- template = _templates[path] ||= pick_template(path)
+ path = find_partial_path(_partial_path)
+ template = pick_template(path)
local_assigns[template.counter_name] = index
result = template.render_partial(self, object, local_assigns, as)
index += 1
@@ -153,5 +153,6 @@ module ActionView
"_#{partial_path}"
end
end
+ memoize :find_partial_path
end
end
diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb
index 2b825ac4e9..5fe1ca86f3 100644
--- a/actionpack/lib/action_view/renderable.rb
+++ b/actionpack/lib/action_view/renderable.rb
@@ -84,7 +84,7 @@ module ActionView
# The template will be compiled if the file has not been compiled yet, or
# if local_assigns has a new key, which isn't supported by the compiled code yet.
def recompile?(symbol)
- !(frozen? && Base::CompiledTemplates.method_defined?(symbol))
+ !(ActionView::PathSet::Path.eager_load_templates? && Base::CompiledTemplates.method_defined?(symbol))
end
end
end
diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb
index 52996c7fcb..e005aa0f03 100644
--- a/actionpack/test/template/compiled_templates_test.rb
+++ b/actionpack/test/template/compiled_templates_test.rb
@@ -30,9 +30,12 @@ uses_mocha 'TestTemplateRecompilation' do
assert_equal "Hello world!", render("test/hello_world.erb")
end
- def test_compiled_template_will_be_recompiled_when_rendered_if_template_is_outside_cache
+ def test_compiled_template_will_always_be_recompiled_when_eager_loaded_templates_is_off
+ ActionView::PathSet::Path.expects(:eager_load_templates?).times(4).returns(false)
assert_equal 0, @compiled_templates.instance_methods.size
assert_equal "Hello world!", render("#{FIXTURE_LOAD_PATH}/test/hello_world.erb")
+ ActionView::Template.any_instance.expects(:compile!).times(3)
+ 3.times { assert_equal "Hello world!", render("#{FIXTURE_LOAD_PATH}/test/hello_world.erb") }
assert_equal 1, @compiled_templates.instance_methods.size
end
--
cgit v1.2.3
From 3fd9036fc554979e951422a79f0f77f061112bdc Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Thu, 24 Jul 2008 11:58:26 -0500
Subject: Added config.dependency_loading to enable or disable the dependency
loader after initialization
---
activesupport/lib/active_support/dependencies.rb | 78 ++++++++++++++++++------
activesupport/test/dependencies_test.rb | 12 ++++
railties/lib/initializer.rb | 27 +++++++-
3 files changed, 95 insertions(+), 22 deletions(-)
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index e3d4f3d7eb..a3f5f799a2 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -51,17 +51,28 @@ module ActiveSupport #:nodoc:
module ModuleConstMissing #:nodoc:
def self.included(base) #:nodoc:
base.class_eval do
- # Rename the original handler so we can chain it to the new one
- alias_method :rails_original_const_missing, :const_missing
+ unless defined? const_missing_without_dependencies
+ alias_method_chain :const_missing, :dependencies
+ end
+ end
+ end
- # Use const_missing to autoload associations so we don't have to
- # require_association when using single-table inheritance.
- def const_missing(class_id)
- ActiveSupport::Dependencies.load_missing_constant self, class_id
+ def self.excluded(base) #:nodoc:
+ base.class_eval do
+ if defined? const_missing_without_dependencies
+ undef_method :const_missing
+ alias_method :const_missing, :const_missing_without_dependencies
+ undef_method :const_missing_without_dependencies
end
end
end
+ # Use const_missing to autoload associations so we don't have to
+ # require_association when using single-table inheritance.
+ def const_missing_with_dependencies(class_id)
+ ActiveSupport::Dependencies.load_missing_constant self, class_id
+ end
+
def unloadable(const_desc = self)
super(const_desc)
end
@@ -92,8 +103,38 @@ module ActiveSupport #:nodoc:
# Object includes this module
module Loadable #:nodoc:
- def load(file, *extras) #:nodoc:
- Dependencies.new_constants_in(Object) { super }
+ def self.included(base) #:nodoc:
+ base.class_eval do
+ unless defined? load_without_new_constant_marking
+ alias_method_chain :load, :new_constant_marking
+ end
+ end
+ end
+
+ def self.excluded(base) #:nodoc:
+ base.class_eval do
+ if defined? load_without_new_constant_marking
+ undef_method :load
+ alias_method :load, :load_without_new_constant_marking
+ undef_method :load_without_new_constant_marking
+ end
+ end
+ end
+
+ def require_or_load(file_name)
+ Dependencies.require_or_load(file_name)
+ end
+
+ def require_dependency(file_name)
+ Dependencies.depend_on(file_name)
+ end
+
+ def require_association(file_name)
+ Dependencies.associate_with(file_name)
+ end
+
+ def load_with_new_constant_marking(file, *extras) #:nodoc:
+ Dependencies.new_constants_in(Object) { load_without_new_constant_marking(file, *extras) }
rescue Exception => exception # errors from loading file
exception.blame_file! file
raise
@@ -145,19 +186,18 @@ module ActiveSupport #:nodoc:
end
end
- def inject!
- Object.instance_eval do
- define_method(:require_or_load) { |file_name| Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load)
- define_method(:require_dependency) { |file_name| Dependencies.depend_on(file_name) } unless Object.respond_to?(:require_dependency)
- define_method(:require_association) { |file_name| Dependencies.associate_with(file_name) } unless Object.respond_to?(:require_association)
-
- alias_method :load_without_new_constant_marking, :load
- include Loadable
- end
-
+ def hook!
+ Object.instance_eval { include Loadable }
Module.instance_eval { include ModuleConstMissing }
Class.instance_eval { include ClassConstMissing }
Exception.instance_eval { include Blamable }
+ true
+ end
+
+ def unhook!
+ ModuleConstMissing.excluded(Module)
+ Loadable.excluded(Object)
+ true
end
def load?
@@ -560,4 +600,4 @@ module ActiveSupport #:nodoc:
end
end
-ActiveSupport::Dependencies.inject!
+ActiveSupport::Dependencies.hook!
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 038547a862..39c9c74c94 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -762,4 +762,16 @@ class DependenciesTest < Test::Unit::TestCase
ensure
ActiveSupport::Dependencies.load_once_paths = []
end
+
+ def test_hook_called_multiple_times
+ assert_nothing_raised { ActiveSupport::Dependencies.hook! }
+ end
+
+ def test_unhook
+ ActiveSupport::Dependencies.unhook!
+ assert !Module.new.respond_to?(:const_missing_without_dependencies)
+ assert !Module.new.respond_to?(:load_without_new_constant_marking)
+ ensure
+ ActiveSupport::Dependencies.hook!
+ end
end
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index 97bb81a3c8..44863ab026 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -171,9 +171,12 @@ module Rails
# Load view path cache
load_view_paths
- # load application classes
+ # Load application classes
load_application_classes
+ # Disable dependency loading during request cycle
+ disable_dependency_loading
+
# Flag initialized
Rails.initialized = true
end
@@ -525,6 +528,12 @@ Run `rake gems:install` to install the missing gems.
Dispatcher.define_dispatcher_callbacks(configuration.cache_classes)
Dispatcher.new(RAILS_DEFAULT_LOGGER).send :run_callbacks, :prepare_dispatch
end
+
+ def disable_dependency_loading
+ if configuration.cache_classes && !configuration.dependency_loading
+ ActiveSupport::Dependencies.unhook!
+ end
+ end
end
# The Configuration class holds all the parameters for the Initializer and
@@ -659,6 +668,17 @@ Run `rake gems:install` to install the missing gems.
!!@reload_plugins
end
+ # Enables or disables dependency loading during the request cycle. Setting
+ # dependency_loading to true will allow new classes to be loaded
+ # during a request. Setting it to false will disable this behavior.
+ #
+ # Those who want to run in a threaded environment should disable this
+ # option and eager load or require all there classes on initialization.
+ #
+ # If cache_classes is disabled, dependency loaded will always be
+ # on.
+ attr_accessor :dependency_loading
+
# An array of gems that this rails application depends on. Rails will automatically load
# these gems during installation, and allow you to install any missing gems with:
#
@@ -707,6 +727,7 @@ Run `rake gems:install` to install the missing gems.
self.view_path = default_view_path
self.controller_paths = default_controller_paths
self.cache_classes = default_cache_classes
+ self.dependency_loading = default_dependency_loading
self.whiny_nils = default_whiny_nils
self.plugins = default_plugins
self.plugin_paths = default_plugin_paths
@@ -876,8 +897,8 @@ Run `rake gems:install` to install the missing gems.
paths
end
- def default_dependency_mechanism
- :load
+ def default_dependency_loading
+ true
end
def default_cache_classes
--
cgit v1.2.3
From a87462afcb6c642e59bfcd2e11e3351e2b718c38 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Thu, 24 Jul 2008 13:41:51 -0500
Subject: AbstractRequest.relative_url_root is no longer automatically
configured by a HTTP header. It can now be set in your configuration
environment with config.action_controller.relative_url_root
---
actionpack/CHANGELOG | 2 +
actionpack/lib/action_controller/base.rb | 4 +
actionpack/lib/action_controller/request.rb | 41 +++-----
.../lib/action_controller/routing/optimisations.rb | 2 +-
actionpack/lib/action_controller/url_rewriter.rb | 6 +-
.../lib/action_view/helpers/asset_tag_helper.rb | 6 +-
actionpack/test/controller/redirect_test.rb | 109 ++++++++++-----------
actionpack/test/controller/request_test.rb | 100 +++++--------------
actionpack/test/controller/routing_test.rb | 10 +-
actionpack/test/controller/url_rewriter_test.rb | 55 +++++------
actionpack/test/template/asset_tag_helper_test.rb | 45 ++++-----
11 files changed, 161 insertions(+), 219 deletions(-)
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index ebe4c047b8..03e011c75c 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* AbstractRequest.relative_url_root is no longer automatically configured by a HTTP header. It can now be set in your configuration environment with config.action_controller.relative_url_root [Josh Peek]
+
* Update Prototype to 1.6.0.2 #599 [Patrick Joyce]
* Conditional GET utility methods. [Jeremy Kemper]
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 4dabff637b..bae7e8c12e 100755
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -354,6 +354,10 @@ module ActionController #:nodoc:
class_inheritable_accessor :allow_forgery_protection
self.allow_forgery_protection = true
+ # If you are deploying to a subdirectory, you will need to set
+ # config.action_controller.relative_url_root
+ class_inheritable_accessor :relative_url_root
+
# Holds the request object that's primarily used to get environment variables through access like
# request.env["REQUEST_URI"] .
attr_internal :request
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index c42f113d2c..c55788a531 100755
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -3,13 +3,16 @@ require 'stringio'
require 'strscan'
module ActionController
- # HTTP methods which are accepted by default.
+ # HTTP methods which are accepted by default.
ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options ))
# CgiRequest and TestRequest provide concrete implementations.
class AbstractRequest
- cattr_accessor :relative_url_root
- remove_method :relative_url_root
+ def self.relative_url_root=(*args)
+ ActiveSupport::Deprecation.warn(
+ "ActionController::AbstractRequest.relative_url_root= has been renamed." +
+ "You can now set it with config.action_controller.relative_url_root=", caller)
+ end
# The hash of environment variables for this request,
# such as { 'RAILS_ENV' => 'production' }.
@@ -111,14 +114,14 @@ module ActionController
end
end
end
-
-
+
+
# Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension.
# Example:
#
# class ApplicationController < ActionController::Base
# before_filter :adjust_format_for_iphone
- #
+ #
# private
# def adjust_format_for_iphone
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
@@ -303,26 +306,10 @@ EOM
path = (uri = request_uri) ? uri.split('?').first.to_s : ''
# Cut off the path to the installation directory if given
- path.sub!(%r/^#{relative_url_root}/, '')
- path || ''
- end
-
- # Returns the path minus the web server relative installation directory.
- # This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
- # It can be automatically extracted for Apache setups. If the server is not
- # Apache, this method returns an empty string.
- def relative_url_root
- @@relative_url_root ||= case
- when @env["RAILS_RELATIVE_URL_ROOT"]
- @env["RAILS_RELATIVE_URL_ROOT"]
- when server_software == 'apache'
- @env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '')
- else
- ''
- end
+ path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '')
+ path || ''
end
-
# Read the request body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
@@ -343,15 +330,15 @@ EOM
@symbolized_path_parameters = @parameters = nil
end
- # The same as path_parameters with explicitly symbolized keys
- def symbolized_path_parameters
+ # The same as path_parameters with explicitly symbolized keys
+ def symbolized_path_parameters
@symbolized_path_parameters ||= path_parameters.symbolize_keys
end
# Returns a hash with the parameters used to form the path of the request.
# Returned hash keys are strings. See symbolized_path_parameters for symbolized keys.
#
- # Example:
+ # Example:
#
# {'action' => 'my_action', 'controller' => 'my_controller'}
def path_parameters
diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb
index cd4a423e6b..4b70ea13f2 100644
--- a/actionpack/lib/action_controller/routing/optimisations.rb
+++ b/actionpack/lib/action_controller/routing/optimisations.rb
@@ -76,7 +76,7 @@ module ActionController
elements << '#{request.host_with_port}'
end
- elements << '#{request.relative_url_root if request.relative_url_root}'
+ elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}'
# The last entry in route.segments appears to *always* be a
# 'divider segment' for '/' but we have assertions to ensure that
diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb
index 3a38f23396..d0bf6c0bd4 100644
--- a/actionpack/lib/action_controller/url_rewriter.rb
+++ b/actionpack/lib/action_controller/url_rewriter.rb
@@ -37,7 +37,7 @@ module ActionController
# * :port - Optionally specify the port to connect to.
# * :anchor - An anchor name to be appended to the path.
# * :skip_relative_url_root - If true, the url is not constructed using the
- # +relative_url_root+ set in ActionController::AbstractRequest.relative_url_root.
+ # +relative_url_root+ set in ActionController::Base.relative_url_root.
# * :trailing_slash - If true, adds a trailing slash, as in "/archive/2009/"
#
# Any other key (:controller , :action , etc.) given to
@@ -67,7 +67,7 @@ module ActionController
[:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
end
trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
- url << ActionController::AbstractRequest.relative_url_root.to_s unless options[:skip_relative_url_root]
+ url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
generated = Routing::Routes.generate(options, {})
url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
@@ -108,7 +108,7 @@ module ActionController
end
path = rewrite_path(options)
- rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root]
+ rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "##{options[:anchor]}" if options[:anchor]
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 8d0ee81684..769eada120 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -476,7 +476,7 @@ module ActionView
if has_request
[ @controller.request.protocol,
ActionController::Base.asset_host.to_s,
- @controller.request.relative_url_root,
+ ActionController::Base.relative_url_root,
dir, source, ext, include_host ].join
else
[ ActionController::Base.asset_host.to_s,
@@ -492,8 +492,8 @@ module ActionView
else
source = "/#{dir}/#{source}" unless source[0] == ?/
if has_request
- unless source =~ %r{^#{@controller.request.relative_url_root}/}
- source = "#{@controller.request.relative_url_root}#{source}"
+ unless source =~ %r{^#{ActionController::Base.relative_url_root}/}
+ source = "#{ActionController::Base.relative_url_root}#{source}"
end
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 28da5c6163..2f8bf7b6ee 100755
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -9,11 +9,11 @@ class Workshop
def initialize(id, new_record)
@id, @new_record = id, new_record
end
-
+
def new_record?
@new_record
end
-
+
def to_s
id.to_s
end
@@ -24,32 +24,32 @@ class RedirectController < ActionController::Base
redirect_to :action => "hello_world"
end
- def redirect_with_status
+ def redirect_with_status
redirect_to({:action => "hello_world", :status => 301})
- end
+ end
def redirect_with_status_hash
redirect_to({:action => "hello_world"}, {:status => 301})
- end
+ end
- def url_redirect_with_status
+ def url_redirect_with_status
redirect_to("http://www.example.com", :status => :moved_permanently)
- end
-
- def url_redirect_with_status_hash
+ end
+
+ def url_redirect_with_status_hash
redirect_to("http://www.example.com", {:status => 301})
- end
+ end
- def relative_url_redirect_with_status
+ def relative_url_redirect_with_status
redirect_to("/things/stuff", :status => :found)
- end
-
+ end
+
def relative_url_redirect_with_status_hash
redirect_to("/things/stuff", {:status => 301})
- end
-
- def redirect_to_back_with_status
- redirect_to :back, :status => 307
+ end
+
+ def redirect_to_back_with_status
+ redirect_to :back, :status => 307
end
def host_redirect
@@ -90,9 +90,9 @@ class RedirectController < ActionController::Base
end
def rescue_errors(e) raise e end
-
+
def rescue_action(e) raise end
-
+
protected
def dashbord_url(id, message)
url_for :action => "dashboard", :params => { "id" => id, "message" => message }
@@ -118,48 +118,48 @@ class RedirectTest < Test::Unit::TestCase
assert_equal "http://test.host/redirect/hello_world", redirect_to_url
end
- def test_redirect_with_status
- get :redirect_with_status
- assert_response 301
- assert_equal "http://test.host/redirect/hello_world", redirect_to_url
- end
+ def test_redirect_with_status
+ get :redirect_with_status
+ assert_response 301
+ assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ end
- def test_redirect_with_status_hash
+ def test_redirect_with_status_hash
get :redirect_with_status_hash
- assert_response 301
- assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ assert_response 301
+ assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ end
+
+ def test_url_redirect_with_status
+ get :url_redirect_with_status
+ assert_response 301
+ assert_equal "http://www.example.com", redirect_to_url
end
-
- def test_url_redirect_with_status
- get :url_redirect_with_status
- assert_response 301
- assert_equal "http://www.example.com", redirect_to_url
- end
def test_url_redirect_with_status_hash
get :url_redirect_with_status_hash
- assert_response 301
- assert_equal "http://www.example.com", redirect_to_url
- end
+ assert_response 301
+ assert_equal "http://www.example.com", redirect_to_url
+ end
-
- def test_relative_url_redirect_with_status
- get :relative_url_redirect_with_status
+
+ def test_relative_url_redirect_with_status
+ get :relative_url_redirect_with_status
assert_response 302
- assert_equal "http://test.host/things/stuff", redirect_to_url
- end
-
+ assert_equal "http://test.host/things/stuff", redirect_to_url
+ end
+
def test_relative_url_redirect_with_status_hash
get :relative_url_redirect_with_status_hash
- assert_response 301
- assert_equal "http://test.host/things/stuff", redirect_to_url
- end
-
- def test_redirect_to_back_with_status
- @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
- get :redirect_to_back_with_status
- assert_response 307
- assert_equal "http://www.example.com/coming/from", redirect_to_url
+ assert_response 301
+ assert_equal "http://test.host/things/stuff", redirect_to_url
+ end
+
+ def test_redirect_to_back_with_status
+ @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
+ get :redirect_to_back_with_status
+ assert_response 307
+ assert_equal "http://www.example.com/coming/from", redirect_to_url
end
def test_simple_redirect_using_options
@@ -204,20 +204,20 @@ class RedirectTest < Test::Unit::TestCase
assert_response :redirect
assert_equal "http://www.example.com/coming/from", redirect_to_url
end
-
+
def test_redirect_to_back_with_no_referer
assert_raises(ActionController::RedirectBackError) {
@request.env["HTTP_REFERER"] = nil
get :redirect_to_back
}
end
-
+
def test_redirect_to_record
ActionController::Routing::Routes.draw do |map|
map.resources :workshops
map.connect ':controller/:action/:id'
end
-
+
get :redirect_to_existing_record
assert_equal "http://test.host/workshops/5", redirect_to_url
assert_redirected_to Workshop.new(5, false)
@@ -237,7 +237,6 @@ class RedirectTest < Test::Unit::TestCase
get :redirect_to_nil
end
end
-
end
module ModuleTest
diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb
index 932c0e21a1..7db5264840 100644
--- a/actionpack/test/controller/request_test.rb
+++ b/actionpack/test/controller/request_test.rb
@@ -3,9 +3,14 @@ require 'action_controller/integration'
class RequestTest < Test::Unit::TestCase
def setup
+ ActionController::Base.relative_url_root = nil
@request = ActionController::TestRequest.new
end
+ def teardown
+ ActionController::Base.relative_url_root = nil
+ end
+
def test_remote_ip
assert_equal '0.0.0.0', @request.remote_ip
@@ -38,7 +43,7 @@ class RequestTest < Test::Unit::TestCase
@request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1,3.4.5.6'
assert_equal '3.4.5.6', @request.remote_ip
-
+
@request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1, 10.0.0.1, 3.4.5.6'
assert_equal '3.4.5.6', @request.remote_ip
@@ -120,155 +125,105 @@ class RequestTest < Test::Unit::TestCase
assert_equal ":8080", @request.port_string
end
- def test_relative_url_root
- @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
- @request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3'
- assert_equal '', @request.relative_url_root, "relative_url_root should be disabled on lighttpd"
-
- @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text'
-
- @request.env['SCRIPT_NAME'] = nil
- assert_equal "", @request.relative_url_root
-
- @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
- assert_equal "", @request.relative_url_root
-
- @request.env['SCRIPT_NAME'] = "/myapp.rb"
- assert_equal "", @request.relative_url_root
-
- @request.relative_url_root = nil
- @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
- assert_equal "/hieraki", @request.relative_url_root
-
- @request.relative_url_root = nil
- @request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi"
- assert_equal "/collaboration/hieraki", @request.relative_url_root
-
- # apache/scgi case
- @request.relative_url_root = nil
- @request.env['SCRIPT_NAME'] = "/collaboration/hieraki"
- assert_equal "/collaboration/hieraki", @request.relative_url_root
-
- @request.relative_url_root = nil
- @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
- @request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3'
- @request.env['RAILS_RELATIVE_URL_ROOT'] = "/hieraki"
- assert_equal "/hieraki", @request.relative_url_root
-
- # @env overrides path guess
- @request.relative_url_root = nil
- @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
- @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text'
- @request.env['RAILS_RELATIVE_URL_ROOT'] = "/real_url"
- assert_equal "/real_url", @request.relative_url_root
- end
-
def test_request_uri
@request.env['SERVER_SOFTWARE'] = 'Apache 42.342.3432'
- @request.relative_url_root = nil
@request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri?mapped=1"
assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
assert_equal "/path/of/some/uri", @request.path
- @request.relative_url_root = nil
@request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri"
assert_equal "/path/of/some/uri", @request.request_uri
assert_equal "/path/of/some/uri", @request.path
- @request.relative_url_root = nil
@request.set_REQUEST_URI "/path/of/some/uri"
assert_equal "/path/of/some/uri", @request.request_uri
assert_equal "/path/of/some/uri", @request.path
- @request.relative_url_root = nil
@request.set_REQUEST_URI "/"
assert_equal "/", @request.request_uri
assert_equal "/", @request.path
- @request.relative_url_root = nil
@request.set_REQUEST_URI "/?m=b"
assert_equal "/?m=b", @request.request_uri
assert_equal "/", @request.path
- @request.relative_url_root = nil
@request.set_REQUEST_URI "/"
@request.env['SCRIPT_NAME'] = "/dispatch.cgi"
assert_equal "/", @request.request_uri
assert_equal "/", @request.path
- @request.relative_url_root = nil
+ ActionController::Base.relative_url_root = "/hieraki"
@request.set_REQUEST_URI "/hieraki/"
@request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
assert_equal "/hieraki/", @request.request_uri
assert_equal "/", @request.path
+ ActionController::Base.relative_url_root = nil
- @request.relative_url_root = nil
+ ActionController::Base.relative_url_root = "/collaboration/hieraki"
@request.set_REQUEST_URI "/collaboration/hieraki/books/edit/2"
@request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi"
assert_equal "/collaboration/hieraki/books/edit/2", @request.request_uri
assert_equal "/books/edit/2", @request.path
+ ActionController::Base.relative_url_root = nil
# The following tests are for when REQUEST_URI is not supplied (as in IIS)
- @request.relative_url_root = nil
@request.set_REQUEST_URI nil
@request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
@request.env['SCRIPT_NAME'] = nil #"/path/dispatch.rb"
assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
assert_equal "/path/of/some/uri", @request.path
+ ActionController::Base.relative_url_root = '/path'
@request.set_REQUEST_URI nil
- @request.relative_url_root = nil
@request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
@request.env['SCRIPT_NAME'] = "/path/dispatch.rb"
assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
assert_equal "/of/some/uri", @request.path
+ ActionController::Base.relative_url_root = nil
@request.set_REQUEST_URI nil
- @request.relative_url_root = nil
@request.env['PATH_INFO'] = "/path/of/some/uri"
@request.env['SCRIPT_NAME'] = nil
assert_equal "/path/of/some/uri", @request.request_uri
assert_equal "/path/of/some/uri", @request.path
@request.set_REQUEST_URI nil
- @request.relative_url_root = nil
@request.env['PATH_INFO'] = "/"
assert_equal "/", @request.request_uri
assert_equal "/", @request.path
@request.set_REQUEST_URI nil
- @request.relative_url_root = nil
@request.env['PATH_INFO'] = "/?m=b"
assert_equal "/?m=b", @request.request_uri
assert_equal "/", @request.path
@request.set_REQUEST_URI nil
- @request.relative_url_root = nil
@request.env['PATH_INFO'] = "/"
@request.env['SCRIPT_NAME'] = "/dispatch.cgi"
assert_equal "/", @request.request_uri
assert_equal "/", @request.path
+ ActionController::Base.relative_url_root = '/hieraki'
@request.set_REQUEST_URI nil
- @request.relative_url_root = nil
@request.env['PATH_INFO'] = "/hieraki/"
@request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
assert_equal "/hieraki/", @request.request_uri
assert_equal "/", @request.path
+ ActionController::Base.relative_url_root = nil
@request.set_REQUEST_URI '/hieraki/dispatch.cgi'
- @request.relative_url_root = '/hieraki'
+ ActionController::Base.relative_url_root = '/hieraki'
assert_equal "/dispatch.cgi", @request.path
- @request.relative_url_root = nil
+ ActionController::Base.relative_url_root = nil
@request.set_REQUEST_URI '/hieraki/dispatch.cgi'
- @request.relative_url_root = '/foo'
+ ActionController::Base.relative_url_root = '/foo'
assert_equal "/hieraki/dispatch.cgi", @request.path
- @request.relative_url_root = nil
+ ActionController::Base.relative_url_root = nil
# This test ensures that Rails uses REQUEST_URI over PATH_INFO
- @request.relative_url_root = nil
+ ActionController::Base.relative_url_root = nil
@request.env['REQUEST_URI'] = "/some/path"
@request.env['PATH_INFO'] = "/another/path"
@request.env['SCRIPT_NAME'] = "/dispatch.cgi"
@@ -276,13 +231,12 @@ class RequestTest < Test::Unit::TestCase
assert_equal "/some/path", @request.path
end
-
def test_host_with_default_port
@request.host = "rubyonrails.org"
@request.port = 80
assert_equal "rubyonrails.org", @request.host_with_port
end
-
+
def test_host_with_non_default_port
@request.host = "rubyonrails.org"
@request.port = 81
@@ -415,15 +369,15 @@ class RequestTest < Test::Unit::TestCase
@request.env["CONTENT_TYPE"] = "application/xml; charset=UTF-8"
assert_equal Mime::XML, @request.content_type
end
-
+
def test_user_agent
assert_not_nil @request.user_agent
end
-
+
def test_parameters
@request.instance_eval { @request_parameters = { "foo" => 1 } }
@request.instance_eval { @query_parameters = { "bar" => 2 } }
-
+
assert_equal({"foo" => 1, "bar" => 2}, @request.parameters)
assert_equal({"foo" => 1}, @request.request_parameters)
assert_equal({"bar" => 2}, @request.query_parameters)
@@ -774,19 +728,19 @@ class MultipartRequestParameterParsingTest < Test::Unit::TestCase
file = params['file']
foo = params['foo']
-
+
if RUBY_VERSION > '1.9'
assert_kind_of File, file
else
assert_kind_of Tempfile, file
end
-
+
assert_equal 'file.txt', file.original_filename
assert_equal "text/plain", file.content_type
-
+
assert_equal 'bar', foo
end
-
+
def test_large_text_file
params = process('large_text_file')
assert_equal %w(file foo), params.keys.sort
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 079189d7b3..84996fd6b1 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -731,15 +731,10 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def request
@request ||= MockRequest.new(:host => "named.route.test", :method => :get)
end
-
- def relative_url_root=(value)
- request.relative_url_root=value
- end
end
class MockRequest
- attr_accessor :path, :path_parameters, :host, :subdomains, :domain,
- :method, :relative_url_root
+ attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method
def initialize(values={})
values.each { |key, value| send("#{key}=", value) }
@@ -920,10 +915,11 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
def test_basic_named_route_with_relative_url_root
rs.add_named_route :home, '', :controller => 'content', :action => 'list'
x = setup_for_named_route
- x.relative_url_root="/foo"
+ ActionController::Base.relative_url_root = "/foo"
assert_equal("http://named.route.test/foo/",
x.send(:home_url))
assert_equal "/foo/", x.send(:home_path)
+ ActionController::Base.relative_url_root = nil
end
def test_named_route_with_option
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index a9974db5d5..64e9a085ca 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -7,7 +7,7 @@ class UrlRewriterTests < Test::Unit::TestCase
@request = ActionController::TestRequest.new
@params = {}
@rewriter = ActionController::UrlRewriter.new(@request, @params)
- end
+ end
def test_port
assert_equal('http://test.host:1271/c/a/i',
@@ -24,7 +24,7 @@ class UrlRewriterTests < Test::Unit::TestCase
@rewriter.rewrite(:protocol => 'https://', :controller => 'c', :action => 'a', :id => 'i')
)
end
-
+
def test_user_name_and_password
assert_equal(
'http://david:secret@test.host/c/a/i',
@@ -38,12 +38,12 @@ class UrlRewriterTests < Test::Unit::TestCase
@rewriter.rewrite(:user => "openid.aol.com/nextangler", :password => "one two?", :controller => 'c', :action => 'a', :id => 'i')
)
end
-
- def test_anchor
- assert_equal(
- 'http://test.host/c/a/i#anchor',
- @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor')
- )
+
+ def test_anchor
+ assert_equal(
+ 'http://test.host/c/a/i#anchor',
+ @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor')
+ )
end
def test_overwrite_params
@@ -55,12 +55,12 @@ class UrlRewriterTests < Test::Unit::TestCase
u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:action => 'hi'})
assert_match %r(/hi/hi/2$), u
end
-
+
def test_overwrite_removes_original
@params[:controller] = 'search'
@params[:action] = 'list'
@params[:list_page] = 1
-
+
assert_equal '/search/list?list_page=2', @rewriter.rewrite(:only_path => true, :overwrite_params => {"list_page" => 2})
u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:list_page => 2})
assert_equal 'http://test.host/search/list?list_page=2', u
@@ -86,19 +86,19 @@ class UrlRewriterTests < Test::Unit::TestCase
end
class UrlWriterTests < Test::Unit::TestCase
-
+
class W
include ActionController::UrlWriter
end
-
+
def teardown
W.default_url_options.clear
end
-
+
def add_host!
W.default_url_options[:host] = 'www.basecamphq.com'
end
-
+
def test_exception_is_thrown_without_host
assert_raises RuntimeError do
W.new.url_for :controller => 'c', :action => 'a', :id => 'i'
@@ -110,35 +110,35 @@ class UrlWriterTests < Test::Unit::TestCase
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor')
)
end
-
+
def test_default_host
add_host!
assert_equal('http://www.basecamphq.com/c/a/i',
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i')
)
end
-
+
def test_host_may_be_overridden
add_host!
assert_equal('http://37signals.basecamphq.com/c/a/i',
W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i')
)
end
-
+
def test_port
add_host!
assert_equal('http://www.basecamphq.com:3000/c/a/i',
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000)
)
end
-
+
def test_protocol
add_host!
assert_equal('https://www.basecamphq.com/c/a/i',
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
)
end
-
+
def test_protocol_with_and_without_separator
add_host!
assert_equal('https://www.basecamphq.com/c/a/i',
@@ -184,15 +184,15 @@ class UrlWriterTests < Test::Unit::TestCase
end
def test_relative_url_root_is_respected
- orig_relative_url_root = ActionController::AbstractRequest.relative_url_root
- ActionController::AbstractRequest.relative_url_root = '/subdir'
+ orig_relative_url_root = ActionController::Base.relative_url_root
+ ActionController::Base.relative_url_root = '/subdir'
add_host!
assert_equal('https://www.basecamphq.com/subdir/c/a/i',
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
)
ensure
- ActionController::AbstractRequest.relative_url_root = orig_relative_url_root
+ ActionController::Base.relative_url_root = orig_relative_url_root
end
def test_named_routes
@@ -217,8 +217,8 @@ class UrlWriterTests < Test::Unit::TestCase
end
def test_relative_url_root_is_respected_for_named_routes
- orig_relative_url_root = ActionController::AbstractRequest.relative_url_root
- ActionController::AbstractRequest.relative_url_root = '/subdir'
+ orig_relative_url_root = ActionController::Base.relative_url_root
+ ActionController::Base.relative_url_root = '/subdir'
ActionController::Routing::Routes.draw do |map|
map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
@@ -231,7 +231,7 @@ class UrlWriterTests < Test::Unit::TestCase
controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
ensure
ActionController::Routing::Routes.load!
- ActionController::AbstractRequest.relative_url_root = orig_relative_url_root
+ ActionController::Base.relative_url_root = orig_relative_url_root
end
def test_only_path
@@ -239,14 +239,14 @@ class UrlWriterTests < Test::Unit::TestCase
map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
map.connect ':controller/:action/:id'
end
-
+
# We need to create a new class in order to install the new named route.
kls = Class.new { include ActionController::UrlWriter }
controller = kls.new
assert controller.respond_to?(:home_url)
assert_equal '/brave/new/world',
controller.send(:url_for, :controller => 'brave', :action => 'new', :id => 'world', :only_path => true)
-
+
assert_equal("/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'unused', :only_path => true))
assert_equal("/home/sweet/home/alabama", controller.send(:home_path, 'alabama'))
ensure
@@ -306,5 +306,4 @@ class UrlWriterTests < Test::Unit::TestCase
def extract_params(url)
url.split('?', 2).last.split('&')
end
-
end
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 0e7f9a94b7..8410e82c3c 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -30,7 +30,6 @@ class AssetTagHelperTest < ActionView::TestCase
end.new
@request = Class.new do
- def relative_url_root() "" end
def protocol() 'http://' end
def ssl?() false end
def host_with_port() 'localhost' end
@@ -118,7 +117,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(image_path("xml")) => %(/images/xml),
%(image_path("xml.png")) => %(/images/xml.png),
%(image_path("dir/xml.png")) => %(/images/dir/xml.png),
- %(image_path("/dir/xml.png")) => %(/dir/xml.png)
+ %(image_path("/dir/xml.png")) => %(/dir/xml.png)
}
PathToImageToTag = {
@@ -173,7 +172,7 @@ class AssetTagHelperTest < ActionView::TestCase
ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'lib1', '/elsewhere/blub/lib2'
assert_dom_equal %(\n\n\n\n\n\n\n), javascript_include_tag(:defaults)
end
-
+
def test_custom_javascript_expansions
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :monkey => ["head", "body", "tail"]
assert_dom_equal %(\n\n\n\n), javascript_include_tag('first', :monkey, 'last')
@@ -216,7 +215,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_image_path
ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
-
+
def test_path_to_image_alias_for_image_path
PathToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
@@ -224,7 +223,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_image_tag
ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
-
+
def test_timebased_asset_id
expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s
assert_equal %( ), image_tag("rails.png")
@@ -233,7 +232,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_should_skip_asset_id_on_complete_url
assert_equal %( ), image_tag("http://www.example.com/rails.png")
end
-
+
def test_should_use_preset_asset_id
ENV["RAILS_ASSET_ID"] = "4500"
assert_equal %( ), image_tag("rails.png")
@@ -255,14 +254,14 @@ class AssetTagHelperTest < ActionView::TestCase
ENV["RAILS_ASSET_ID"] = ""
ActionController::Base.asset_host = 'http://a0.example.com'
ActionController::Base.perform_caching = true
-
+
assert_dom_equal(
%(),
javascript_include_tag(:all, :cache => true)
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
-
+
assert_dom_equal(
%(),
javascript_include_tag(:all, :cache => "money")
@@ -344,7 +343,7 @@ class AssetTagHelperTest < ActionView::TestCase
ensure
FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'cache', 'money.js'))
end
-
+
def test_caching_javascript_include_tag_with_all_and_recursive_puts_defaults_at_the_start_of_the_file
ENV["RAILS_ASSET_ID"] = ""
ActionController::Base.asset_host = 'http://a0.example.com'
@@ -390,7 +389,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_caching_javascript_include_tag_when_caching_off
ENV["RAILS_ASSET_ID"] = ""
ActionController::Base.perform_caching = false
-
+
assert_dom_equal(
%(\n\n\n\n\n\n\n),
javascript_include_tag(:all, :cache => true)
@@ -402,7 +401,7 @@ class AssetTagHelperTest < ActionView::TestCase
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
-
+
assert_dom_equal(
%(\n\n\n\n\n\n\n),
javascript_include_tag(:all, :cache => "money")
@@ -420,7 +419,7 @@ class AssetTagHelperTest < ActionView::TestCase
ENV["RAILS_ASSET_ID"] = ""
ActionController::Base.asset_host = 'http://a0.example.com'
ActionController::Base.perform_caching = true
-
+
assert_dom_equal(
%( ),
stylesheet_link_tag(:all, :cache => true)
@@ -459,7 +458,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_caching_stylesheet_include_tag_when_caching_off
ENV["RAILS_ASSET_ID"] = ""
ActionController::Base.perform_caching = false
-
+
assert_dom_equal(
%( \n \n ),
stylesheet_link_tag(:all, :cache => true)
@@ -471,7 +470,7 @@ class AssetTagHelperTest < ActionView::TestCase
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
-
+
assert_dom_equal(
%( \n \n ),
stylesheet_link_tag(:all, :cache => "money")
@@ -490,6 +489,8 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
tests ActionView::Helpers::AssetTagHelper
def setup
+ ActionController::Base.relative_url_root = "/collaboration/hieraki"
+
@controller = Class.new do
attr_accessor :request
@@ -497,22 +498,22 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
"http://www.example.com/collaboration/hieraki"
end
end.new
-
- @request = Class.new do
- def relative_url_root
- "/collaboration/hieraki"
- end
+ @request = Class.new do
def protocol
'gopher://'
end
end.new
-
+
@controller.request = @request
-
+
ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
end
+ def teardown
+ ActionController::Base.relative_url_root = nil
+ end
+
def test_should_compute_proper_path
assert_dom_equal(%( ), auto_discovery_link_tag)
assert_dom_equal(%(/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
@@ -521,7 +522,7 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
assert_dom_equal(%( ), image_tag("mouse.png", :mouseover => "/images/mouse_over.png"))
assert_dom_equal(%( ), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png")))
end
-
+
def test_should_ignore_relative_root_path_on_complete_url
assert_dom_equal(%(http://www.example.com/images/xml.png), image_path("http://www.example.com/images/xml.png"))
end
--
cgit v1.2.3
From 11fdcf88c2aea72ec84c5d4ab05986f5d35a9a81 Mon Sep 17 00:00:00 2001
From: Sam Granieri
Date: Thu, 24 Jul 2008 13:51:54 -0500
Subject: Check for ActionMailer and ActionController before attempting to
eager load their view paths Signed-off-by: Joshua Peek
---
railties/lib/initializer.rb | 4 ++--
railties/test/initializer_test.rb | 21 ++++++++++++++++++++-
2 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index 44863ab026..32411e8928 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -341,8 +341,8 @@ Run `rake gems:install` to install the missing gems.
def load_view_paths
ActionView::PathSet::Path.eager_load_templates! if configuration.cache_classes
- ActionMailer::Base.template_root.load
- ActionController::Base.view_paths.load
+ ActionMailer::Base.template_root.load if configuration.frameworks.include?(:action_mailer)
+ ActionController::Base.view_paths.load if configuration.frameworks.include?(:action_controller)
end
# Eager load application classes
diff --git a/railties/test/initializer_test.rb b/railties/test/initializer_test.rb
index dee7abe05f..07303a510e 100644
--- a/railties/test/initializer_test.rb
+++ b/railties/test/initializer_test.rb
@@ -136,8 +136,27 @@ uses_mocha 'framework paths' do
end
end
- protected
+ def test_action_mailer_load_paths_set_only_if_action_mailer_in_use
+ @config.frameworks = [:action_controller]
+ initializer = Rails::Initializer.new @config
+ initializer.send :require_frameworks
+
+ assert_nothing_raised NameError do
+ initializer.send :load_view_paths
+ end
+ end
+ def test_action_controller_load_paths_set_only_if_action_controller_in_use
+ @config.frameworks = []
+ initializer = Rails::Initializer.new @config
+ initializer.send :require_frameworks
+
+ assert_nothing_raised NameError do
+ initializer.send :load_view_paths
+ end
+ end
+
+ protected
def assert_framework_path(path)
assert @config.framework_paths.include?(path),
"<#{path.inspect}> not found among <#{@config.framework_paths.inspect}>"
--
cgit v1.2.3
From f48b9ab5c2741ddbdbc0a9f4cd06875a1e3c8b02 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Thu, 24 Jul 2008 14:06:22 -0500
Subject: ActionController::Base.relative_url_root falls back to
ENV['RAILS_RELATIVE_URL_ROOT']
---
actionpack/lib/action_controller/base.rb | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index bae7e8c12e..5f4a38dac0 100755
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -356,7 +356,12 @@ module ActionController #:nodoc:
# If you are deploying to a subdirectory, you will need to set
# config.action_controller.relative_url_root
- class_inheritable_accessor :relative_url_root
+ # This defaults to ENV['RAILS_RELATIVE_URL_ROOT']
+ cattr_writer :relative_url_root
+
+ def self.relative_url_root
+ @@relative_url_root || ENV['RAILS_RELATIVE_URL_ROOT']
+ end
# Holds the request object that's primarily used to get environment variables through access like
# request.env["REQUEST_URI"] .
--
cgit v1.2.3
From e8fc894f66daa7909d1790f2cd145844d256d282 Mon Sep 17 00:00:00 2001
From: George Ogata
Date: Wed, 23 Jul 2008 06:38:26 +1000
Subject: Make observers define #after_find in the model only if needed.
[#676 state:resolved]
Signed-off-by: Michael Koziarski
---
activerecord/lib/active_record/observer.rb | 3 +++
activerecord/test/cases/lifecycle_test.rb | 32 ++++++++++++++++++++++++++++--
2 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index c96e5f9d51..b35e407cc1 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -189,6 +189,9 @@ module ActiveRecord
def add_observer!(klass)
klass.add_observer(self)
+ if respond_to?(:after_find) && !klass.method_defined?(:after_find)
+ klass.class_eval 'def after_find() end'
+ end
end
end
end
diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb
index 3432abee31..ab005c6b00 100755
--- a/activerecord/test/cases/lifecycle_test.rb
+++ b/activerecord/test/cases/lifecycle_test.rb
@@ -143,12 +143,12 @@ class LifecycleTest < ActiveRecord::TestCase
assert_equal developer.name, multi_observer.record.name
end
- def test_after_find_cannot_be_observed_when_its_not_defined_on_the_model
+ def test_after_find_can_be_observed_when_its_not_defined_on_the_model
observer = MinimalisticObserver.instance
assert_equal Minimalistic, MinimalisticObserver.observed_class
minimalistic = Minimalistic.find(1)
- assert_nil observer.minimalistic
+ assert_equal minimalistic, observer.minimalistic
end
def test_after_find_can_be_observed_when_its_defined_on_the_model
@@ -159,6 +159,34 @@ class LifecycleTest < ActiveRecord::TestCase
assert_equal topic, observer.topic
end
+ def test_after_find_is_not_created_if_its_not_used
+ # use a fresh class so an observer can't have defined an
+ # after_find on it
+ model_class = Class.new(ActiveRecord::Base)
+ observer_class = Class.new(ActiveRecord::Observer)
+ observer_class.observe(model_class)
+
+ observer = observer_class.instance
+
+ assert !model_class.method_defined?(:after_find)
+ end
+
+ def test_after_find_is_not_clobbered_if_it_already_exists
+ # use a fresh observer class so we can instantiate it (Observer is
+ # a Singleton)
+ model_class = Class.new(ActiveRecord::Base) do
+ def after_find; end
+ end
+ original_method = model_class.instance_method(:after_find)
+ observer_class = Class.new(ActiveRecord::Observer) do
+ def after_find; end
+ end
+ observer_class.observe(model_class)
+
+ observer = observer_class.instance
+ assert_equal original_method, model_class.instance_method(:after_find)
+ end
+
def test_invalid_observer
assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers }
end
--
cgit v1.2.3
From 490178c93008c6fca20e3e2d6302a28d86aab94d Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Sun, 27 Jul 2008 16:06:51 -0500
Subject: Revert "Ensure adapater specific code is loaded on
ActiveRecord::Base.establish_connection"
This reverts commit 8b858782fa693e89a47fc3dd5ae38d842ede6d04.
---
.../connection_adapters/abstract/connection_specification.rb | 1 -
1 file changed, 1 deletion(-)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index 07b122efd1..2a8807fb78 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -211,7 +211,6 @@ module ActiveRecord
clear_active_connection_name
@active_connection_name = name
@@defined_connections[name] = spec
- connection
when Symbol, String
if configuration = configurations[spec.to_s]
establish_connection(configuration)
--
cgit v1.2.3
From f7abf0c9db61621b3f27061debd7983075cdca61 Mon Sep 17 00:00:00 2001
From: Clemens Kofler
Date: Sun, 27 Jul 2008 16:34:20 -0500
Subject: error_message_on takes an options hash instead of ordered parameters
[#704 state:resolved] Signed-off-by: Joshua Peek
---
.../action_view/helpers/active_record_helper.rb | 46 ++++++++++++++-------
actionpack/lib/action_view/helpers/form_helper.rb | 4 +-
.../test/template/active_record_helper_test.rb | 48 +++++++++++-----------
3 files changed, 58 insertions(+), 40 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb
index 959d8a8563..ff70ce9aae 100644
--- a/actionpack/lib/action_view/helpers/active_record_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -25,7 +25,7 @@ module ActionView
# Returns an entire form with all needed input tags for a specified Active Record object. For example, if @post
# has attributes named +title+ of type +VARCHAR+ and +body+ of type +TEXT+ then
#
- # form("post")
+ # form("post")
#
# would yield a form like the following (modulus formatting):
#
@@ -90,23 +90,41 @@ module ActionView
end
# Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
- # This error message is wrapped in a DIV tag, which can be extended to include a +prepend_text+ and/or +append_text+
- # (to properly explain the error), and a +css_class+ to style it accordingly. +object+ should either be the name of an instance variable or
- # the actual object. As an example, let's say you have a model @post that has an error message on the +title+ attribute:
+ # This error message is wrapped in a DIV tag, which can be extended to include a :prepend_text
+ # and/or :append_text (to properly explain the error), and a :css_class to style it
+ # accordingly. +object+ should either be the name of an instance variable or the actual object. The method can be
+ # passed in either as a string or a symbol.
+ # As an example, let's say you have a model @post that has an error message on the +title+ attribute:
#
# <%= error_message_on "post", "title" %>
# # => can't be empty
#
- # <%= error_message_on @post, "title" %>
+ # <%= error_message_on @post, :title %>
# # => can't be empty
#
- # <%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %>
- # # => Title simply can't be empty (or it won't work).
- def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
+ # <%= error_message_on "post", "title",
+ # :prepend_text => "Title simply ",
+ # :append_text => " (or it won't work).",
+ # :css_class => "inputError" %>
+ def error_message_on(object, method, *args)
+ options = args.extract_options!
+ unless args.empty?
+ ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate' +
+ 'prepend_text, append_text, and css_class arguments', caller)
+
+ options[:prepend_text] = args[0] || ''
+ options[:append_text] = args[1] || ''
+ options[:css_class] = args[2] || 'formError'
+ end
+ options.reverse_merge!(:prepend_text => '', :append_text => '', :css_class => 'formError')
+
if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
(errors = obj.errors.on(method))
- content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class)
- else
+ content_tag("div",
+ "#{options[:prepend_text]}#{errors.is_a?(Array) ? errors.first : errors}#{options[:append_text]}",
+ :class => options[:css_class]
+ )
+ else
''
end
end
@@ -133,7 +151,7 @@ module ActionView
#
# To specify the display for one object, you simply provide its name as a parameter.
# For example, for the @user model:
- #
+ #
# error_messages_for 'user'
#
# To specify more than one object, you simply list them; optionally, you can add an extra :object_name parameter, which
@@ -157,7 +175,7 @@ module ActionView
else
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
end
-
+
count = objects.inject(0) {|sum, object| sum + object.errors.count }
unless count.zero?
html = {}
@@ -174,7 +192,7 @@ module ActionView
I18n.with_options :locale => options[:locale], :scope => [:active_record, :error] do |locale|
header_message = if options.include?(:header_message)
options[:header_message]
- else
+ else
object_name = options[:object_name].to_s.gsub('_', ' ')
object_name = I18n.t(object_name, :default => object_name)
locale.t :header_message, :count => count, :object_name => object_name
@@ -193,7 +211,7 @@ module ActionView
''
end
end
-
+
private
def all_input_tags(record, record_name, options)
input_block = options[:input_block] || default_input_block
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index ada6fa2ea8..7bb82ba5bb 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -782,8 +782,8 @@ module ActionView
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
end
- def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError")
- @template.error_message_on(@object, method, prepend_text, append_text, css_class)
+ def error_message_on(method, *args)
+ @template.error_message_on(@object, method, *args)
end
def error_messages(options = {})
diff --git a/actionpack/test/template/active_record_helper_test.rb b/actionpack/test/template/active_record_helper_test.rb
index dfc30e651a..e46f95d18b 100644
--- a/actionpack/test/template/active_record_helper_test.rb
+++ b/actionpack/test/template/active_record_helper_test.rb
@@ -10,17 +10,17 @@ class ActiveRecordHelperTest < ActionView::TestCase
alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast)
alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast)
end
-
+
User = Struct.new("User", :email)
User.class_eval do
alias_method :email_before_type_cast, :email unless respond_to?(:email_before_type_cast)
end
-
+
Column = Struct.new("Column", :type, :name, :human_name)
end
def setup_post
- @post = Post.new
+ @post = Post.new
def @post.errors
Class.new {
def on(field)
@@ -33,12 +33,12 @@ class ActiveRecordHelperTest < ActionView::TestCase
false
end
end
- def empty?() false end
- def count() 1 end
+ def empty?() false end
+ def count() 1 end
def full_messages() [ "Author name can't be empty" ] end
}.new
end
-
+
def @post.new_record?() true end
def @post.to_param() nil end
@@ -58,16 +58,16 @@ class ActiveRecordHelperTest < ActionView::TestCase
end
def setup_user
- @user = User.new
+ @user = User.new
def @user.errors
Class.new {
def on(field) field == "email" end
- def empty?() false end
- def count() 1 end
+ def empty?() false end
+ def count() 1 end
def full_messages() [ "User email can't be empty" ] end
}.new
end
-
+
def @user.new_record?() true end
def @user.to_param() nil end
@@ -81,7 +81,7 @@ class ActiveRecordHelperTest < ActionView::TestCase
@user.email = ""
end
-
+
def protect_against_forgery?
@protect_against_forgery ? true : false
end
@@ -92,7 +92,7 @@ class ActiveRecordHelperTest < ActionView::TestCase
setup_user
@response = ActionController::TestResponse.new
-
+
@controller = Object.new
def @controller.url_for(options)
options = options.symbolize_keys
@@ -111,7 +111,7 @@ class ActiveRecordHelperTest < ActionView::TestCase
assert_dom_equal(
%(
),
text_area("post", "body")
- )
+ )
end
def test_text_field_with_errors
@@ -140,7 +140,7 @@ class ActiveRecordHelperTest < ActionView::TestCase
form("post")
)
end
-
+
def test_form_with_protect_against_forgery
@protect_against_forgery = true
@request_forgery_protection_token = 'authenticity_token'
@@ -150,7 +150,7 @@ class ActiveRecordHelperTest < ActionView::TestCase
form("post")
)
end
-
+
def test_form_with_method_option
assert_dom_equal(
%(
),
@@ -211,9 +211,9 @@ class ActiveRecordHelperTest < ActionView::TestCase
other_post = @post
assert_dom_equal "can't be empty
", error_message_on(other_post, :author_name)
end
-
- def test_error_message_on_should_use_options
- assert_dom_equal "beforecan't be emptyafter
", error_message_on(:post, :author_name, "before", "after", "differentError")
+
+ def test_error_message_on_with_options_hash
+ assert_dom_equal "beforecan't be emptyafter
", error_message_on(:post, :author_name, :css_class => 'differentError', :prepend_text => 'before', :append_text => 'after')
end
def test_error_messages_for_many_objects
@@ -224,10 +224,10 @@ class ActiveRecordHelperTest < ActionView::TestCase
# add the default to put post back in the title
assert_dom_equal %(2 errors prohibited this post from being saved There were problems with the following fields:
User email can't be empty Author name can't be empty ), error_messages_for("user", "post", :object_name => "post")
-
+
# symbols work as well
assert_dom_equal %(2 errors prohibited this post from being saved There were problems with the following fields:
User email can't be empty Author name can't be empty ), error_messages_for(:user, :post, :object_name => :post)
-
+
# any default works too
assert_dom_equal %(2 errors prohibited this monkey from being saved There were problems with the following fields:
User email can't be empty Author name can't be empty ), error_messages_for(:user, :post, :object_name => "monkey")
@@ -242,7 +242,7 @@ class ActiveRecordHelperTest < ActionView::TestCase
message = "Please fix the following fields and resubmit:"
assert_dom_equal %(#{header_message} #{message}
User email can't be empty Author name can't be empty ), error_messages_for(:user, :post, :header_message => header_message, :message => message)
end
-
+
def test_error_messages_for_non_instance_variable
actual_user = @user
actual_post = @post
@@ -251,14 +251,14 @@ class ActiveRecordHelperTest < ActionView::TestCase
#explicitly set object
assert_dom_equal %(1 error prohibited this post from being saved There were problems with the following fields:
Author name can't be empty ), error_messages_for("post", :object => actual_post)
-
+
#multiple objects
assert_dom_equal %(2 errors prohibited this user from being saved There were problems with the following fields:
User email can't be empty Author name can't be empty ), error_messages_for("user", "post", :object => [actual_user, actual_post])
-
+
#nil object
assert_equal '', error_messages_for('user', :object => nil)
end
-
+
def test_form_with_string_multipart
assert_dom_equal(
%(Title
\nBody
Back to the hill and over it again!
),
--
cgit v1.2.3
From 10d9fe4bf3110c1d5de0c6b509fe0cbb9d5eda1d Mon Sep 17 00:00:00 2001
From: Clemens Kofler
Date: Sun, 27 Jul 2008 16:49:19 -0500
Subject: Refactored TextHelper#truncate, highlight, excerpt, word_wrap and
auto_link to accept options hash [#705 state:resolved] Signed-off-by: Joshua
Peek
---
.../action_view/helpers/active_record_helper.rb | 2 +-
actionpack/lib/action_view/helpers/text_helper.rb | 206 +++++++++++++++------
actionpack/test/template/text_helper_test.rb | 51 ++++-
3 files changed, 191 insertions(+), 68 deletions(-)
diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb
index ff70ce9aae..fce03ff605 100644
--- a/actionpack/lib/action_view/helpers/active_record_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -109,7 +109,7 @@ module ActionView
def error_message_on(object, method, *args)
options = args.extract_options!
unless args.empty?
- ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate' +
+ ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate ' +
'prepend_text, append_text, and css_class arguments', caller)
options[:prepend_text] = args[0] || ''
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 9342b38680..3c9f7230c3 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -34,40 +34,69 @@ module ActionView
end
if RUBY_VERSION < '1.9'
- # If +text+ is longer than +length+, +text+ will be truncated to the length of
- # +length+ (defaults to 30) and the last characters will be replaced with the +truncate_string+
- # (defaults to "...").
+ # Truncates a given +text+ after a given :length if +text+ is longer than :length
+ # (defaults to 30). The last characters will be replaced with the :omission (defaults to "...").
#
# ==== Examples
- # truncate("Once upon a time in a world far far away", 14)
- # # => Once upon a...
#
# truncate("Once upon a time in a world far far away")
# # => Once upon a time in a world f...
#
- # truncate("And they found that many people were sleeping better.", 25, "(clipped)")
+ # truncate("Once upon a time in a world far far away", :length => 14)
+ # # => Once upon a...
+ #
+ # truncate("And they found that many people were sleeping better.", :length => 25, "(clipped)")
# # => And they found that many (clipped)
#
+ # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 15)
+ # # => And they found... (continued)
+ #
+ # You can still use truncate with the old API that accepts the
+ # +length+ as its optional second and the +ellipsis+ as its
+ # optional third parameter:
+ # truncate("Once upon a time in a world far far away", 14)
+ # # => Once upon a time in a world f...
+ #
# truncate("And they found that many people were sleeping better.", 15, "... (continued)")
# # => And they found... (continued)
- def truncate(text, length = 30, truncate_string = "...")
+ def truncate(text, *args)
+ options = args.extract_options!
+ unless args.empty?
+ ActiveSupport::Deprecation.warn('truncate takes an option hash instead of separate ' +
+ 'length and omission arguments', caller)
+
+ options[:length] = args[0] || 30
+ options[:omission] = args[1] || "..."
+ end
+ options.reverse_merge!(:length => 30, :omission => "...")
+
if text
- l = length - truncate_string.chars.length
+ l = options[:length] - options[:omission].chars.length
chars = text.chars
- (chars.length > length ? chars[0...l] + truncate_string : text).to_s
+ (chars.length > options[:length] ? chars[0...l] + options[:omission] : text).to_s
end
end
else
- def truncate(text, length = 30, truncate_string = "...") #:nodoc:
+ def truncate(text, *args) #:nodoc:
+ options = args.extract_options!
+ unless args.empty?
+ ActiveSupport::Deprecation.warn('truncate takes an option hash instead of separate ' +
+ 'length and omission arguments', caller)
+
+ options[:length] = args[0] || 30
+ options[:omission] = args[1] || "..."
+ end
+ options.reverse_merge!(:length => 30, :omission => "...")
+
if text
- l = length - truncate_string.length
- (text.length > length ? text[0...l] + truncate_string : text).to_s
+ l = options[:length].to_i - options[:omission].length
+ (text.length > options[:length].to_i ? text[0...l] + options[:omission] : text).to_s
end
end
end
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
- # a +highlighter+ string. The highlighter can be specialized by passing +highlighter+
+ # a :highlighter string. The highlighter can be specialized by passing :highlighter
# as a single-quoted string with \1 where the phrase is to be inserted (defaults to
# '\1 ')
#
@@ -78,52 +107,75 @@ module ActionView
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
# # => You searched for: ruby, rails, dhh
#
- # highlight('You searched for: rails', ['for', 'rails'], '\1 ')
+ # highlight('You searched for: rails', ['for', 'rails'], :highlighter => '\1 ')
# # => You searched for : rails
#
- # highlight('You searched for: rails', 'rails', "\1 ")
- # # => You searched for: \1')
+ # highlight('You searched for: rails', 'rails', :highlighter => ' \1 ')
+ # # => You searched for: rails
+ #
+ # You can still use highlight with the old API that accepts the
+ # +highlighter+ as its optional third parameter:
+ # highlight('You searched for: rails', 'rails', '\1 ') # => You searched for: rails
+ def highlight(text, phrases, *args)
+ options = args.extract_options!
+ unless args.empty?
+ options[:highlighter] = args[0] || '\1 '
+ end
+ options.reverse_merge!(:highlighter => '\1 ')
+
if text.blank? || phrases.blank?
text
else
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})/i, highlighter)
+ text.gsub(/(#{match})/i, options[:highlighter])
end
end
if RUBY_VERSION < '1.9'
# Extracts an excerpt from +text+ that matches the first instance of +phrase+.
- # The +radius+ expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
- # defined in +radius+ (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
- # then the +excerpt_string+ will be prepended/appended accordingly. The resulting string will be stripped in any case.
- # If the +phrase+ isn't found, nil is returned.
+ # The :radius option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
+ # defined in :radius (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
+ # then the :omission option (which defaults to "...") will be prepended/appended accordingly. The resulting string
+ # will be stripped in any case. If the +phrase+ isn't found, nil is returned.
#
# ==== Examples
- # excerpt('This is an example', 'an', 5)
- # # => "...s is an exam..."
+ # excerpt('This is an example', 'an', :radius => 5)
+ # # => ...s is an exam...
#
- # excerpt('This is an example', 'is', 5)
- # # => "This is a..."
+ # excerpt('This is an example', 'is', :radius => 5)
+ # # => This is a...
#
# excerpt('This is an example', 'is')
- # # => "This is an example"
+ # # => This is an example
#
- # excerpt('This next thing is an example', 'ex', 2)
- # # => "...next..."
+ # excerpt('This next thing is an example', 'ex', :radius => 2)
+ # # => ...next...
#
- # excerpt('This is also an example', 'an', 8, ' ')
- # # => " is also an example"
- def excerpt(text, phrase, radius = 100, excerpt_string = "...")
+ # excerpt('This is also an example', 'an', :radius => 8, :omission => ' ')
+ # # => is also an example
+ #
+ # You can still use excerpt with the old API that accepts the
+ # +radius+ as its optional third and the +ellipsis+ as its
+ # optional forth parameter:
+ # excerpt('This is an example', 'an', 5) # => ...s is an exam...
+ # excerpt('This is also an example', 'an', 8, ' ') # => is also an example
+ def excerpt(text, phrase, *args)
+ options = args.extract_options!
+ unless args.empty?
+ options[:radius] = args[0] || 100
+ options[:omission] = args[1] || "..."
+ end
+ options.reverse_merge!(:radius => 100, :omission => "...")
+
if text && phrase
phrase = Regexp.escape(phrase)
if found_pos = text.chars =~ /(#{phrase})/i
- start_pos = [ found_pos - radius, 0 ].max
- end_pos = [ [ found_pos + phrase.chars.length + radius - 1, 0].max, text.chars.length ].min
+ start_pos = [ found_pos - options[:radius], 0 ].max
+ end_pos = [ [ found_pos + phrase.chars.length + options[:radius] - 1, 0].max, text.chars.length ].min
- prefix = start_pos > 0 ? excerpt_string : ""
- postfix = end_pos < text.chars.length - 1 ? excerpt_string : ""
+ prefix = start_pos > 0 ? options[:omission] : ""
+ postfix = end_pos < text.chars.length - 1 ? options[:omission] : ""
prefix + text.chars[start_pos..end_pos].strip + postfix
else
@@ -132,16 +184,23 @@ module ActionView
end
end
else
- def excerpt(text, phrase, radius = 100, excerpt_string = "...") #:nodoc:
+ def excerpt(text, phrase, *args) #:nodoc:
+ options = args.extract_options!
+ unless args.empty?
+ options[:radius] = args[0] || 100
+ options[:omission] = args[1] || "..."
+ end
+ options.reverse_merge!(:radius => 100, :omission => "...")
+
if text && phrase
phrase = Regexp.escape(phrase)
if found_pos = text =~ /(#{phrase})/i
- start_pos = [ found_pos - radius, 0 ].max
- end_pos = [ [ found_pos + phrase.length + radius - 1, 0].max, text.length ].min
+ start_pos = [ found_pos - options[:radius], 0 ].max
+ end_pos = [ [ found_pos + phrase.length + options[:radius] - 1, 0].max, text.length ].min
- prefix = start_pos > 0 ? excerpt_string : ""
- postfix = end_pos < text.length - 1 ? excerpt_string : ""
+ prefix = start_pos > 0 ? options[:omission] : ""
+ postfix = end_pos < text.length - 1 ? options[:omission] : ""
prefix + text[start_pos..end_pos].strip + postfix
else
@@ -176,20 +235,31 @@ module ActionView
# (which is 80 by default).
#
# ==== Examples
- # word_wrap('Once upon a time', 4)
- # # => Once\nupon\na\ntime
- #
- # word_wrap('Once upon a time', 8)
- # # => Once upon\na time
#
# word_wrap('Once upon a time')
# # => Once upon a time
#
- # word_wrap('Once upon a time', 1)
+ # word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...')
+ # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\n a successor to the throne turned out to be more trouble than anyone could have\n imagined...
+ #
+ # word_wrap('Once upon a time', :line_width => 8)
+ # # => Once upon\na time
+ #
+ # word_wrap('Once upon a time', :line_width => 1)
# # => Once\nupon\na\ntime
- def word_wrap(text, line_width = 80)
+ #
+ # You can still use word_wrap with the old API that accepts the
+ # +line_width+ as its optional second parameter:
+ # word_wrap('Once upon a time', 8) # => Once upon\na time
+ def word_wrap(text, *args)
+ options = args.extract_options!
+ unless args.blank?
+ options[:line_width] = args[0] || 80
+ end
+ options.reverse_merge!(:line_width => 80)
+
text.split("\n").collect do |line|
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
+ line.length > options[:line_width] ? line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip : line
end * "\n"
end
@@ -336,12 +406,32 @@ module ActionView
# # => "Welcome to my new blog at http://www.m... .
# Please e-mail me at me@email.com ."
#
- def auto_link(text, link = :all, href_options = {}, &block)
+ #
+ # You can still use auto_link with the old API that accepts the
+ # +link+ as its optional second parameter and the +html_options+ hash
+ # as its optional third parameter:
+ # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com."
+ # auto_link(post_body, :urls) # => Once upon\na time
+ # # => "Welcome to my new blog at http://www.myblog.com .
+ # Please e-mail me at me@email.com."
+ #
+ # auto_link(post_body, :all, :target => "_blank") # => Once upon\na time
+ # # => "Welcome to my new blog at http://www.myblog.com .
+ # Please e-mail me at me@email.com ."
+ def auto_link(text, *args, &block)#link = :all, href_options = {}, &block)
return '' if text.blank?
- case link
- when :all then auto_link_email_addresses(auto_link_urls(text, href_options, &block), &block)
- when :email_addresses then auto_link_email_addresses(text, &block)
- when :urls then auto_link_urls(text, href_options, &block)
+
+ options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter
+ unless args.empty?
+ options[:link] = args[0] || :all
+ options[:html] = args[1] || {}
+ end
+ options.reverse_merge!(:link => :all, :html => {})
+
+ case options[:link].to_sym
+ when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], &block), &block)
+ when :email_addresses then auto_link_email_addresses(text, &block)
+ when :urls then auto_link_urls(text, options[:html], &block)
end
end
@@ -468,7 +558,7 @@ module ActionView
[-\w]+ # subdomain or domain
(?:\.[-\w]+)* # remaining subdomains or domain
(?::\d+)? # port
- (?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$])))*)* # path
+ (?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))+)?)* # path
(?:\?[\w\+@%&=.;-]+)? # query string
(?:\#[\w\-]*)? # trailing anchor
)
@@ -477,8 +567,8 @@ module ActionView
# Turns all urls into clickable links. If a block is given, each url
# is yielded and the result is used as the link text.
- def auto_link_urls(text, href_options = {})
- extra_options = tag_options(href_options.stringify_keys) || ""
+ def auto_link_urls(text, html_options = {})
+ extra_options = tag_options(html_options.stringify_keys) || ""
text.gsub(AUTO_LINK_RE) do
all, a, b, c, d = $&, $1, $2, $3, $4
if a =~ / 12)
+ assert_equal "Hello Wor...", truncate("Hello World!!", :length => 12)
end
def test_truncate_should_use_default_length_of_30
@@ -44,23 +44,29 @@ class TextHelperTest < ActionView::TestCase
assert_equal str[0...27] + "...", truncate(str)
end
+ def test_truncate_with_options_hash
+ assert_equal "This is a string that wil[...]", truncate("This is a string that will go longer then the default truncate length of 30", :omission => "[...]")
+ assert_equal "Hello W...", truncate("Hello World!", :length => 10)
+ assert_equal "Hello[...]", truncate("Hello World!", :omission => "[...]", :length => 10)
+ end
+
if RUBY_VERSION < '1.9.0'
def test_truncate_multibyte
with_kcode 'none' do
- assert_equal "\354\225\210\353\205\225\355...", truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", 10)
+ assert_equal "\354\225\210\353\205\225\355...", truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", :length => 10)
end
with_kcode 'u' do
assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...",
- truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244", 10)
+ truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244", :length => 10)
end
end
else
def test_truncate_multibyte
assert_equal "\354\225\210\353\205\225\355...",
- truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", 10)
+ truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", :length => 10)
assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'),
- truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), 10)
+ truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), :length => 10)
end
end
@@ -88,7 +94,7 @@ class TextHelperTest < ActionView::TestCase
assert_equal ' ', highlight(' ', 'blank text is returned verbatim')
end
- def test_highlighter_with_regexp
+ def test_highlight_with_regexp
assert_equal(
"This is a beautiful! morning",
highlight("This is a beautiful! morning", "beautiful!")
@@ -105,10 +111,17 @@ class TextHelperTest < ActionView::TestCase
)
end
- def test_highlighting_multiple_phrases_in_one_pass
+ def test_highlight_with_multiple_phrases_in_one_pass
assert_equal %(wow em ), highlight('wow em', %w(wow em), '\1 ')
end
+ def test_highlight_with_options_hash
+ assert_equal(
+ "This is a beautiful morning, but also a beautiful day",
+ highlight("This is a beautiful morning, but also a beautiful day", "beautiful", :highlighter => '\1 ')
+ )
+ end
+
def test_excerpt
assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", 5))
assert_equal("This is a...", excerpt("This is a beautiful morning", "this", 5))
@@ -138,6 +151,16 @@ class TextHelperTest < ActionView::TestCase
assert_equal('...is a beautiful? mor...', excerpt('This is a beautiful? morning', 'beautiful', 5))
end
+ def test_excerpt_with_options_hash
+ assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", :radius => 5))
+ assert_equal("[...]is a beautiful morn[...]", excerpt("This is a beautiful morning", "beautiful", :omission => "[...]",:radius => 5))
+ assert_equal(
+ "This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome tempera[...]",
+ excerpt("This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome temperatures. So what are you gonna do about it?", "very",
+ :omission => "[...]")
+ )
+ end
+
if RUBY_VERSION < '1.9'
def test_excerpt_with_utf8
with_kcode('u') do
@@ -162,6 +185,10 @@ class TextHelperTest < ActionView::TestCase
assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", 15))
end
+ def test_word_wrap_with_options_hash
+ assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", :line_width => 15))
+ end
+
def test_pluralization
assert_equal("1 count", pluralize(1, "count"))
assert_equal("2 counts", pluralize(2, "count"))
@@ -285,7 +312,13 @@ class TextHelperTest < ActionView::TestCase
url = "http://api.rubyonrails.com/Foo.html"
email = "fantabulous@shiznadel.ic"
- assert_equal %(#{url[0...7]}... #{email[0...7]}...
), auto_link("#{url} #{email}
") { |url| truncate(url, 10) }
+ assert_equal %(#{url[0...7]}... #{email[0...7]}...
), auto_link("#{url} #{email}
") { |url| truncate(url, :length => 10) }
+ end
+
+ def test_auto_link_with_options_hash
+ assert_equal 'Welcome to my new blog at . Please e-mail me at me@email.com .',
+ auto_link("Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.",
+ :link => :all, :html => { :class => "menu", :target => "_blank" })
end
def test_cycle_class
--
cgit v1.2.3
From 6e754551254a8cc64e034163f5d0dc155b450388 Mon Sep 17 00:00:00 2001
From: Pratik Naik
Date: Mon, 28 Jul 2008 12:26:59 +0100
Subject: Merge docrails changes
---
actionpack/lib/action_controller/integration.rb | 18 +-
actionpack/lib/action_controller/response.rb | 48 +-
actionpack/lib/action_controller/test_case.rb | 62 +-
actionpack/lib/action_controller/test_process.rb | 9 +-
actionpack/lib/action_controller/url_rewriter.rb | 97 +-
activerecord/lib/active_record/associations.rb | 418 ++++--
activerecord/lib/active_record/base.rb | 29 +-
activerecord/lib/active_record/transactions.rb | 9 +-
.../lib/active_support/core_ext/array/access.rb | 20 +-
.../active_support/core_ext/time/conversions.rb | 1 +
railties/Rakefile | 15 +
.../doc/guides/creating_plugins/acts_as_yaffle.txt | 193 +++
railties/doc/guides/creating_plugins/appendix.txt | 46 +
.../guides/creating_plugins/creating_plugins.txt | 84 ++
.../guides/creating_plugins/custom_generator.txt | 69 +
.../doc/guides/creating_plugins/custom_route.txt | 69 +
.../creating_plugins/migration_generator.txt | 89 ++
.../doc/guides/creating_plugins/odds_and_ends.txt | 122 ++
.../doc/guides/creating_plugins/preparation.txt | 169 +++
.../guides/creating_plugins/string_to_squawk.txt | 103 ++
.../doc/guides/creating_plugins/view_helper.txt | 61 +
railties/doc/guides/icons/README | 5 +
railties/doc/guides/icons/callouts/1.png | Bin 0 -> 329 bytes
railties/doc/guides/icons/callouts/10.png | Bin 0 -> 361 bytes
railties/doc/guides/icons/callouts/11.png | Bin 0 -> 565 bytes
railties/doc/guides/icons/callouts/12.png | Bin 0 -> 617 bytes
railties/doc/guides/icons/callouts/13.png | Bin 0 -> 623 bytes
railties/doc/guides/icons/callouts/14.png | Bin 0 -> 411 bytes
railties/doc/guides/icons/callouts/15.png | Bin 0 -> 640 bytes
railties/doc/guides/icons/callouts/2.png | Bin 0 -> 353 bytes
railties/doc/guides/icons/callouts/3.png | Bin 0 -> 350 bytes
railties/doc/guides/icons/callouts/4.png | Bin 0 -> 345 bytes
railties/doc/guides/icons/callouts/5.png | Bin 0 -> 348 bytes
railties/doc/guides/icons/callouts/6.png | Bin 0 -> 355 bytes
railties/doc/guides/icons/callouts/7.png | Bin 0 -> 344 bytes
railties/doc/guides/icons/callouts/8.png | Bin 0 -> 357 bytes
railties/doc/guides/icons/callouts/9.png | Bin 0 -> 357 bytes
railties/doc/guides/icons/caution.png | Bin 0 -> 2554 bytes
railties/doc/guides/icons/example.png | Bin 0 -> 2354 bytes
railties/doc/guides/icons/home.png | Bin 0 -> 1340 bytes
railties/doc/guides/icons/important.png | Bin 0 -> 2657 bytes
railties/doc/guides/icons/next.png | Bin 0 -> 1302 bytes
railties/doc/guides/icons/note.png | Bin 0 -> 2730 bytes
railties/doc/guides/icons/prev.png | Bin 0 -> 1348 bytes
railties/doc/guides/icons/tip.png | Bin 0 -> 2602 bytes
railties/doc/guides/icons/up.png | Bin 0 -> 1320 bytes
railties/doc/guides/icons/warning.png | Bin 0 -> 2828 bytes
...ating_records_directly_from_form_parameters.txt | 86 ++
.../cross_site_scripting.txt | 64 +
.../securing_rails_applications.txt | 14 +
.../securing_rails_applications/sql_injection.txt | 87 ++
.../testing_rails_applications.txt | 1371 ++++++++++++++++++++
52 files changed, 3179 insertions(+), 179 deletions(-)
create mode 100644 railties/doc/guides/creating_plugins/acts_as_yaffle.txt
create mode 100644 railties/doc/guides/creating_plugins/appendix.txt
create mode 100644 railties/doc/guides/creating_plugins/creating_plugins.txt
create mode 100644 railties/doc/guides/creating_plugins/custom_generator.txt
create mode 100644 railties/doc/guides/creating_plugins/custom_route.txt
create mode 100644 railties/doc/guides/creating_plugins/migration_generator.txt
create mode 100644 railties/doc/guides/creating_plugins/odds_and_ends.txt
create mode 100644 railties/doc/guides/creating_plugins/preparation.txt
create mode 100644 railties/doc/guides/creating_plugins/string_to_squawk.txt
create mode 100644 railties/doc/guides/creating_plugins/view_helper.txt
create mode 100644 railties/doc/guides/icons/README
create mode 100644 railties/doc/guides/icons/callouts/1.png
create mode 100644 railties/doc/guides/icons/callouts/10.png
create mode 100644 railties/doc/guides/icons/callouts/11.png
create mode 100644 railties/doc/guides/icons/callouts/12.png
create mode 100644 railties/doc/guides/icons/callouts/13.png
create mode 100644 railties/doc/guides/icons/callouts/14.png
create mode 100644 railties/doc/guides/icons/callouts/15.png
create mode 100644 railties/doc/guides/icons/callouts/2.png
create mode 100644 railties/doc/guides/icons/callouts/3.png
create mode 100644 railties/doc/guides/icons/callouts/4.png
create mode 100644 railties/doc/guides/icons/callouts/5.png
create mode 100644 railties/doc/guides/icons/callouts/6.png
create mode 100644 railties/doc/guides/icons/callouts/7.png
create mode 100644 railties/doc/guides/icons/callouts/8.png
create mode 100644 railties/doc/guides/icons/callouts/9.png
create mode 100644 railties/doc/guides/icons/caution.png
create mode 100644 railties/doc/guides/icons/example.png
create mode 100644 railties/doc/guides/icons/home.png
create mode 100644 railties/doc/guides/icons/important.png
create mode 100644 railties/doc/guides/icons/next.png
create mode 100644 railties/doc/guides/icons/note.png
create mode 100644 railties/doc/guides/icons/prev.png
create mode 100644 railties/doc/guides/icons/tip.png
create mode 100644 railties/doc/guides/icons/up.png
create mode 100644 railties/doc/guides/icons/warning.png
create mode 100644 railties/doc/guides/securing_rails_applications/creating_records_directly_from_form_parameters.txt
create mode 100644 railties/doc/guides/securing_rails_applications/cross_site_scripting.txt
create mode 100644 railties/doc/guides/securing_rails_applications/securing_rails_applications.txt
create mode 100644 railties/doc/guides/securing_rails_applications/sql_injection.txt
create mode 100644 railties/doc/guides/testing_rails_applications/testing_rails_applications.txt
diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb
index 2a732448f2..43158ea412 100644
--- a/actionpack/lib/action_controller/integration.rb
+++ b/actionpack/lib/action_controller/integration.rb
@@ -165,11 +165,19 @@ module ActionController
status/100 == 3
end
- # Performs a GET request with the given parameters. The parameters may
- # be +nil+, a Hash, or a string that is appropriately encoded
- # (application/x-www-form-urlencoded or multipart/form-data ).
- # The headers should be a hash. The keys will automatically be upcased, with the
- # prefix 'HTTP_' added if needed.
+ # Performs a GET request with the given parameters.
+ #
+ # - +path+: The URI (as a String) on which you want to perform a GET request.
+ # - +parameters+: The HTTP parameters that you want to pass. This may be +nil+,
+ # a Hash, or a String that is appropriately encoded
+ # (application/x-www-form-urlencoded or multipart/form-data ).
+ # - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
+ # automatically be upcased, with the prefix 'HTTP_' added if needed.
+ #
+ # This method returns an AbstractResponse object, which one can use to inspect
+ # the details of the response. Furthermore, if this method was called from an
+ # ActionController::IntegrationTest object, then that object's @response
+ # instance variable will point to the same response object.
#
# You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
# +put+, +delete+, and +head+.
diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb
index 9955532844..da352b6993 100755
--- a/actionpack/lib/action_controller/response.rb
+++ b/actionpack/lib/action_controller/response.rb
@@ -1,19 +1,61 @@
require 'digest/md5'
-module ActionController
- class AbstractResponse #:nodoc:
+module ActionController # :nodoc:
+ # Represents an HTTP response generated by a controller action. One can use an
+ # ActionController::AbstractResponse object to retrieve the current state of the
+ # response, or customize the response. An AbstractResponse object can either
+ # represent a "real" HTTP response (i.e. one that is meant to be sent back to the
+ # web browser) or a test response (i.e. one that is generated from integration
+ # tests). See CgiResponse and TestResponse, respectively.
+ #
+ # AbstractResponse is mostly a Ruby on Rails framework implement detail, and should
+ # never be used directly in controllers. Controllers should use the methods defined
+ # in ActionController::Base instead. For example, if you want to set the HTTP
+ # response's content MIME type, then use ActionControllerBase#headers instead of
+ # AbstractResponse#headers.
+ #
+ # Nevertheless, integration tests may want to inspect controller responses in more
+ # detail, and that's when AbstractResponse can be useful for application developers.
+ # Integration test methods such as ActionController::Integration::Session#get and
+ # ActionController::Integration::Session#post return objects of type TestResponse
+ # (which are of course also of type AbstractResponse).
+ #
+ # For example, the following demo integration "test" prints the body of the
+ # controller response to the console:
+ #
+ # class DemoControllerTest < ActionController::IntegrationTest
+ # def test_print_root_path_to_console
+ # get('/')
+ # puts @response.body
+ # end
+ # end
+ class AbstractResponse
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
attr_accessor :request
- attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
+
+ # The body content (e.g. HTML) of the response, as a String.
+ attr_accessor :body
+ # The headers of the response, as a Hash. It maps header names to header values.
+ attr_accessor :headers
+ attr_accessor :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
def initialize
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
end
+ # Sets the HTTP response's content MIME type. For example, in the controller
+ # you could write this:
+ #
+ # response.content_type = "text/plain"
+ #
+ # If a character set has been defined for this response (see charset=) then
+ # the character set information will also be included in the content type
+ # information.
def content_type=(mime_type)
self.headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
end
+ # Returns the response's content MIME type, or nil if content type has been set.
def content_type
content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0]
content_type.blank? ? nil : content_type
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index c09050c390..3e66947d5f 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -15,23 +15,61 @@ module ActionController
end
end
- # Superclass for Action Controller functional tests. Infers the controller under test from the test class name,
- # and creates @controller, @request, @response instance variables.
+ # Superclass for ActionController functional tests. Functional tests allow you to
+ # test a single controller action per test method. This should not be confused with
+ # integration tests (see ActionController::IntegrationTest), which are more like
+ # "stories" that can involve multiple controllers and mutliple actions (i.e. multiple
+ # different HTTP requests).
#
- # class WidgetsControllerTest < ActionController::TestCase
- # def test_index
- # get :index
+ # == Basic example
+ #
+ # Functional tests are written as follows:
+ # 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
+ # an HTTP request.
+ # 2. Then, one asserts whether the current state is as expected. "State" can be anything:
+ # the controller's HTTP response, the database contents, etc.
+ #
+ # For example:
+ #
+ # class BooksControllerTest < ActionController::TestCase
+ # def test_create
+ # # Simulate a POST response with the given HTTP parameters.
+ # post(:create, :book => { :title => "Love Hina" })
+ #
+ # # Assert that the controller tried to redirect us to
+ # # the created book's URI.
+ # assert_response :found
+ #
+ # # Assert that the controller really put the book in the database.
+ # assert_not_nil Book.find_by_title("Love Hina")
# end
# end
#
- # * @controller - WidgetController.new
- # * @request - ActionController::TestRequest.new
- # * @response - ActionController::TestResponse.new
+ # == Special instance variables
+ #
+ # ActionController::TestCase will also automatically provide the following instance
+ # variables for use in the tests:
+ #
+ # @controller ::
+ # The controller instance that will be tested.
+ # @request ::
+ # An ActionController::TestRequest, representing the current HTTP
+ # request. You can modify this object before sending the HTTP request. For example,
+ # you might want to set some session properties before sending a GET request.
+ # @response ::
+ # An ActionController::TestResponse object, representing the response
+ # of the last HTTP response. In the above example, @response becomes valid
+ # after calling +post+. If the various assert methods are not sufficient, then you
+ # may use this object to inspect the HTTP response in detail.
+ #
+ # (Earlier versions of Rails required each functional test to subclass
+ # Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
#
- # (Earlier versions of Rails required each functional test to subclass Test::Unit::TestCase and define
- # @controller, @request, @response in +setup+.)
+ # == Controller is automatically inferred
#
- # If the controller cannot be inferred from the test class name, you can explicity set it with +tests+.
+ # ActionController::TestCase will automatically infer the controller under test
+ # from the test class name. If the controller cannot be inferred from the test
+ # class name, you can explicity set it with +tests+.
#
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
# tests WidgetController
@@ -103,4 +141,4 @@ module ActionController
@request.remote_addr = '208.77.188.166' # example.com
end
end
-end
\ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index 721592b81f..66675aaa13 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -266,7 +266,13 @@ module ActionController #:nodoc:
end
end
- class TestResponse < AbstractResponse #:nodoc:
+ # Integration test methods such as ActionController::Integration::Session#get
+ # and ActionController::Integration::Session#post return objects of class
+ # TestResponse, which represent the HTTP response results of the requested
+ # controller actions.
+ #
+ # See AbstractResponse for more information on controller response objects.
+ class TestResponse < AbstractResponse
include TestResponseBehavior
end
@@ -348,6 +354,7 @@ module ActionController #:nodoc:
module TestProcess
def self.included(base)
# execute the request simulating a specific HTTP method and set/volley the response
+ # TODO: this should be un-DRY'ed for the sake of API documentation.
%w( get post put delete head ).each do |method|
base.class_eval <<-EOV, __FILE__, __LINE__
def #{method}(action, parameters = nil, session = nil, flash = nil)
diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb
index d0bf6c0bd4..d86e2db67d 100644
--- a/actionpack/lib/action_controller/url_rewriter.rb
+++ b/actionpack/lib/action_controller/url_rewriter.rb
@@ -1,19 +1,96 @@
module ActionController
- # Write URLs from arbitrary places in your codebase, such as your mailers.
+ # In routes.rb one defines URL-to-controller mappings, but the reverse
+ # is also possible: an URL can be generated from one of your routing definitions.
+ # URL generation functionality is centralized in this module.
#
- # Example:
+ # See ActionController::Routing and ActionController::Resources for general
+ # information about routing and routes.rb.
#
- # class MyMailer
- # include ActionController::UrlWriter
- # default_url_options[:host] = 'www.basecamphq.com'
+ # Tip: If you need to generate URLs from your models or some other place,
+ # then ActionController::UrlWriter is what you're looking for. Read on for
+ # an introduction.
#
- # def signup_url(token)
- # url_for(:controller => 'signup', action => 'index', :token => token)
+ # == URL generation from parameters
+ #
+ # As you may know, some functions - such as ActionController::Base#url_for
+ # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
+ # of parameters. For example, you've probably had the chance to write code
+ # like this in one of your views:
+ #
+ # <%= link_to('Click here', :controller => 'users',
+ # :action => 'new', :message => 'Welcome!') %>
+ #
+ # #=> Generates a link to: /users/new?message=Welcome%21
+ #
+ # link_to, and all other functions that require URL generation functionality,
+ # actually use ActionController::UrlWriter under the hood. And in particular,
+ # they use the ActionController::UrlWriter#url_for method. One can generate
+ # the same path as the above example by using the following code:
+ #
+ # include UrlWriter
+ # url_for(:controller => 'users',
+ # :action => 'new',
+ # :message => 'Welcome!',
+ # :only_path => true)
+ # # => "/users/new?message=Welcome%21"
+ #
+ # Notice the :only_path => true part. This is because UrlWriter has no
+ # information about the website hostname that your Rails app is serving. So if you
+ # want to include the hostname as well, then you must also pass the :host
+ # argument:
+ #
+ # include UrlWriter
+ # url_for(:controller => 'users',
+ # :action => 'new',
+ # :message => 'Welcome!',
+ # :host => 'www.example.com') # Changed this.
+ # # => "http://www.example.com/users/new?message=Welcome%21"
+ #
+ # By default, all controllers and views have access to a special version of url_for,
+ # that already knows what the current hostname is. So if you use url_for in your
+ # controllers or your views, then you don't need to explicitly pass the :host
+ # argument.
+ #
+ # For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for.
+ # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for'
+ # in full. However, mailers don't have hostname information, and what's why you'll still
+ # have to specify the :host argument when generating URLs in mailers.
+ #
+ #
+ # == URL generation for named routes
+ #
+ # UrlWriter also allows one to access methods that have been auto-generated from
+ # named routes. For example, suppose that you have a 'users' resource in your
+ # routes.rb :
+ #
+ # map.resources :users
+ #
+ # This generates, among other things, the method users_path . By default,
+ # this method is accessible from your controllers, views and mailers. If you need
+ # to access this auto-generated method from other places (such as a model), then
+ # you can do that in two ways.
+ #
+ # The first way is to include ActionController::UrlWriter in your class:
+ #
+ # class User < ActiveRecord::Base
+ # include ActionController::UrlWriter # !!!
+ #
+ # def name=(value)
+ # write_attribute('name', value)
+ # write_attribute('base_uri', users_path) # !!!
# end
- # end
+ # end
#
- # In addition to providing +url_for+, named routes are also accessible after
- # including UrlWriter.
+ # The second way is to access them through ActionController::UrlWriter.
+ # The autogenerated named routes methods are available as class methods:
+ #
+ # class User < ActiveRecord::Base
+ # def name=(value)
+ # write_attribute('name', value)
+ # path = ActionController::UrlWriter.users_path # !!!
+ # write_attribute('base_uri', path) # !!!
+ # end
+ # end
module UrlWriter
# The default options for urls written by this writer. Typically a :host
# pair is provided.
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index d916275ab9..4e33dfe69f 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -73,6 +73,7 @@ module ActiveRecord
end
end
+ # See ActiveRecord::Associations::ClassMethods for documentation.
module Associations # :nodoc:
def self.included(base)
base.extend(ClassMethods)
@@ -150,6 +151,7 @@ module ActiveRecord
# #others.destroy_all | X | X | X
# #others.find(*args) | X | X | X
# #others.find_first | X | |
+ # #others.exist? | X | X | X
# #others.uniq | X | X | X
# #others.reset | X | X | X
#
@@ -612,31 +614,53 @@ module ActiveRecord
# All of the association macros can be specialized through options. This makes cases more complex than the simple and guessable ones
# possible.
module ClassMethods
- # Adds the following methods for retrieval and query of collections of associated objects:
- # +collection+ is replaced with the symbol passed as the first argument, so
- # has_many :clients would add among others clients.empty? .
- # * collection(force_reload = false) - Returns an array of all the associated objects.
+ # Specifies a one-to-many association. The following methods for retrieval and query of
+ # collections of associated objects will be added:
+ #
+ # [collection(force_reload = false)]
+ # Returns an array of all the associated objects.
# An empty array is returned if none are found.
- # * collection<<(object, ...) - Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
- # * collection.delete(object, ...) - Removes one or more objects from the collection by setting their foreign keys to +NULL+.
+ # [collection<<(object, ...)]
+ # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
+ # [collection.delete(object, ...)]
+ # Removes one or more objects from the collection by setting their foreign keys to +NULL+.
# This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model.
- # * collection=objects - Replaces the collections content by deleting and adding objects as appropriate.
- # * collection_singular_ids - Returns an array of the associated objects' ids
- # * collection_singular_ids=ids - Replace the collection with the objects identified by the primary keys in +ids+
- # * collection.clear - Removes every object from the collection. This destroys the associated objects if they
- # are associated with :dependent => :destroy , deletes them directly from the database if :dependent => :delete_all ,
- # otherwise sets their foreign keys to +NULL+.
- # * collection.empty? - Returns +true+ if there are no associated objects.
- # * collection.size - Returns the number of associated objects.
- # * collection.find - Finds an associated object according to the same rules as Base.find.
- # * collection.build(attributes = {}, ...) - Returns one or more new objects of the collection type that have been instantiated
- # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an
- # associated object already exists, not if it's +nil+!
- # * collection.create(attributes = {}) - Returns a new object of the collection type that has been instantiated
- # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
- # *Note:* This only works if an associated object already exists, not if it's +nil+!
+ # [collection=objects]
+ # Replaces the collections content by deleting and adding objects as appropriate.
+ # [collection_singular_ids]
+ # Returns an array of the associated objects' ids
+ # [collection_singular_ids=ids]
+ # Replace the collection with the objects identified by the primary keys in +ids+
+ # [collection.clear]
+ # Removes every object from the collection. This destroys the associated objects if they
+ # are associated with :dependent => :destroy , deletes them directly from the
+ # database if :dependent => :delete_all , otherwise sets their foreign keys to +NULL+.
+ # [collection.empty?]
+ # Returns +true+ if there are no associated objects.
+ # [collection.size]
+ # Returns the number of associated objects.
+ # [collection.find(...)]
+ # Finds an associated object according to the same rules as ActiveRecord::Base.find.
+ # [collection.exist?(...)]
+ # Checks whether an associated object with the given conditions exists.
+ # Uses the same rules as ActiveRecord::Base.exists?.
+ # [collection.build(attributes = {}, ...)]
+ # Returns one or more new objects of the collection type that have been instantiated
+ # with +attributes+ and linked to this object through a foreign key, but have not yet
+ # been saved. Note: This only works if an associated object already exists, not if
+ # it's +nil+!
+ # [collection.create(attributes = {})]
+ # Returns a new object of the collection type that has been instantiated
+ # with +attributes+, linked to this object through a foreign key, and that has already
+ # been saved (if it passed the validation). Note: This only works if an associated
+ # object already exists, not if it's +nil+!
+ #
+ # (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
+ # has_many :clients would add among others clients.empty? .)
+ #
+ # === Example
#
- # Example: A Firm class declares has_many :clients , which will add:
+ # A Firm class declares has_many :clients , which will add:
# * Firm#clients (similar to Clients.find :all, :conditions => "firm_id = #{id}" )
# * Firm#clients<<
# * Firm#clients.delete
@@ -647,54 +671,77 @@ module ActiveRecord
# * Firm#clients.empty? (similar to firm.clients.size == 0 )
# * Firm#clients.size (similar to Client.count "firm_id = #{id}" )
# * Firm#clients.find (similar to Client.find(id, :conditions => "firm_id = #{id}") )
+ # * Firm#clients.exist?(:name => 'ACME') (similar to Client.exist?(:name => 'ACME', :firm_id => firm.id) )
# * Firm#clients.build (similar to Client.new("firm_id" => id) )
# * Firm#clients.create (similar to c = Client.new("firm_id" => id); c.save; c )
# The declaration can also include an options hash to specialize the behavior of the association.
#
- # Options are:
- # * :class_name - Specify the class name of the association. Use it only if that name can't be inferred
+ # === Supported options
+ # [:class_name]
+ # Specify the class name of the association. Use it only if that name can't be inferred
# from the association name. So has_many :products will by default be linked to the Product class, but
# if the real class name is SpecialProduct, you'll have to specify it with this option.
- # * :conditions - Specify the conditions that the associated objects must meet in order to be included as a +WHERE+
+ # [:conditions]
+ # Specify the conditions that the associated objects must meet in order to be included as a +WHERE+
# SQL fragment, such as price > 5 AND name LIKE 'B%' . Record creations from the association are scoped if a hash
# is used. has_many :posts, :conditions => {:published => true} will create published posts with @blog.posts.create
# or @blog.posts.build .
- # * :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment,
+ # [:order]
+ # Specify the order in which the associated objects are returned as an ORDER BY SQL fragment,
# such as last_name, first_name DESC .
- # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name
+ # [:foreign_key]
+ # Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id"
# as the default :foreign_key .
- # * :primary_key - Specify the method that returns the primary key used for the association. By default this is +id+.
- # * :dependent - If set to :destroy all the associated objects are destroyed
+ # [:primary_key]
+ # Specify the method that returns the primary key used for the association. By default this is +id+.
+ # [:dependent]
+ # If set to :destroy all the associated objects are destroyed
# alongside this object by calling their +destroy+ method. If set to :delete_all all associated
# objects are deleted *without* calling their +destroy+ method. If set to :nullify all associated
# objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. *Warning:* This option is ignored when also using
# the :through option.
- # * :finder_sql - Specify a complete SQL statement to fetch the association. This is a good way to go for complex
+ # [:finder_sql]
+ # Specify a complete SQL statement to fetch the association. This is a good way to go for complex
# associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
- # * :counter_sql - Specify a complete SQL statement to fetch the size of the association. If :finder_sql is
+ # [:counter_sql]
+ # Specify a complete SQL statement to fetch the size of the association. If :finder_sql is
# specified but not :counter_sql , :counter_sql will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM .
- # * :extend - Specify a named module for extending the proxy. See "Association extensions".
- # * :include - Specify second-order associations that should be eager loaded when the collection is loaded.
- # * :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * :limit - An integer determining the limit on the number of rows that should be returned.
- # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
- # * :select - By default, this is * as in SELECT * FROM , but can be changed if you, for example, want to do a join
+ # [:extend]
+ # Specify a named module for extending the proxy. See "Association extensions".
+ # [:include]
+ # Specify second-order associations that should be eager loaded when the collection is loaded.
+ # [:group]
+ # An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
+ # [:limit]
+ # An integer determining the limit on the number of rows that should be returned.
+ # [:offset]
+ # An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
+ # [:select]
+ # By default, this is * as in SELECT * FROM , but can be changed if you, for example, want to do a join
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
- # * :as - Specifies a polymorphic interface (See belongs_to ).
- # * :through - Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key
+ # [:as]
+ # Specifies a polymorphic interface (See belongs_to ).
+ # [:through]
+ # Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key
# are ignored, as the association uses the source reflection. You can only use a :through query through a belongs_to
# or has_many association on the join model.
- # * :source - Specifies the source association name used by has_many :through queries. Only use it if the name cannot be
+ # [:source]
+ # Specifies the source association name used by has_many :through queries. Only use it if the name cannot be
# inferred from the association. has_many :subscribers, :through => :subscriptions will look for either :subscribers or
# :subscriber on Subscription, unless a :source is given.
- # * :source_type - Specifies type of the source association used by has_many :through queries where the source
+ # [:source_type]
+ # Specifies type of the source association used by has_many :through queries where the source
# association is a polymorphic +belongs_to+.
- # * :uniq - If true, duplicates will be omitted from the collection. Useful in conjunction with :through .
- # * :readonly - If true, all the associated objects are readonly through the association.
- # * :validate - If false, don't validate the associated objects when saving the parent object. true by default.
- # * :accessible - Mass assignment is allowed for this assocation (similar to ActiveRecord::Base#attr_accessible ).
- #
+ # [:uniq]
+ # If true, duplicates will be omitted from the collection. Useful in conjunction with :through .
+ # [:readonly]
+ # If true, all the associated objects are readonly through the association.
+ # [:validate]
+ # If false, don't validate the associated objects when saving the parent object. true by default.
+ # [:accessible]
+ # Mass assignment is allowed for this assocation (similar to ActiveRecord::Base#attr_accessible ).
+
# Option examples:
# has_many :comments, :order => "posted_on"
# has_many :comments, :include => :author
@@ -725,58 +772,91 @@ module ActiveRecord
end
end
- # Adds the following methods for retrieval and query of a single associated object:
- # +association+ is replaced with the symbol passed as the first argument, so
- # has_one :manager would add among others manager.nil? .
- # * association(force_reload = false) - Returns the associated object. +nil+ is returned if none is found.
- # * association=(associate) - Assigns the associate object, extracts the primary key, sets it as the foreign key,
+ # Specifies a one-to-one association with another class. This method should only be used
+ # if the other class contains the foreign key. If the current class contains the foreign key,
+ # then you should use +belongs_to+ instead. See also ActiveRecord::Associations::ClassMethods's overview
+ # on when to use has_one and when to use belongs_to.
+ #
+ # The following methods for retrieval and query of a single associated object will be added:
+ #
+ # [association(force_reload = false)]
+ # Returns the associated object. +nil+ is returned if none is found.
+ # [association=(associate)]
+ # Assigns the associate object, extracts the primary key, sets it as the foreign key,
# and saves the associate object.
- # * association.nil? - Returns +true+ if there is no associated object.
- # * build_association(attributes = {}) - Returns a new object of the associated type that has been instantiated
- # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. Note: This ONLY works if
- # an association already exists. It will NOT work if the association is +nil+.
- # * create_association(attributes = {}) - Returns a new object of the associated type that has been instantiated
- # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
+ # [association.nil?]
+ # Returns +true+ if there is no associated object.
+ # [build_association(attributes = {})]
+ # Returns a new object of the associated type that has been instantiated
+ # with +attributes+ and linked to this object through a foreign key, but has not
+ # yet been saved. Note: This ONLY works if an association already exists.
+ # It will NOT work if the association is +nil+.
+ # [create_association(attributes = {})]
+ # Returns a new object of the associated type that has been instantiated
+ # with +attributes+, linked to this object through a foreign key, and that
+ # has already been saved (if it passed the validation).
+ #
+ # (+association+ is replaced with the symbol passed as the first argument, so
+ # has_one :manager would add among others manager.nil? .)
#
- # Example: An Account class declares has_one :beneficiary , which will add:
+ # === Example
+ #
+ # An Account class declares has_one :beneficiary , which will add:
# * Account#beneficiary (similar to Beneficiary.find(:first, :conditions => "account_id = #{id}") )
# * Account#beneficiary=(beneficiary) (similar to beneficiary.account_id = account.id; beneficiary.save )
# * Account#beneficiary.nil?
# * Account#build_beneficiary (similar to Beneficiary.new("account_id" => id) )
# * Account#create_beneficiary (similar to b = Beneficiary.new("account_id" => id); b.save; b )
#
+ # === Options
+ #
# The declaration can also include an options hash to specialize the behavior of the association.
#
# Options are:
- # * :class_name - Specify the class name of the association. Use it only if that name can't be inferred
+ # [:class_name]
+ # Specify the class name of the association. Use it only if that name can't be inferred
# from the association name. So has_one :manager will by default be linked to the Manager class, but
# if the real class name is Person, you'll have to specify it with this option.
- # * :conditions - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
+ # [:conditions]
+ # Specify the conditions that the associated object must meet in order to be included as a +WHERE+
# SQL fragment, such as rank = 5 .
- # * :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment,
+ # [:order]
+ # Specify the order in which the associated objects are returned as an ORDER BY SQL fragment,
# such as last_name, first_name DESC .
- # * :dependent - If set to :destroy , the associated object is destroyed when this object is. If set to
+ # [:dependent]
+ # If set to :destroy , the associated object is destroyed when this object is. If set to
# :delete , the associated object is deleted *without* calling its destroy method. If set to :nullify , the associated
# object's foreign key is set to +NULL+. Also, association is assigned.
- # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name
+ # [:foreign_key]
+ # Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id"
# as the default :foreign_key .
- # * :primary_key - Specify the method that returns the primary key used for the association. By default this is +id+.
- # * :include - Specify second-order associations that should be eager loaded when this object is loaded.
- # * :as - Specifies a polymorphic interface (See belongs_to ).
- # * :select - By default, this is * as in SELECT * FROM , but can be changed if, for example, you want to do a join
+ # [:primary_key]
+ # Specify the method that returns the primary key used for the association. By default this is +id+.
+ # [:include]
+ # Specify second-order associations that should be eager loaded when this object is loaded.
+ # [:as]
+ # Specifies a polymorphic interface (See belongs_to ).
+ # [:select]
+ # By default, this is * as in SELECT * FROM , but can be changed if, for example, you want to do a join
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
- # * :through : Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key
+ # [:through]
+ # Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key
# are ignored, as the association uses the source reflection. You can only use a :through query through a
# has_one or belongs_to association on the join model.
- # * :source - Specifies the source association name used by has_one :through queries. Only use it if the name cannot be
+ # [:source]
+ # Specifies the source association name used by has_one :through queries. Only use it if the name cannot be
# inferred from the association. has_one :favorite, :through => :favorites will look for a
# :favorite on Favorite, unless a :source is given.
- # * :source_type - Specifies type of the source association used by has_one :through queries where the source
+ # [:source_type]
+ # Specifies type of the source association used by has_one :through queries where the source
# association is a polymorphic +belongs_to+.
- # * :readonly - If true, the associated object is readonly through the association.
- # * :validate - If false, don't validate the associated object when saving the parent object. +false+ by default.
- # * :accessible - Mass assignment is allowed for this assocation (similar to ActiveRecord::Base#attr_accessible ).
+ # [:readonly]
+ # If true, the associated object is readonly through the association.
+ # [:validate]
+ # If false, don't validate the associated object when saving the parent object. +false+ by default.
+ # [:accessible]
+ # Mass assignment is allowed for this assocation (similar to ActiveRecord::Base#attr_accessible ).
#
# Option examples:
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
@@ -816,18 +896,34 @@ module ActiveRecord
end
end
- # Adds the following methods for retrieval and query for a single associated object for which this object holds an id:
- # +association+ is replaced with the symbol passed as the first argument, so
- # belongs_to :author would add among others author.nil? .
- # * association(force_reload = false) - Returns the associated object. +nil+ is returned if none is found.
- # * association=(associate) - Assigns the associate object, extracts the primary key, and sets it as the foreign key.
- # * association.nil? - Returns +true+ if there is no associated object.
- # * build_association(attributes = {}) - Returns a new object of the associated type that has been instantiated
+ # Specifies a one-to-one association with another class. This method should only be used
+ # if this class contains the foreign key. If the other class contains the foreign key,
+ # then you should use +has_one+ instead. See also ActiveRecord::Associations::ClassMethods's overview
+ # on when to use +has_one+ and when to use +belongs_to+.
+ #
+ # Methods will be added for retrieval and query for a single associated object, for which
+ # this object holds an id:
+ #
+ # [association(force_reload = false)]
+ # Returns the associated object. +nil+ is returned if none is found.
+ # [association=(associate)]
+ # Assigns the associate object, extracts the primary key, and sets it as the foreign key.
+ # [association.nil?]
+ # Returns +true+ if there is no associated object.
+ # [build_association(attributes = {})]
+ # Returns a new object of the associated type that has been instantiated
# with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
- # * create_association(attributes = {}) - Returns a new object of the associated type that has been instantiated
- # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
+ # [create_association(attributes = {})]
+ # Returns a new object of the associated type that has been instantiated
+ # with +attributes+, linked to this object through a foreign key, and that
+ # has already been saved (if it passed the validation).
+ #
+ # (+association+ is replaced with the symbol passed as the first argument, so
+ # belongs_to :author would add among others author.nil? .)
#
- # Example: A Post class declares belongs_to :author , which will add:
+ # === Example
+ #
+ # A Post class declares belongs_to :author , which will add:
# * Post#author (similar to Author.find(author_id) )
# * Post#author=(author) (similar to post.author_id = author.id )
# * Post#author? (similar to post.author == some_author )
@@ -836,23 +932,30 @@ module ActiveRecord
# * Post#create_author (similar to post.author = Author.new; post.author.save; post.author )
# The declaration can also include an options hash to specialize the behavior of the association.
#
- # Options are:
- # * :class_name - Specify the class name of the association. Use it only if that name can't be inferred
+ # === Options
+ #
+ # [:class_name]
+ # Specify the class name of the association. Use it only if that name can't be inferred
# from the association name. So has_one :author will by default be linked to the Author class, but
# if the real class name is Person, you'll have to specify it with this option.
- # * :conditions - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
+ # [:conditions]
+ # Specify the conditions that the associated object must meet in order to be included as a +WHERE+
# SQL fragment, such as authorized = 1 .
- # * :select - By default, this is * as in SELECT * FROM , but can be changed if, for example, you want to do a join
+ # [:select]
+ # By default, this is * as in SELECT * FROM , but can be changed if, for example, you want to do a join
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
- # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name
+ # [:foreign_key]
+ # Specify the foreign key used for the association. By default this is guessed to be the name
# of the association with an "_id" suffix. So a class that defines a belongs_to :person association will use
# "person_id" as the default :foreign_key . Similarly, belongs_to :favorite_person, :class_name => "Person"
# will use a foreign key of "favorite_person_id".
- # * :dependent - If set to :destroy , the associated object is destroyed when this object is. If set to
+ # [:dependent]
+ # If set to :destroy , the associated object is destroyed when this object is. If set to
# :delete , the associated object is deleted *without* calling its destroy method. This option should not be specified when
# belongs_to is used in conjunction with a has_many relationship on another class because of the potential to leave
# orphaned records behind.
- # * :counter_cache - Caches the number of belonging objects on the associate class through the use of +increment_counter+
+ # [:counter_cache]
+ # Caches the number of belonging objects on the associate class through the use of +increment_counter+
# and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's
# destroyed. This requires that a column named #{table_name}_count (such as +comments_count+ for a belonging Comment class)
# is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing
@@ -860,13 +963,18 @@ module ActiveRecord
# When creating a counter cache column, the database statement or migration must specify a default value of 0 , failing to do
# this results in a counter with +NULL+ value, which will never increment.
# Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+.
- # * :include - Specify second-order associations that should be eager loaded when this object is loaded.
- # * :polymorphic - Specify this association is a polymorphic association by passing +true+.
+ # [:include]
+ # Specify second-order associations that should be eager loaded when this object is loaded.
+ # [:polymorphic]
+ # Specify this association is a polymorphic association by passing +true+.
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
# to the +attr_readonly+ list in the associated classes (e.g. class Post; attr_readonly :comments_count; end ).
- # * :readonly - If true, the associated object is readonly through the association.
- # * :validate - If false, don't validate the associated objects when saving the parent object. +false+ by default.
- # * :accessible - Mass assignment is allowed for this assocation (similar to ActiveRecord::Base#attr_accessible ).
+ # [:readonly]
+ # If true, the associated object is readonly through the association.
+ # [:validate]
+ # If false, don't validate the associated objects when saving the parent object. +false+ by default.
+ # [:accessible]
+ # Mass assignment is allowed for this assocation (similar to ActiveRecord::Base#attr_accessible ).
#
# Option examples:
# belongs_to :firm, :foreign_key => "client_of"
@@ -952,8 +1060,9 @@ module ActiveRecord
configure_dependency_for_belongs_to(reflection)
end
- # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
- # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
+ # Specifies a many-to-many relationship with another class. This associates two classes via an
+ # intermediate join table. Unless the join table is explicitly specified as an option, it is
+ # guessed using the lexical order of the class names. So a join between Developer and Project
# will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence
# is calculated using the < operator for String. This means that if the strings are of different lengths,
# and the strings are equal when compared up to the shortest length, then the longer string is considered of higher
@@ -968,28 +1077,48 @@ module ActiveRecord
# associations with attributes to a real join model (see introduction).
#
# Adds the following methods for retrieval and query:
- # +collection+ is replaced with the symbol passed as the first argument, so
- # has_and_belongs_to_many :categories would add among others categories.empty? .
- # * collection(force_reload = false) - Returns an array of all the associated objects.
+ #
+ # [collection(force_reload = false)]
+ # Returns an array of all the associated objects.
# An empty array is returned if none are found.
- # * collection<<(object, ...) - Adds one or more objects to the collection by creating associations in the join table
+ # [collection<<(object, ...)]
+ # Adds one or more objects to the collection by creating associations in the join table
# (collection.push and collection.concat are aliases to this method).
- # * collection.delete(object, ...) - Removes one or more objects from the collection by removing their associations from the join table.
+ # [collection.delete(object, ...)]
+ # Removes one or more objects from the collection by removing their associations from the join table.
# This does not destroy the objects.
- # * collection=objects - Replaces the collection's content by deleting and adding objects as appropriate.
- # * collection_singular_ids - Returns an array of the associated objects' ids.
- # * collection_singular_ids=ids - Replace the collection by the objects identified by the primary keys in +ids+.
- # * collection.clear - Removes every object from the collection. This does not destroy the objects.
- # * collection.empty? - Returns +true+ if there are no associated objects.
- # * collection.size - Returns the number of associated objects.
- # * collection.find(id) - Finds an associated object responding to the +id+ and that
+ # [collection=objects]
+ # Replaces the collection's content by deleting and adding objects as appropriate.
+ # [collection_singular_ids]
+ # Returns an array of the associated objects' ids.
+ # [collection_singular_ids=ids]
+ # Replace the collection by the objects identified by the primary keys in +ids+.
+ # [collection.clear]
+ # Removes every object from the collection. This does not destroy the objects.
+ # [collection.empty?]
+ # Returns +true+ if there are no associated objects.
+ # [collection.size]
+ # Returns the number of associated objects.
+ # [collection.find(id)]
+ # Finds an associated object responding to the +id+ and that
# meets the condition that it has to be associated with this object.
- # * collection.build(attributes = {}) - Returns a new object of the collection type that has been instantiated
+ # Uses the same rules as ActiveRecord::Base.find.
+ # [collection.exist?(...)]
+ # Checks whether an associated object with the given conditions exists.
+ # Uses the same rules as ActiveRecord::Base.exists?.
+ # [collection.build(attributes = {})]
+ # Returns a new object of the collection type that has been instantiated
# with +attributes+ and linked to this object through the join table, but has not yet been saved.
- # * collection.create(attributes = {}) - Returns a new object of the collection type that has been instantiated
+ # [collection.create(attributes = {})]
+ # Returns a new object of the collection type that has been instantiated
# with +attributes+, linked to this object through the join table, and that has already been saved (if it passed the validation).
#
- # Example: A Developer class declares has_and_belongs_to_many :projects , which will add:
+ # (+collection+ is replaced with the symbol passed as the first argument, so
+ # has_and_belongs_to_many :categories would add among others categories.empty? .)
+ #
+ # === Example
+ #
+ # A Developer class declares has_and_belongs_to_many :projects , which will add:
# * Developer#projects
# * Developer#projects<<
# * Developer#projects.delete
@@ -1000,45 +1129,66 @@ module ActiveRecord
# * Developer#projects.empty?
# * Developer#projects.size
# * Developer#projects.find(id)
+ # * Developer#clients.exist?(...)
# * Developer#projects.build (similar to Project.new("project_id" => id) )
# * Developer#projects.create (similar to c = Project.new("project_id" => id); c.save; c )
# The declaration may include an options hash to specialize the behavior of the association.
#
- # Options are:
- # * :class_name - Specify the class name of the association. Use it only if that name can't be inferred
+ # === Options
+ #
+ # [:class_name]
+ # Specify the class name of the association. Use it only if that name can't be inferred
# from the association name. So has_and_belongs_to_many :projects will by default be linked to the
# Project class, but if the real class name is SuperProject, you'll have to specify it with this option.
- # * :join_table - Specify the name of the join table if the default based on lexical order isn't what you want.
- # WARNING: If you're overwriting the table name of either class, the +table_name+ method MUST be declared underneath any
- # +has_and_belongs_to_many+ declaration in order to work.
- # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name
+ # [:join_table]
+ # Specify the name of the join table if the default based on lexical order isn't what you want.
+ # WARNING: If you're overwriting the table name of either class, the +table_name+ method
+ # MUST be declared underneath any +has_and_belongs_to_many+ declaration in order to work.
+ # [:foreign_key]
+ # Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_and_belongs_to_many+ association
# will use "person_id" as the default :foreign_key .
- # * :association_foreign_key - Specify the association foreign key used for the association. By default this is
+ # [:association_foreign_key]
+ # Specify the association foreign key used for the association. By default this is
# guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is Project,
# the +has_and_belongs_to_many+ association will use "project_id" as the default :association_foreign_key .
- # * :conditions - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
+ # [:conditions]
+ # Specify the conditions that the associated object must meet in order to be included as a +WHERE+
# SQL fragment, such as authorized = 1 . Record creations from the association are scoped if a hash is used.
# has_many :posts, :conditions => {:published => true} will create published posts with @blog.posts.create
# or @blog.posts.build .
- # * :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment,
+ # [:order]
+ # Specify the order in which the associated objects are returned as an ORDER BY SQL fragment,
# such as last_name, first_name DESC
- # * :uniq - If true, duplicate associated objects will be ignored by accessors and query methods.
- # * :finder_sql - Overwrite the default generated SQL statement used to fetch the association with a manual statement
- # * :delete_sql - Overwrite the default generated SQL statement used to remove links between the associated
+ # [:uniq]
+ # If true, duplicate associated objects will be ignored by accessors and query methods.
+ # [:finder_sql]
+ # Overwrite the default generated SQL statement used to fetch the association with a manual statement
+ # [:delete_sql]
+ # Overwrite the default generated SQL statement used to remove links between the associated
# classes with a manual statement.
- # * :insert_sql - Overwrite the default generated SQL statement used to add links between the associated classes
+ # [:insert_sql]
+ # Overwrite the default generated SQL statement used to add links between the associated classes
# with a manual statement.
- # * :extend - Anonymous module for extending the proxy, see "Association extensions".
- # * :include - Specify second-order associations that should be eager loaded when the collection is loaded.
- # * :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * :limit - An integer determining the limit on the number of rows that should be returned.
- # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
- # * :select - By default, this is * as in SELECT * FROM , but can be changed if, for example, you want to do a join
+ # [:extend]
+ # Anonymous module for extending the proxy, see "Association extensions".
+ # [:include]
+ # Specify second-order associations that should be eager loaded when the collection is loaded.
+ # [:group]
+ # An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
+ # [:limit]
+ # An integer determining the limit on the number of rows that should be returned.
+ # [:offset]
+ # An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
+ # [:select]
+ # By default, this is * as in SELECT * FROM , but can be changed if, for example, you want to do a join
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
- # * :readonly - If true, all the associated objects are readonly through the association.
- # * :validate - If false, don't validate the associated objects when saving the parent object. +true+ by default.
- # * :accessible - Mass assignment is allowed for this assocation (similar to ActiveRecord::Base#attr_accessible ).
+ # [:readonly]
+ # If true, all the associated objects are readonly through the association.
+ # [:validate]
+ # If false, don't validate the associated objects when saving the parent object. +true+ by default.
+ # [:accessible<]
+ # Mass assignment is allowed for this assocation (similar to ActiveRecord::Base#attr_accessible ).
#
# Option examples:
# has_and_belongs_to_many :projects
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 4f5d72a0be..9cb64223e2 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -83,8 +83,33 @@ module ActiveRecord #:nodoc:
class ReadOnlyRecord < ActiveRecordError
end
- # Used by Active Record transaction mechanism to distinguish rollback from other exceptional situations.
- # You can use it to roll your transaction back explicitly in the block passed to +transaction+ method.
+ # ActiveRecord::Transactions::ClassMethods.transaction uses this exception
+ # to distinguish a deliberate rollback from other exceptional situations.
+ # Normally, raising an exception will cause the +transaction+ method to rollback
+ # the database transaction *and* pass on the exception. But if you raise an
+ # ActiveRecord::Rollback exception, then the database transaction will be rolled back,
+ # without passing on the exception.
+ #
+ # For example, you could do this in your controller to rollback a transaction:
+ #
+ # class BooksController < ActionController::Base
+ # def create
+ # Book.transaction do
+ # book = Book.new(params[:book])
+ # book.save!
+ # if today_is_friday?
+ # # The system must fail on Friday so that our support department
+ # # won't be out of job. We silently rollback this transaction
+ # # without telling the user.
+ # raise ActiveRecord::Rollback, "Call tech support!"
+ # end
+ # end
+ # # ActiveRecord::Rollback is the only exception that won't be passed on
+ # # by ActiveRecord::Base.transaction, so this line will still be reached
+ # # even on Friday.
+ # redirect_to root_url
+ # end
+ # end
class Rollback < ActiveRecordError
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 354a6c83a2..0531afbb52 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -66,12 +66,15 @@ module ActiveRecord
# will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
# depends on or you can raise exceptions in the callbacks to rollback.
#
- # == Exception handling
+ # == Exception handling and rolling back
#
# Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you
- # should be ready to catch those in your application code. One exception is the ActiveRecord::Rollback exception, which will
- # trigger a ROLLBACK when raised, but not be re-raised by the transaction block.
+ # should be ready to catch those in your application code.
+ #
+ # One exception is the ActiveRecord::Rollback exception, which will trigger a ROLLBACK when raised,
+ # but not be re-raised by the transaction block.
module ClassMethods
+ # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
def transaction(&block)
connection.increment_open_transactions
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 4ac95efdc7..779ca40aea 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -8,6 +8,7 @@ module ActiveSupport #:nodoc:
# %w( a b c d ).from(0) # => %w( a b c d )
# %w( a b c d ).from(2) # => %w( c d )
# %w( a b c d ).from(10) # => nil
+ # %w().from(0) # => nil
def from(position)
self[position..-1]
end
@@ -17,51 +18,52 @@ module ActiveSupport #:nodoc:
# %w( a b c d ).to(0) # => %w( a )
# %w( a b c d ).to(2) # => %w( a b c )
# %w( a b c d ).to(10) # => %w( a b c d )
+ # %w().to(0) # => %w()
def to(position)
self[0..position]
end
- # Equal to self[1]
+ # Equals to self[1] .
def second
self[1]
end
- # Equal to self[2]
+ # Equals to self[2] .
def third
self[2]
end
- # Equal to self[3]
+ # Equals to self[3] .
def fourth
self[3]
end
- # Equal to self[4]
+ # Equals to self[4] .
def fifth
self[4]
end
- # Equal to self[5]
+ # Equals to self[5] .
def sixth
self[5]
end
- # Equal to self[6]
+ # Equals to self[6] .
def seventh
self[6]
end
- # Equal to self[7]
+ # Equals to self[7] .
def eighth
self[7]
end
- # Equal to self[8]
+ # Equals to self[8] .
def ninth
self[8]
end
- # Equal to self[9]
+ # Equals to self[9] .
def tenth
self[9]
end
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index 9054008309..f42be46770 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -30,6 +30,7 @@ module ActiveSupport #:nodoc:
# time.to_s(:time) # => "06:10:17"
#
# time.to_formatted_s(:db) # => "2007-01-18 06:10:17"
+ # time.to_formatted_s(:number) # => "20070118061017"
# time.to_formatted_s(:short) # => "18 Jan 06:10"
# time.to_formatted_s(:long) # => "January 18, 2007 06:10"
# time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10"
diff --git a/railties/Rakefile b/railties/Rakefile
index f64b60b0dd..174c85b59a 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -272,6 +272,21 @@ Rake::RDocTask.new { |rdoc|
rdoc.rdoc_files.include('lib/commands/**/*.rb')
}
+guides = ['securing_rails_applications', 'testing_rails_applications', 'creating_plugins']
+guides_html_files = []
+guides.each do |guide_name|
+ input = "doc/guides/#{guide_name}/#{guide_name}.txt"
+ output = "doc/guides/#{guide_name}/#{guide_name}.html"
+ guides_html_files << output
+ file output => Dir["doc/guides/#{guide_name}/*.txt"] do
+ sh "mizuho", input, "--template", "manualsonrails", "--multi-page",
+ "--icons-dir", "../icons"
+ end
+end
+
+desc "Generate HTML output for the guides"
+task :generate_guides => guides_html_files
+
# Generate GEM ----------------------------------------------------------------------------
task :copy_gem_environment do
diff --git a/railties/doc/guides/creating_plugins/acts_as_yaffle.txt b/railties/doc/guides/creating_plugins/acts_as_yaffle.txt
new file mode 100644
index 0000000000..12d40deb18
--- /dev/null
+++ b/railties/doc/guides/creating_plugins/acts_as_yaffle.txt
@@ -0,0 +1,193 @@
+== Add an `acts_as_yaffle` method to ActiveRecord ==
+
+A common pattern in plugins is to add a method called `acts_as_something` to models. In this case, you want to write a method called `acts_as_yaffle` that adds a `squawk` method to your models.
+
+To keep things clean, create a new test file called 'acts_as_yaffle_test.rb' in your plugin's test directory and require your test helper.
+
+[source, ruby]
+------------------------------------------------------
+# File: vendor/plugins/yaffle/test/acts_as_yaffle_test.rb
+
+require File.dirname(__FILE__) + '/test_helper.rb'
+
+class Hickwall < ActiveRecord::Base
+ acts_as_yaffle
+end
+
+class ActsAsYaffleTest < Test::Unit::TestCase
+end
+------------------------------------------------------
+
+[source, ruby]
+------------------------------------------------------
+# File: vendor/plugins/lib/acts_as_yaffle.rb
+
+module Yaffle
+end
+------------------------------------------------------
+
+One of the most common plugin patterns for `acts_as_yaffle` plugins is to structure your file like so:
+
+[source, ruby]
+------------------------------------------------------
+module Yaffle
+ def self.included(base)
+ base.send :extend, ClassMethods
+ end
+
+ module ClassMethods
+ # any method placed here will apply to classes, like Hickwall
+ def acts_as_something
+ send :include, InstanceMethods
+ end
+ end
+
+ module InstanceMethods
+ # any method placed here will apply to instaces, like @hickwall
+ end
+end
+------------------------------------------------------
+
+With structure you can easily separate the methods that will be used for the class (like `Hickwall.some_method`) and the instance (like `@hickwell.some_method`).
+
+Let's add class method named `acts_as_yaffle` - testing it out first. You already defined the ActiveRecord models in your test helper, so if you run tests now they will fail.
+
+Back in your `acts_as_yaffle` file, update ClassMethods like so:
+
+[source, ruby]
+------------------------------------------------------
+module ClassMethods
+ def acts_as_yaffle(options = {})
+ send :include, InstanceMethods
+ end
+end
+------------------------------------------------------
+
+Now that test should pass. Since your plugin is going to work with field names, you need to allow people to define the field names, in case there is a naming conflict. You can write a few simple tests for this:
+
+[source, ruby]
+------------------------------------------------------
+# File: vendor/plugins/yaffle/test/acts_as_yaffle_test.rb
+
+require File.dirname(__FILE__) + '/test_helper.rb'
+
+class ActsAsYaffleTest < Test::Unit::TestCase
+ def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
+ assert_equal "last_squawk", Hickwall.yaffle_text_field
+ end
+
+ def test_a_hickwalls_yaffle_date_field_should_be_last_squawked_at
+ assert_equal "last_squawked_at", Hickwall.yaffle_date_field
+ end
+
+ def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
+ assert_equal "last_tweet", Wickwall.yaffle_text_field
+ end
+
+ def test_a_wickwalls_yaffle_date_field_should_be_last_tweeted_at
+ assert_equal "last_tweeted_at", Wickwall.yaffle_date_field
+ end
+end
+------------------------------------------------------
+
+To make these tests pass, you could modify your `acts_as_yaffle` file like so:
+
+[source, ruby]
+------------------------------------------------------
+# File: vendor/plugins/yaffle/lib/acts_as_yaffle.rb
+
+module Yaffle
+ def self.included(base)
+ base.send :extend, ClassMethods
+ end
+
+ module ClassMethods
+ def acts_as_yaffle(options = {})
+ cattr_accessor :yaffle_text_field, :yaffle_date_field
+ self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
+ self.yaffle_date_field = (options[:yaffle_date_field] || :last_squawked_at).to_s
+ send :include, InstanceMethods
+ end
+ end
+
+ module InstanceMethods
+ end
+end
+------------------------------------------------------
+
+Now you can add tests for the instance methods, and the instance method itself:
+
+[source, ruby]
+------------------------------------------------------
+# File: vendor/plugins/yaffle/test/acts_as_yaffle_test.rb
+
+require File.dirname(__FILE__) + '/test_helper.rb'
+
+class ActsAsYaffleTest < Test::Unit::TestCase
+
+ def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
+ assert_equal "last_squawk", Hickwall.yaffle_text_field
+ end
+ def test_a_hickwalls_yaffle_date_field_should_be_last_squawked_at
+ assert_equal "last_squawked_at", Hickwall.yaffle_date_field
+ end
+
+ def test_a_wickwalls_yaffle_text_field_should_be_last_squawk
+ assert_equal "last_tweet", Wickwall.yaffle_text_field
+ end
+ def test_a_wickwalls_yaffle_date_field_should_be_last_squawked_at
+ assert_equal "last_tweeted_at", Wickwall.yaffle_date_field
+ end
+
+ def test_hickwalls_squawk_should_populate_last_squawk
+ hickwall = Hickwall.new
+ hickwall.squawk("Hello World")
+ assert_equal "squawk! Hello World", hickwall.last_squawk
+ end
+ def test_hickwalls_squawk_should_populate_last_squawked_at
+ hickwall = Hickwall.new
+ hickwall.squawk("Hello World")
+ assert_equal Date.today, hickwall.last_squawked_at
+ end
+
+ def test_wickwalls_squawk_should_populate_last_tweet
+ wickwall = Wickwall.new
+ wickwall.squawk("Hello World")
+ assert_equal "squawk! Hello World", wickwall.last_tweet
+ end
+ def test_wickwalls_squawk_should_populate_last_tweeted_at
+ wickwall = Wickwall.new
+ wickwall.squawk("Hello World")
+ assert_equal Date.today, wickwall.last_tweeted_at
+ end
+end
+------------------------------------------------------
+
+[source, ruby]
+------------------------------------------------------
+# File: vendor/plugins/yaffle/lib/acts_as_yaffle.rb
+
+module Yaffle
+ def self.included(base)
+ base.send :extend, ClassMethods
+ end
+
+ module ClassMethods
+ def acts_as_yaffle(options = {})
+ cattr_accessor :yaffle_text_field, :yaffle_date_field
+ self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
+ self.yaffle_date_field = (options[:yaffle_date_field] || :last_squawked_at).to_s
+ send :include, InstanceMethods
+ end
+ end
+
+ module InstanceMethods
+ def squawk(string)
+ write_attribute(self.class.yaffle_text_field, string.to_squawk)
+ write_attribute(self.class.yaffle_date_field, Date.today)
+ end
+ end
+end
+------------------------------------------------------
+
+Note the use of `write_attribute` to write to the field in model.
diff --git a/railties/doc/guides/creating_plugins/appendix.txt b/railties/doc/guides/creating_plugins/appendix.txt
new file mode 100644
index 0000000000..a78890ccd5
--- /dev/null
+++ b/railties/doc/guides/creating_plugins/appendix.txt
@@ -0,0 +1,46 @@
+== Appendix ==
+
+=== References ===
+
+ * http://nubyonrails.com/articles/the-complete-guide-to-rails-plugins-part-i
+ * http://nubyonrails.com/articles/2006/05/09/the-complete-guide-to-rails-plugins-part-ii
+ * http://github.com/technoweenie/attachment_fu/tree/master
+ * http://daddy.platte.name/2007/05/rails-plugins-keep-initrb-thin.html
+
+=== Final plugin directory structure ===
+
+The final plugin should have a directory structure that looks something like this:
+
+------------------------------------------------
+ |-- MIT-LICENSE
+ |-- README
+ |-- Rakefile
+ |-- generators
+ | `-- yaffle
+ | |-- USAGE
+ | |-- templates
+ | | `-- definition.txt
+ | `-- yaffle_generator.rb
+ |-- init.rb
+ |-- install.rb
+ |-- lib
+ | |-- acts_as_yaffle.rb
+ | |-- commands.rb
+ | |-- core_ext.rb
+ | |-- routing.rb
+ | `-- view_helpers.rb
+ |-- tasks
+ | `-- yaffle_tasks.rake
+ |-- test
+ | |-- acts_as_yaffle_test.rb
+ | |-- core_ext_test.rb
+ | |-- database.yml
+ | |-- debug.log
+ | |-- routing_test.rb
+ | |-- schema.rb
+ | |-- test_helper.rb
+ | `-- view_helpers_test.rb
+ |-- uninstall.rb
+ `-- yaffle_plugin.sqlite3.db
+------------------------------------------------
+
diff --git a/railties/doc/guides/creating_plugins/creating_plugins.txt b/railties/doc/guides/creating_plugins/creating_plugins.txt
new file mode 100644
index 0000000000..f2ed6ed8bb
--- /dev/null
+++ b/railties/doc/guides/creating_plugins/creating_plugins.txt
@@ -0,0 +1,84 @@
+The Basics of Creating Rails Plugins
+====================================
+
+Pretend for a moment that you are an avid bird watcher. Your favorite bird is the Yaffle, and you want to create a plugin that allows other developers to share in the Yaffle goodness.
+
+In this tutorial you will learn how to create a plugin that includes:
+
+ * Core Extensions - extending String with a `to_squawk` method:
++
+[source, ruby]
+-------------------------------------------
+# Anywhere
+"hello!".to_squawk # => "squawk! hello!"
+-------------------------------------------
+
+* An `acts_as_yaffle` method for ActiveRecord models that adds a `squawk` method:
++
+[source, ruby]
+-------------------------------------------
+class Hickwall < ActiveRecord::Base
+ acts_as_yaffle :yaffle_text_field => :last_sang_at
+end
+
+Hickwall.new.squawk("Hello World")
+-------------------------------------------
+
+* A view helper that will print out squawking info:
++
+[source, ruby]
+-------------------------------------------
+squawk_info_for(@hickwall)
+-------------------------------------------
+
+* A generator that creates a migration to add squawk columns to a model:
++
+-------------------------------------------
+script/generate yaffle hickwall
+-------------------------------------------
+
+* A custom generator command:
++
+[source, ruby]
+-------------------------------------------
+class YaffleGenerator < Rails::Generator::NamedBase
+ def manifest
+ m.yaffle_definition
+ end
+end
+-------------------------------------------
+
+* A custom route method:
++
+[source, ruby]
+-------------------------------------------
+ActionController::Routing::Routes.draw do |map|
+ map.yaffles
+end
+-------------------------------------------
+
+In addition you'll learn how to:
+
+ * test your plugins.
+ * work with 'init.rb', how to store model, views, controllers, helpers and even other plugins in your plugins.
+ * create documentation for your plugin.
+ * write custom Rake tasks in your plugin.
+
+
+include::preparation.txt[]
+
+include::string_to_squawk.txt[]
+
+include::acts_as_yaffle.txt[]
+
+include::view_helper.txt[]
+
+include::migration_generator.txt[]
+
+include::custom_generator.txt[]
+
+include::custom_route.txt[]
+
+include::odds_and_ends.txt[]
+
+include::appendix.txt[]
diff --git a/railties/doc/guides/creating_plugins/custom_generator.txt b/railties/doc/guides/creating_plugins/custom_generator.txt
new file mode 100644
index 0000000000..6d9613ea01
--- /dev/null
+++ b/railties/doc/guides/creating_plugins/custom_generator.txt
@@ -0,0 +1,69 @@
+== Add a custom generator command ==
+
+You may have noticed above that you can used one of the built-in rails migration commands `m.migration_template`. You can create your own commands for these, using the following steps:
+
+ 1. Add the require and hook statements to init.rb.
+ 2. Create the commands - creating 3 sets, Create, Destroy, List.
+ 3. Add the method to your generator.
+
+Working with the internals of generators is beyond the scope of this tutorial, but here is a basic example:
+
+[source, ruby]
+-----------------------------------------------------------
+# File: vendor/plugins/yaffle/init.rb
+require "commands"
+Rails::Generator::Commands::Create.send :include, Yaffle::Generator::Commands::Create
+Rails::Generator::Commands::Destroy.send :include, Yaffle::Generator::Commands::Destroy
+Rails::Generator::Commands::List.send :include, Yaffle::Generator::Commands::List
+-----------------------------------------------------------
+
+[source, ruby]
+-----------------------------------------------------------
+# File: vendor/plugins/yaffle/lib/commands.rb
+
+require 'rails_generator'
+require 'rails_generator/commands'
+
+module Yaffle #:nodoc:
+ module Generator #:nodoc:
+ module Commands #:nodoc:
+ module Create
+ def yaffle_definition
+ file("definition.txt", "definition.txt")
+ end
+ end
+
+ module Destroy
+ def yaffle_definition
+ file("definition.txt", "definition.txt")
+ end
+ end
+
+ module List
+ def yaffle_definition
+ file("definition.txt", "definition.txt")
+ end
+ end
+ end
+ end
+end
+-----------------------------------------------------------
+
+-----------------------------------------------------------
+# File: vendor/plugins/yaffle/generators/yaffle/templates/definition.txt
+
+Yaffle is a bird
+-----------------------------------------------------------
+
+[source, ruby]
+-----------------------------------------------------------
+# File: vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb
+
+class YaffleGenerator < Rails::Generator::NamedBase
+ def manifest
+ m.yaffle_definition
+ end
+end
+-----------------------------------------------------------
+
+This example just uses the built-in "file" method, but you could do anything that Ruby allows.
diff --git a/railties/doc/guides/creating_plugins/custom_route.txt b/railties/doc/guides/creating_plugins/custom_route.txt
new file mode 100644
index 0000000000..7e399247ee
--- /dev/null
+++ b/railties/doc/guides/creating_plugins/custom_route.txt
@@ -0,0 +1,69 @@
+== Add a Custom Route ==
+
+Testing routes in plugins can be complex, especially if the controllers are also in the plugin itself. Jamis Buck showed a great example of this in http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2.
+
+[source, ruby]
+--------------------------------------------------------
+# File: vendor/plugins/yaffle/test/routing_test.rb
+
+require "#{File.dirname(__FILE__)}/test_helper"
+
+class RoutingTest < Test::Unit::TestCase
+
+ def setup
+ ActionController::Routing::Routes.draw do |map|
+ map.yaffles
+ end
+ end
+
+ def test_yaffles_route
+ assert_recognition :get, "/yaffles", :controller => "yaffles_controller", :action => "index"
+ end
+
+ private
+
+ # yes, I know about assert_recognizes, but it has proven problematic to
+ # use in these tests, since it uses RouteSet#recognize (which actually
+ # tries to instantiate the controller) and because it uses an awkward
+ # parameter order.
+ def assert_recognition(method, path, options)
+ result = ActionController::Routing::Routes.recognize_path(path, :method => method)
+ assert_equal options, result
+ end
+end
+--------------------------------------------------------
+
+[source, ruby]
+--------------------------------------------------------
+# File: vendor/plugins/yaffle/init.rb
+
+require "routing"
+ActionController::Routing::RouteSet::Mapper.send :include, Yaffle::Routing::MapperExtensions
+--------------------------------------------------------
+
+[source, ruby]
+--------------------------------------------------------
+# File: vendor/plugins/yaffle/lib/routing.rb
+
+module Yaffle #:nodoc:
+ module Routing #:nodoc:
+ module MapperExtensions
+ def yaffles
+ @set.add_route("/yaffles", {:controller => "yaffles_controller", :action => "index"})
+ end
+ end
+ end
+end
+--------------------------------------------------------
+
+[source, ruby]
+--------------------------------------------------------
+# File: config/routes.rb
+
+ActionController::Routing::Routes.draw do |map|
+ ...
+ map.yaffles
+end
+--------------------------------------------------------
+
+You can also see if your routes work by running `rake routes` from your app directory.
diff --git a/railties/doc/guides/creating_plugins/migration_generator.txt b/railties/doc/guides/creating_plugins/migration_generator.txt
new file mode 100644
index 0000000000..598a0c8437
--- /dev/null
+++ b/railties/doc/guides/creating_plugins/migration_generator.txt
@@ -0,0 +1,89 @@
+== Create a migration generator ==
+
+When you created the plugin above, you specified the --with-generator option, so you already have the generator stubs in your plugin.
+
+We'll be relying on the built-in rails generate template for this tutorial. Going into the details of generators is beyond the scope of this tutorial.
+
+Type:
+
+ script/generate
+
+You should see the line:
+
+ Plugins (vendor/plugins): yaffle
+
+When you run `script/generate yaffle` you should see the contents of your USAGE file. For this plugin, the USAGE file looks like this:
+
+------------------------------------------------------------------
+Description:
+ Creates a migration that adds yaffle squawk fields to the given model
+
+Example:
+ ./script/generate yaffle hickwall
+
+ This will create:
+ db/migrate/TIMESTAMP_add_yaffle_fields_to_hickwall
+------------------------------------------------------------------
+
+Now you can add code to your generator:
+
+[source, ruby]
+------------------------------------------------------------------
+# File: vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb
+
+class YaffleGenerator < Rails::Generator::NamedBase
+ def manifest
+ record do |m|
+ m.migration_template 'migration:migration.rb', "db/migrate", {:assigns => yaffle_local_assigns,
+ :migration_file_name => "add_yaffle_fields_to_#{custom_file_name}"
+ }
+ end
+ end
+
+ private
+ def custom_file_name
+ custom_name = class_name.underscore.downcase
+ custom_name = custom_name.pluralize if ActiveRecord::Base.pluralize_table_names
+ end
+
+ def yaffle_local_assigns
+ returning(assigns = {}) do
+ assigns[:migration_action] = "add"
+ assigns[:class_name] = "add_yaffle_fields_to_#{custom_file_name}"
+ assigns[:table_name] = custom_file_name
+ assigns[:attributes] = [Rails::Generator::GeneratedAttribute.new("last_squawk", "string")]
+ assigns[:attributes] << Rails::Generator::GeneratedAttribute.new("last_squawked_at", "datetime")
+ end
+ end
+end
+------------------------------------------------------------------
+
+Note that you need to be aware of whether or not table names are pluralized.
+
+This does a few things:
+
+ * Reuses the built in rails `migration_template` method.
+ * Reuses the built-in rails migration template.
+
+When you run the generator like
+
+ script/generate yaffle bird
+
+You will see a new file:
+
+[source, ruby]
+------------------------------------------------------------------
+# File: db/migrate/20080529225649_add_yaffle_fields_to_birds.rb
+
+class AddYaffleFieldsToBirds < ActiveRecord::Migration
+ def self.up
+ add_column :birds, :last_squawk, :string
+ add_column :birds, :last_squawked_at, :datetime
+ end
+
+ def self.down
+ remove_column :birds, :last_squawked_at
+ remove_column :birds, :last_squawk
+ end
+end
+------------------------------------------------------------------
diff --git a/railties/doc/guides/creating_plugins/odds_and_ends.txt b/railties/doc/guides/creating_plugins/odds_and_ends.txt
new file mode 100644
index 0000000000..eb127f73ca
--- /dev/null
+++ b/railties/doc/guides/creating_plugins/odds_and_ends.txt
@@ -0,0 +1,122 @@
+== Odds and ends ==
+
+=== Work with init.rb ===
+
+The plugin initializer script 'init.rb' is invoked via `eval` (not `require`) so it has slightly different behavior.
+
+If you reopen any classes in init.rb itself your changes will potentially be made to the wrong module. There are 2 ways around this:
+
+The first way is to explicitly define the top-level module space for all modules and classes, like `::Hash`:
+
+[source, ruby]
+---------------------------------------------------
+# File: vendor/plugins/yaffle/init.rb
+
+class ::Hash
+ def is_a_special_hash?
+ true
+ end
+end
+---------------------------------------------------
+
+OR you can use `module_eval` or `class_eval`:
+
+---------------------------------------------------
+# File: vendor/plugins/yaffle/init.rb
+
+Hash.class_eval do
+ def is_a_special_hash?
+ true
+ end
+end
+---------------------------------------------------
+
+=== Generate RDoc Documentation ===
+
+Once your plugin is stable, the tests pass on all database and you are ready to deploy do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy.
+
+The first step is to update the README file with detailed information about how to use your plugin. A few key things to include are:
+
+ * Your name.
+ * How to install.
+ * How to add the functionality to the app (several examples of common use cases).
+ * Warning, gotchas or tips that might help save users time.
+
+Once your README is solid, go through and add rdoc comments to all of the methods that developers will use.
+
+Before you generate your documentation, be sure to go through and add nodoc comments to those modules and methods that are not important to your users.
+
+Once your comments are good to go, navigate to your plugin directory and run:
+
+ rake rdoc
+
+
+=== Store models, views, helpers, and controllers in your plugins ===
+
+You can easily store models, views, helpers and controllers in plugins. Just create a folder for each in the lib folder, add them to the load path and remove them from the load once path:
+
+[source, ruby]
+---------------------------------------------------------
+# File: vendor/plugins/yaffle/init.rb
+
+%w{ models controllers helpers }.each do |dir|
+ path = File.join(directory, 'lib', dir)
+ $LOAD_PATH << path
+ Dependencies.load_paths << path
+ Dependencies.load_once_paths.delete(path)
+end
+---------------------------------------------------------
+
+Adding directories to the load path makes them appear just like files in the the main app directory - except that they are only loaded once, so you have to restart the web server to see the changes in the browser.
+
+Adding directories to the load once paths allow those changes to picked up as soon as you save the file - without having to restart the web server.
+
+
+=== Write custom Rake tasks in your plugin ===
+
+When you created the plugin with the built-in rails generator, it generated a rake file for you in 'vendor/plugins/yaffle/tasks/yaffle.rake'. Any rake task you add here will be available to the app.
+
+Many plugin authors put all of their rake tasks into a common namespace that is the same as the plugin, like so:
+
+[source, ruby]
+---------------------------------------------------------
+# File: vendor/plugins/yaffle/tasks/yaffle.rake
+
+namespace :yaffle do
+ desc "Prints out the word 'Yaffle'"
+ task :squawk => :environment do
+ puts "squawk!"
+ end
+end
+---------------------------------------------------------
+
+When you run `rake -T` from your plugin you will see:
+
+---------------------------------------------------------
+yaffle:squawk # Prints out the word 'Yaffle'
+---------------------------------------------------------
+
+You can add as many files as you want in the tasks directory, and if they end in .rake Rails will pick them up.
+
+=== Store plugins in alternate locations ===
+
+You can store plugins wherever you want - you just have to add those plugins to the plugins path in 'environment.rb'.
+
+Since the plugin is only loaded after the plugin paths are defined, you can't redefine this in your plugins - but it may be good to now.
+
+You can even store plugins inside of other plugins for complete plugin madness!
+
+[source, ruby]
+---------------------------------------------------------
+config.plugin_paths << File.join(RAILS_ROOT,"vendor","plugins","yaffle","lib","plugins")
+---------------------------------------------------------
+
+=== Create your own Plugin Loaders and Plugin Locators ===
+
+If the built-in plugin behavior is inadequate, you can change almost every aspect of the location and loading process. You can write your own plugin locators and plugin loaders, but that's beyond the scope of this tutorial.
+
+
+=== Use Custom Plugin Generators ===
+
+If you are an RSpec fan, you can install the `rspec_plugin_generator` gem, which will generate the spec folder and database for you. See http://github.com/pat-maddox/rspec-plugin-generator/tree/master.
+
diff --git a/railties/doc/guides/creating_plugins/preparation.txt b/railties/doc/guides/creating_plugins/preparation.txt
new file mode 100644
index 0000000000..77e3a3561f
--- /dev/null
+++ b/railties/doc/guides/creating_plugins/preparation.txt
@@ -0,0 +1,169 @@
+== Preparation ==
+
+=== Create the basic app ===
+
+In this tutorial we will create a basic rails application with 1 resource: bird. Start out by building the basic rails app:
+
+------------------------------------------------
+rails plugin_demo
+cd plugin_demo
+script/generate scaffold bird name:string
+rake db:migrate
+script/server
+------------------------------------------------
+
+Then navigate to http://localhost:3000/birds. Make sure you have a functioning rails app before continuing.
+
+NOTE: The aforementioned instructions will work for sqlite3. For more detailed instructions on how to create a rails app for other databases see the API docs.
+
+
+=== Create the plugin ===
+
+The built-in Rails plugin generator stubs out a new plugin. Pass the plugin name, either 'CamelCased' or 'under_scored', as an argument. Pass `\--with-generator` to add an example generator also.
+
+This creates a plugin in 'vendor/plugins' including an 'init.rb' and 'README' as well as standard 'lib', 'task', and 'test' directories.
+
+Examples:
+----------------------------------------------
+./script/generate plugin BrowserFilters
+./script/generate plugin BrowserFilters --with-generator
+----------------------------------------------
+
+Later in the plugin we will create a generator, so go ahead and add the `\--with-generator` option now:
+
+----------------------------------------------
+script/generate plugin yaffle --with-generator
+----------------------------------------------
+
+You should see the following output:
+
+----------------------------------------------
+create vendor/plugins/yaffle/lib
+create vendor/plugins/yaffle/tasks
+create vendor/plugins/yaffle/test
+create vendor/plugins/yaffle/README
+create vendor/plugins/yaffle/MIT-LICENSE
+create vendor/plugins/yaffle/Rakefile
+create vendor/plugins/yaffle/init.rb
+create vendor/plugins/yaffle/install.rb
+create vendor/plugins/yaffle/uninstall.rb
+create vendor/plugins/yaffle/lib/yaffle.rb
+create vendor/plugins/yaffle/tasks/yaffle_tasks.rake
+create vendor/plugins/yaffle/test/core_ext_test.rb
+create vendor/plugins/yaffle/generators
+create vendor/plugins/yaffle/generators/yaffle
+create vendor/plugins/yaffle/generators/yaffle/templates
+create vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb
+create vendor/plugins/yaffle/generators/yaffle/USAGE
+----------------------------------------------
+
+For this plugin you won't need the file 'vendor/plugins/yaffle/lib/yaffle.rb' so you can delete that.
+
+----------------------------------------------
+rm vendor/plugins/yaffle/lib/yaffle.rb
+----------------------------------------------
+
+.Editor's note:
+NOTE: Many plugin authors prefer to keep this file, and add all of the require statements in it. That way, they only line in init.rb would be `require "yaffle"`. If you are developing a plugin that has a lot of files in the lib directory, you may want to create a subdirectory like lib/yaffle and store your files in there. That way your init.rb file stays clean
+
+
+=== Setup the plugin for testing ===
+
+Testing plugins that use the entire Rails stack can be complex, and the generator doesn't offer any help. In this tutorial you will learn how to test your plugin against multiple different adapters using ActiveRecord. This tutorial will not cover how to use fixtures in plugin tests.
+
+To setup your plugin to allow for easy testing you'll need to add 3 files:
+
+ * A 'database.yml' file with all of your connection strings.
+ * A 'schema.rb' file with your table definitions.
+ * A test helper that sets up the database before your tests.
+
+For this plugin you'll need 2 tables/models, Hickwalls and Wickwalls, so add the following files:
+
+*vendor/plugins/yaffle/test/database.yml:*
+
+----------------------------------------------
+sqlite:
+ :adapter: sqlite
+ :dbfile: yaffle_plugin.sqlite.db
+
+sqlite3:
+ :adapter: sqlite3
+ :dbfile: yaffle_plugin.sqlite3.db
+
+postgresql:
+ :adapter: postgresql
+ :username: postgres
+ :password: postgres
+ :database: yaffle_plugin_test
+ :min_messages: ERROR
+
+mysql:
+ :adapter: mysql
+ :host: localhost
+ :username: rails
+ :password:
+ :database: yaffle_plugin_test
+----------------------------------------------
+
+*vendor/plugins/yaffle/test/test_helper.rb:*
+
+[source, ruby]
+----------------------------------------------
+ActiveRecord::Schema.define(:version => 0) do
+ create_table :hickwalls, :force => true do |t|
+ t.string :name
+ t.string :last_squawk
+ t.datetime :last_squawked_at
+ end
+ create_table :wickwalls, :force => true do |t|
+ t.string :name
+ t.string :last_tweet
+ t.datetime :last_tweeted_at
+ end
+end
+
+# File: vendor/plugins/yaffle/test/test_helper.rb
+
+ENV['RAILS_ENV'] = 'test'
+ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..'
+
+require 'test/unit'
+require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
+
+config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
+ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
+
+db_adapter = ENV['DB']
+
+# no db passed, try one of these fine config-free DBs before bombing.
+db_adapter ||=
+ begin
+ require 'rubygems'
+ require 'sqlite'
+ 'sqlite'
+ rescue MissingSourceFile
+ begin
+ require 'sqlite3'
+ 'sqlite3'
+ rescue MissingSourceFile
+ end
+ end
+
+if db_adapter.nil?
+ raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
+end
+
+ActiveRecord::Base.establish_connection(config[db_adapter])
+
+load(File.dirname(__FILE__) + "/schema.rb")
+
+require File.dirname(__FILE__) + '/../init.rb'
+
+class Hickwall < ActiveRecord::Base
+ acts_as_yaffle
+end
+
+class Wickwall < ActiveRecord::Base
+ acts_as_yaffle :yaffle_text_field => :last_tweet, :yaffle_date_field => :last_tweeted_at
+end
+----------------------------------------------
diff --git a/railties/doc/guides/creating_plugins/string_to_squawk.txt b/railties/doc/guides/creating_plugins/string_to_squawk.txt
new file mode 100644
index 0000000000..50516cef69
--- /dev/null
+++ b/railties/doc/guides/creating_plugins/string_to_squawk.txt
@@ -0,0 +1,103 @@
+== Add a `to_squawk` method to String ==
+
+To update a core class you will have to:
+
+ * Write tests for the desired functionality.
+ * Create a file for the code you wish to use.
+ * Require that file from your 'init.rb'.
+
+Most plugins store their code classes in the plugin's lib directory. When you add a file to the lib directory, you must also require that file from 'init.rb'. The file you are going to add for this tutorial is 'lib/core_ext.rb'.
+
+First, you need to write the tests. Testing plugins is very similar to testing rails apps. The generated test file should look something like this:
+
+[source, ruby]
+--------------------------------------------------------
+# File: vendor/plugins/yaffle/test/core_ext_test.rb
+
+require 'test/unit'
+
+class CoreExtTest < Test::Unit::TestCase
+ # Replace this with your real tests.
+ def test_this_plugin
+ flunk
+ end
+end
+--------------------------------------------------------
+
+Start off by removing the default test, and adding a require statement for your test helper.
+
+[source, ruby]
+--------------------------------------------------------
+# File: vendor/plugins/yaffle/test/core_ext_test.rb
+
+require 'test/unit'
+require File.dirname(__FILE__) + '/test_helper.rb'
+
+class CoreExtTest < Test::Unit::TestCase
+end
+--------------------------------------------------------
+
+Navigate to your plugin directory and run `rake test`:
+
+--------------------------------------------------------
+cd vendor/plugins/yaffle
+rake test
+--------------------------------------------------------
+
+Your test should fail with `no such file to load -- ./test/../lib/core_ext.rb (LoadError)` because we haven't created any file yet. Create the file 'lib/core_ext.rb' and re-run the tests. You should see a different error message:
+
+--------------------------------------------------------
+1.) Failure ...
+No tests were specified
+--------------------------------------------------------
+
+Great - now you are ready to start development. The first thing we'll do is to add a method to String called `to_squawk` which will prefix the string with the word ``squawk!''. The test will look something like this:
+
+[source, ruby]
+--------------------------------------------------------
+# File: vendor/plugins/yaffle/init.rb
+
+class CoreExtTest < Test::Unit::TestCase
+ def test_string_should_respond_to_squawk
+ assert_equal true, "".respond_to?(:to_squawk)
+ end
+
+ def test_string_prepend_empty_strings_with_the_word_squawk
+ assert_equal "squawk!", "".to_squawk
+ end
+
+ def test_string_prepend_non_empty_strings_with_the_word_squawk
+ assert_equal "squawk! Hello World", "Hello World".to_squawk
+ end
+end
+--------------------------------------------------------
+
+[source, ruby]
+--------------------------------------------------------
+# File: vendor/plugins/yaffle/init.rb
+
+require "core_ext"
+--------------------------------------------------------
+
+[source, ruby]
+--------------------------------------------------------
+# File: vendor/plugins/yaffle/lib/core_ext.rb
+
+String.class_eval do
+ def to_squawk
+ "squawk! #{self}".strip
+ end
+end
+--------------------------------------------------------
+
+When monkey-patching existing classes it's often better to use `class_eval` instead of opening the class directly.
+
+To test that your method does what it says it does, run the unit tests. To test this manually, fire up a console and start squawking:
+
+--------------------------------------------------------
+$ ./script/console
+>> "Hello World".to_squawk
+=> "squawk! Hello World"
+--------------------------------------------------------
+
+If that worked, congratulations! You just created your first test-driven plugin that extends a core ruby class.
diff --git a/railties/doc/guides/creating_plugins/view_helper.txt b/railties/doc/guides/creating_plugins/view_helper.txt
new file mode 100644
index 0000000000..b03a190e1a
--- /dev/null
+++ b/railties/doc/guides/creating_plugins/view_helper.txt
@@ -0,0 +1,61 @@
+== Create a `squawk_info_for` view helper ==
+
+Creating a view helper is a 3-step process:
+
+ * Add an appropriately named file to the 'lib' directory.
+ * Require the file and hooks in 'init.rb'.
+ * Write the tests.
+
+First, create the test to define the functionality you want:
+
+[source, ruby]
+---------------------------------------------------------------
+# File: vendor/plugins/yaffle/test/view_helpers_test.rb
+
+require File.dirname(__FILE__) + '/test_helper.rb'
+include YaffleViewHelper
+
+class ViewHelpersTest < Test::Unit::TestCase
+ def test_squawk_info_for_should_return_the_text_and_date
+ time = Time.now
+ hickwall = Hickwall.new
+ hickwall.last_squawk = "Hello World"
+ hickwall.last_squawked_at = time
+ assert_equal "Hello World, #{time.to_s}", squawk_info_for(hickwall)
+ end
+end
+---------------------------------------------------------------
+
+Then add the following statements to init.rb:
+
+[source, ruby]
+---------------------------------------------------------------
+# File: vendor/plugins/yaffle/init.rb
+
+require "view_helpers"
+ActionView::Base.send :include, YaffleViewHelper
+---------------------------------------------------------------
+
+Then add the view helpers file and
+
+[source, ruby]
+---------------------------------------------------------------
+# File: vendor/plugins/yaffle/lib/view_helpers.rb
+
+module YaffleViewHelper
+ def squawk_info_for(yaffle)
+ returning "" do |result|
+ result << yaffle.read_attribute(yaffle.class.yaffle_text_field)
+ result << ", "
+ result << yaffle.read_attribute(yaffle.class.yaffle_date_field).to_s
+ end
+ end
+end
+---------------------------------------------------------------
+
+You can also test this in script/console by using the `helper` method:
+
+---------------------------------------------------------------
+$ ./script/console
+>> helper.squawk_info_for(@some_yaffle_instance)
+---------------------------------------------------------------
diff --git a/railties/doc/guides/icons/README b/railties/doc/guides/icons/README
new file mode 100644
index 0000000000..f12b2a730c
--- /dev/null
+++ b/railties/doc/guides/icons/README
@@ -0,0 +1,5 @@
+Replaced the plain DocBook XSL admonition icons with Jimmac's DocBook
+icons (http://jimmac.musichall.cz/ikony.php3). I dropped transparency
+from the Jimmac icons to get round MS IE and FOP PNG incompatibilies.
+
+Stuart Rackham
diff --git a/railties/doc/guides/icons/callouts/1.png b/railties/doc/guides/icons/callouts/1.png
new file mode 100644
index 0000000000..7d473430b7
Binary files /dev/null and b/railties/doc/guides/icons/callouts/1.png differ
diff --git a/railties/doc/guides/icons/callouts/10.png b/railties/doc/guides/icons/callouts/10.png
new file mode 100644
index 0000000000..997bbc8246
Binary files /dev/null and b/railties/doc/guides/icons/callouts/10.png differ
diff --git a/railties/doc/guides/icons/callouts/11.png b/railties/doc/guides/icons/callouts/11.png
new file mode 100644
index 0000000000..ce47dac3f5
Binary files /dev/null and b/railties/doc/guides/icons/callouts/11.png differ
diff --git a/railties/doc/guides/icons/callouts/12.png b/railties/doc/guides/icons/callouts/12.png
new file mode 100644
index 0000000000..31daf4e2f2
Binary files /dev/null and b/railties/doc/guides/icons/callouts/12.png differ
diff --git a/railties/doc/guides/icons/callouts/13.png b/railties/doc/guides/icons/callouts/13.png
new file mode 100644
index 0000000000..14021a89c2
Binary files /dev/null and b/railties/doc/guides/icons/callouts/13.png differ
diff --git a/railties/doc/guides/icons/callouts/14.png b/railties/doc/guides/icons/callouts/14.png
new file mode 100644
index 0000000000..64014b75fe
Binary files /dev/null and b/railties/doc/guides/icons/callouts/14.png differ
diff --git a/railties/doc/guides/icons/callouts/15.png b/railties/doc/guides/icons/callouts/15.png
new file mode 100644
index 0000000000..0d65765fcf
Binary files /dev/null and b/railties/doc/guides/icons/callouts/15.png differ
diff --git a/railties/doc/guides/icons/callouts/2.png b/railties/doc/guides/icons/callouts/2.png
new file mode 100644
index 0000000000..5d09341b2f
Binary files /dev/null and b/railties/doc/guides/icons/callouts/2.png differ
diff --git a/railties/doc/guides/icons/callouts/3.png b/railties/doc/guides/icons/callouts/3.png
new file mode 100644
index 0000000000..ef7b700471
Binary files /dev/null and b/railties/doc/guides/icons/callouts/3.png differ
diff --git a/railties/doc/guides/icons/callouts/4.png b/railties/doc/guides/icons/callouts/4.png
new file mode 100644
index 0000000000..adb8364eb5
Binary files /dev/null and b/railties/doc/guides/icons/callouts/4.png differ
diff --git a/railties/doc/guides/icons/callouts/5.png b/railties/doc/guides/icons/callouts/5.png
new file mode 100644
index 0000000000..4d7eb46002
Binary files /dev/null and b/railties/doc/guides/icons/callouts/5.png differ
diff --git a/railties/doc/guides/icons/callouts/6.png b/railties/doc/guides/icons/callouts/6.png
new file mode 100644
index 0000000000..0ba694af6c
Binary files /dev/null and b/railties/doc/guides/icons/callouts/6.png differ
diff --git a/railties/doc/guides/icons/callouts/7.png b/railties/doc/guides/icons/callouts/7.png
new file mode 100644
index 0000000000..472e96f8ac
Binary files /dev/null and b/railties/doc/guides/icons/callouts/7.png differ
diff --git a/railties/doc/guides/icons/callouts/8.png b/railties/doc/guides/icons/callouts/8.png
new file mode 100644
index 0000000000..5e60973c21
Binary files /dev/null and b/railties/doc/guides/icons/callouts/8.png differ
diff --git a/railties/doc/guides/icons/callouts/9.png b/railties/doc/guides/icons/callouts/9.png
new file mode 100644
index 0000000000..a0676d26cc
Binary files /dev/null and b/railties/doc/guides/icons/callouts/9.png differ
diff --git a/railties/doc/guides/icons/caution.png b/railties/doc/guides/icons/caution.png
new file mode 100644
index 0000000000..cb9d5ea0df
Binary files /dev/null and b/railties/doc/guides/icons/caution.png differ
diff --git a/railties/doc/guides/icons/example.png b/railties/doc/guides/icons/example.png
new file mode 100644
index 0000000000..bba1c0010d
Binary files /dev/null and b/railties/doc/guides/icons/example.png differ
diff --git a/railties/doc/guides/icons/home.png b/railties/doc/guides/icons/home.png
new file mode 100644
index 0000000000..37a5231bac
Binary files /dev/null and b/railties/doc/guides/icons/home.png differ
diff --git a/railties/doc/guides/icons/important.png b/railties/doc/guides/icons/important.png
new file mode 100644
index 0000000000..1096c23295
Binary files /dev/null and b/railties/doc/guides/icons/important.png differ
diff --git a/railties/doc/guides/icons/next.png b/railties/doc/guides/icons/next.png
new file mode 100644
index 0000000000..64e126bdda
Binary files /dev/null and b/railties/doc/guides/icons/next.png differ
diff --git a/railties/doc/guides/icons/note.png b/railties/doc/guides/icons/note.png
new file mode 100644
index 0000000000..841820f7c4
Binary files /dev/null and b/railties/doc/guides/icons/note.png differ
diff --git a/railties/doc/guides/icons/prev.png b/railties/doc/guides/icons/prev.png
new file mode 100644
index 0000000000..3e8f12fe24
Binary files /dev/null and b/railties/doc/guides/icons/prev.png differ
diff --git a/railties/doc/guides/icons/tip.png b/railties/doc/guides/icons/tip.png
new file mode 100644
index 0000000000..a3a029d898
Binary files /dev/null and b/railties/doc/guides/icons/tip.png differ
diff --git a/railties/doc/guides/icons/up.png b/railties/doc/guides/icons/up.png
new file mode 100644
index 0000000000..2db1ce62fa
Binary files /dev/null and b/railties/doc/guides/icons/up.png differ
diff --git a/railties/doc/guides/icons/warning.png b/railties/doc/guides/icons/warning.png
new file mode 100644
index 0000000000..0b0c419df2
Binary files /dev/null and b/railties/doc/guides/icons/warning.png differ
diff --git a/railties/doc/guides/securing_rails_applications/creating_records_directly_from_form_parameters.txt b/railties/doc/guides/securing_rails_applications/creating_records_directly_from_form_parameters.txt
new file mode 100644
index 0000000000..b237c4a17f
--- /dev/null
+++ b/railties/doc/guides/securing_rails_applications/creating_records_directly_from_form_parameters.txt
@@ -0,0 +1,86 @@
+== Creating records directly from form parameters ==
+
+=== The problem ===
+
+Let's say you want to make a user registration system. Your users table looks like this:
+
+-------------------------------------------------------
+CREATE TABLE users (
+ id INTEGER PRIMARY KEY,
+ name VARCHAR(20) NOT NULL, -- the login name
+ password VARCHAR(20) NOT NULL,
+ role VARCHAR(20) NOT NULL DEFAULT "User", -- e.g. "Admin", "Moderator, "User"
+ approved INTEGER NOT NULL DEFAULT 0 -- is the registered accound approved by the administrator?
+);
+
+CREATE UNIQUE INDEX users_name_unique ON users(name);
+-------------------------------------------------------
+
+The registration form:
+
+[source, xhtml]
+-------------------------------------------------------
+
+
+
+
+-------------------------------------------------------
+
+The easiest way to create a user object from the form data in the controller is:
+
+[source, ruby]
+-------------------------------------------------------
+User.create(params[:user])
+-------------------------------------------------------
+
+But what happens if someone decides to save the registration form to his disk and play around with adding a few fields?
+
+[source, xhtml]
+-------------------------------------------------------
+
+
+
+
+
+
+-------------------------------------------------------
+
+He can create an account, make himself admin and approve his own account with one click.
+
+=== The solution ===
+
+Active Record provides two ways of securing sensitive attributes from being overwritten by malicious users that change the form. The first is `attr_protected` that denies mass-assignment the right to change the named parameters.
+
+Using `attr_protected`, we can secure the User models like this:
+
+[source, ruby]
+-------------------------------------------------------
+class User < ActiveRecord::Base
+ attr_protected :approved, :role
+end
+-------------------------------------------------------
+
+This will ensure that on doing `User.create(params[:user])` both `params[:user][:approved]` and `params[:user][:role]` will be ignored. You'll have to manually set them like this:
+
+[source, ruby]
+-------------------------------------------------------
+user = User.new(params[:user])
+user.approved = sanitize_properly(params[:user][:approved])
+user.role = sanitize_properly(params[:user][:role])
+-------------------------------------------------------
+
+=== Allowing instead of protecting ===
+
+If you're afraid you might forget to apply `attr_protected` to the right attributes before making your model available to the cruel world, you can also specify the protection in reverse. You simply allow access instead of deny it, so only the attributes named in `attr_accessible` are available for mass-assignment.
+
+Using `attr_accessible`, we can secure the User models like this:
+
+[source, ruby]
+-------------------------------------------------------
+class User < ActiveRecord::Base
+ attr_accessible :name, :password
+end
+-------------------------------------------------------
+
+This description has exactly the same affect as doing `attr_protected :approved, :role`, but when you add new attrbutes to the User model, they'll be protected by default.
+
diff --git a/railties/doc/guides/securing_rails_applications/cross_site_scripting.txt b/railties/doc/guides/securing_rails_applications/cross_site_scripting.txt
new file mode 100644
index 0000000000..56b3940511
--- /dev/null
+++ b/railties/doc/guides/securing_rails_applications/cross_site_scripting.txt
@@ -0,0 +1,64 @@
+== Cross Site Scripting (CSS/XSS) ==
+
+=== The problem ===
+
+Many web applications use session cookies to track the requests of a user. The cookie is used to identify the request and connect it to the session data (the `session` method in Rails). Usually the session contains a reference to the user that is currently logged in, e.g. the id of a User object.
+
+Cross Site Scripting is a technique for ``stealing'' the cookie from another visitor of the website, and thus stealing the login. Cookies are only available from the domain where the were originally created. The easiest way to get access to the cookie is to place a specially crafted piece of JavaScript code on the website; the script can read the cookie of a visitor and send it to the attacker, e.g. by transmitting the data as an URL parameter to another website.
+2.2 Example of an attack
+
+A site with the following Eruby template is vulnerable to XSS:
+
+------------------------------------
+<%= params[:text] %>
+------------------------------------
+
+The 'text' parameter is directly included in the page, so it is possible to insert JavaScript code by setting the parameter to something like
+
+[source, xhtml]
+------------------------------------
+
+------------------------------------
+
+After url-encoding the script (e.g. with `CGI.encode`) the attacker can put it into an URL and make his victim open the URL in the browser.
+
+If you open the URL
+
+------------------------------------
+http://website.domain/controller/action?text=%3Cscript%3Ealert%28document.cookie%29%3C%2Fscript%3E
+------------------------------------
+
+you will get a popup window with your session cookie. Instead of opening the popup the script could as well send the session id to another server.
+
+=== How to protect your application ===
+
+Obviously the key to a successful attack is the possibility to place JavaScript on a website that can receive the session cookie. This can be a blog with user comments, a web forum or a Wiki. How can you protect against it? Strictly convert HTML meta characters (`<` and `>`) to the equivalent HTML entities (`<` and `>`) in every string that is rendered in the website. This will ensure that, no matter what kind of text an attacker enters in a form or attaches to an URL, the browser will always render it as plain text and never interpret any HTML tags. This is a good idea anyway, as a user can easily mess up your layout by leaving tags open. If you want the user to be able to format his texts, it is usually better to use a markup language like Textile, Markdown or RDoc.
+
+Rails provides the helper method `h` for HTML meta character conversion in Views.
+
+Example:
+
+------------------------------------
+<%=h post.subject %>
+<%=h post.text %>
+------------------------------------
+
+You should accustom to using `h` for any variable that is rendered in the view, even if you think you can trust it to be from a reliable source.
+
+=== XSS attacks using an echo service ===
+
+The echo service is a service running on TCP port 7 that returns back everything you send to it. On Debian it is active by default. Now how can this be a security problem for a web application?
+
+If the server of the target website 'target.domain' is running an echo service, the attacker could create a form like the following on his own website:
+
+[source, xhtml]
+------------------------------------
+
+
+
+
+------------------------------------
+
+If he makes someone who has a session cookie on the target website submit this form, the content of the hidden field is sent to the echo server at port 7 – and returned. If the browser decides to display the returned data as HTML (some versions of IE do), it will execute the JavaScript code; and because the domain is 'target.domain' the session cookie is available for the script.
+
+This is rather a client-side issue, but to reduce the probability of a successful attack you should deactivate any echo services on the web server. However this does not provide full security, because there are also some other services (e.g. FTP, POP3) that can be used instead of the echo server.
diff --git a/railties/doc/guides/securing_rails_applications/securing_rails_applications.txt b/railties/doc/guides/securing_rails_applications/securing_rails_applications.txt
new file mode 100644
index 0000000000..b2cebbd311
--- /dev/null
+++ b/railties/doc/guides/securing_rails_applications/securing_rails_applications.txt
@@ -0,0 +1,14 @@
+Securing Rails applications
+===========================
+
+This manual describes common security problems in web applications and how
+to avoid them with Rails. If you have any questions or suggestions, please
+mail me at ror(at)andreas-s.net.
+
+
+include::sql_injection.txt[]
+
+include::cross_site_scripting.txt[]
+
+include::creating_records_directly_from_form_parameters.txt[]
+
diff --git a/railties/doc/guides/securing_rails_applications/sql_injection.txt b/railties/doc/guides/securing_rails_applications/sql_injection.txt
new file mode 100644
index 0000000000..51a0520b39
--- /dev/null
+++ b/railties/doc/guides/securing_rails_applications/sql_injection.txt
@@ -0,0 +1,87 @@
+== SQL Injection ==
+
+=== The problem ===
+
+SQL injection is the #1 security problem in many web applications. How does it work? If the web application includes strings from unreliable sources (usually form parameters) in SQL statements and doesn't correctly quote any SQL meta characters like backslashes or single quotes, an attacker can change `WHERE` conditions in SQL statements, create records with invalid data or even execute arbitrary SQL statements.
+
+=== How to protect your application ===
+
+If you only use the predefined ActiveRecord functions (attributes, save, find) without writing any conditions, limits or SQL queries yourself, ActiveRecord takes care of quoting any dangerous characters in the data for you.
+
+If you don't, you have to make sure that strings for arguments that are directly used to build SQL queries (like the condition, limit and sort arguments for `find :all`) do not contain any SQL meta characters.
+
+=== Using arbitrary strings in conditions and SQL statements ===
+
+==== Wrong ====
+
+Imagine a webmail system where a user can select a list of all the emails with a certain subject. A query could look like this:
+
+[source, ruby]
+---------------------------------------------------------------------------
+Email.find(:all, "owner_id = 123 AND subject = '#{params[:subject]}'")
+---------------------------------------------------------------------------
+
+This is dangerous. Imagine a user sending the string `\' OR 1 \--` in the parameter 'subject'; the resulting statement will look like this:
+
+[source, ruby]
+---------------------------------------------------------------------------
+Email.find(:all, "owner_id = 123 AND subject = '' OR 1 --'")
+---------------------------------------------------------------------------
+
+Because of ``OR 1'' the condition is always true. The part ``\--'' starts a SQL comment; everything after it will be ignored. The result: the user will get a list of all the emails in the database.
+
+(Of course the 'owner_id' would have to be inserted dynamically in a real application; this was omitted to keep the examples as simple as possible.)
+
+==== Correct ====
+
+[source, ruby]
+---------------------------------------------------------------------------
+Email.find(:all, ["owner_id = 123 AND subject = ?", params[:subject]])
+---------------------------------------------------------------------------
+
+If the argument for find_all is an array instead of a string, ActiveRecord will insert the elements 2..n of the array for the `?` placeholders in the element 1, add quotation marks if the elements are strings, and quote all characters that have a special meaning for the database adapter used by the `Email` model.
+
+If you don't like the syntax of the array, you can take care of the quoting yourself by calling the `quote_value` method of the model class. You have to do this when you use `find_by_sql`, as the Array argument doesn't work there:
+
+[source, ruby]
+---------------------------------------------------------------------------
+Email.find_by_sql("SELECT * FROM email WHERE owner_id = 123 AND subject = #{Email.quote_value(subject)}")
+---------------------------------------------------------------------------
+
+The quotation marks are added automatically by `Email.quote_value` if the argument is a string.
+
+=== Extracting queries into model methods ===
+
+If you need to execute a query with the similar options in several places in your code, you should create a model method for that query. Instead of
+
+[source, ruby]
+---------------------------------------------------------------------------
+emails = Email.find(:all, ["subject = ?", subject])
+---------------------------------------------------------------------------
+
+you could define the following class method in the model:
+
+[source, ruby]
+---------------------------------------------------------------------------
+class Email < ActiveRecord::Base
+ def self.find_with_subject(subject)
+ Email.find(:all, ["subject = ?", subject])
+ end
+end
+---------------------------------------------------------------------------
+
+and call it like this:
+
+[source, ruby]
+---------------------------------------------------------------------------
+emails = Email.find_with_subject(subject)
+---------------------------------------------------------------------------
+
+This has the advantage that you don't have to care about meta characters when using the function `find_with_subject`. Generally you should always make sure that this kind of model method can not break anything, even if it is called with untrusted arguments.
+
+.ActiveRecord automatically generates finder methods
+NOTE: The `find_with_subject` method given in the above example is actually redundant.
+ActiveRecord automatically generates finder methods for columns. In this case, ActiveRecord
+automatically generates the method `find_by_subject` (which works exactly like the `find_with_subject`
+method given in the example). `find_with_subject` is only given here as an example. In general,
+you should use ActiveRecord's auto-generated finder methods instead of writing your own.
diff --git a/railties/doc/guides/testing_rails_applications/testing_rails_applications.txt b/railties/doc/guides/testing_rails_applications/testing_rails_applications.txt
new file mode 100644
index 0000000000..a775ed2f09
--- /dev/null
+++ b/railties/doc/guides/testing_rails_applications/testing_rails_applications.txt
@@ -0,0 +1,1371 @@
+A Guide to Testing Rails Applications
+=====================================
+
+This article is for fellow Rubyists looking for more information on test writing and how that fits into Ruby On Rails. If you're new to test writing or experienced with test writing, but not in Rails, hopefully you'll gain some practical tips and insight on successful testing.
+Assumptions
+
+Just so we're all on the same page here, I'm making a few assumptions about you.
+
+ * You've got Ruby installed and know how to run a Ruby script.
+ * You've got Rails installed
+ * You've created a basic Rails application with 1 controller and 1 model.
+
+If you haven't accomplished all of the above, you might be jumping ahead of yourself. Check out www.rubyonrails.org for some great beginner's tutorials.
+
+Make sure you have Ruby 1.8.6 or greater and Rails 2.0 or greater.
+
+Also, hats off to xal (and others) for creating this site.
+
+
+== Testing 1-2-3... is this thing on?
+
+=== What are These Strange Creatures?
+
+I have to say that tests are such a straight-forward concept that it's actually harder to describe what they are than it is to show you how to do them.
+
+So what are tests?
+
+The Official Answer::
+ Tests are collections of questions and scenarios that provide us (developers) with consistent results which prove that our application is doing what we expect it to do.
+
+The Unofficial Answer::
+ We write tests to interrogate, taunt, bully, punish, and otherwise kick the crap out of our application code until it consistently gives us the correct answer. Think of it as ``tough love''. ;)
+
+Need some examples?
+
+ * ensure user names have at least 4 characters
+ * ensure that when we save this record, the administrator is e-mailed
+ * ensure all replies are deleted when we delete the parent message
+ * ensure that Canadians have GST of 7% applied to their purchases
+
+The idea of tests is that you write them at the same time as you write your application. In essence, you're writing two application. As your application grows and becomes more complex, you create many tests. Re-running these tests at any point will help you discover if you've ``broke'' anything along the way.
+
+Some developers actually take this a step further and create the tests before they create the application. This is called Test Driven Development (TDD), and although I personally don't subscribe to it, it's a totally legit way of coding.
+
+There's volumes written about Test Driven Development and testing in general. For more information, talk to Mr. Google.
+
+=== So Why Test?
+
+As mentioned before, tests offer proof that you've done a good job. Your tests become your own personal QA assistant. If you code your tests correctly, at any point in development, you will know:
+
+ * what processes work
+ * what processes fail
+ * the effect of adding new pieces onto your application
+
+With your tests as your safety net, you can do wild refactoring of your code and be able to tell what needs to be addressed simply by seeing which tests fail.
+
+In addition to normal "did it pass?" testing, you can go the opposite route. You can explicitly try to break your application such as feeding it unexpected and crazy input, shutting down critical resources like a database to see what happens, and tampering with things such as session values just to see what happens. By testing your application where the weak points are, you can fix it *before* it ever becomes an issue.
+
+A strong suite of tests ensures your application has a high level of quality. It's worth the extra effort.
+
+Another positive side-effect of writing tests is that you have basic usage documentation of how things work. Simply by looking at your testing code, you can see how you need to work with an object to make it do it's thing.
+
+But enough about theory.
+
+
+== Introducing Test/Unit
+
+=== The Small Picture
+
+==== Hello World
+
+As we all know, Ruby ships with a boat load of great libraries. If you're like me, you've only used a quarter of them or so. One little gem of a library is 'test/unit'. It comes packaged with Ruby 1.8.1, so there's nothing to download.
+
+By using one line of code `require \'test/unit'`, you arm your Ruby script with the capability to write tests.
+
+Let's show you a really basic test, and then we'll step through it and explain some details.
+
+[source,ruby]
+------------------------------------------------------
+# hello_test.rb
+
+require 'test/unit'
+
+class HelloTestCase < Test::Unit::TestCase
+ def test_hello
+ assert true
+ end
+end
+------------------------------------------------------
+
+Quite possibly the simplest and least useful test ever invented, but it shows you the bare bones of writing a test case. That script can be run from the command line the same way your run any other Ruby script.
+
+Simply run `ruby hello_test.rb` and you will see the following:
+
+------------------------------------------------------
+Started
+.
+Finished in 0.0 seconds.
+
+1 tests, 1 assertions, 0 failures, 0 errors
+------------------------------------------------------
+
+Congratulations, your first test.
+
+
+==== What Does It All Mean?
+
+By looking at the output of a test, you will be able to tell if the tests pass or not. In our example, not surprisingly, we've passed. The summary shows *1 test, 1 assertion, 0 failures, and 0 errors*.
+
+So, let's break our test source code down.
+
+First, line 1.
+
+[source,ruby]
+------------------------------------------------------
+require 'test/unit'
+------------------------------------------------------
+
+You'll always have this when writing unit tests. It contains the classes and functionality to write and run unit tests.
+
+Next, we have a class `HelloTestCase` which derives from `Test::Unit::TestCase`.
+
+[source,ruby]
+------------------------------------------------------
+class HelloTestCase < Test::Unit::TestCase
+------------------------------------------------------
+
+All of the tests that you create will directly subclass `Test::Unit::TestCase`. The TestCase provides the ``housing'' of where your tests will live. More on this in a bit.
+
+Finally, we have a method called `test_hello`.
+
+[source,ruby]
+------------------------------------------------------
+def test_hello
+------------------------------------------------------
+
+All tests must follow this naming convention: *their names start with the first 4 characters test*, as in `test_hello`, `testme`, and `testarosa`. If you create a method that doesn't start with test, the testing framework won't recognize it as a test, hence, won't run it automatically, hence it is a normal Ruby method.
+
+Inside our `test_hello` method, we have an assertion.
+
+[source,ruby]
+------------------------------------------------------
+assert true
+------------------------------------------------------
+
+Assertions are where the real work gets done. There are a whole army of different types of assertions that you'll use to make sure your code is doing the right thing.
+
+
+=== The Big Picture
+
+Grab a cup of coffee and dunk your head in some ice water, because here's some more theory.
+
+There are 4 major players in the testing game.
+
+==== A: The Assertion
+
+An 'Assertion' is 1 line of code that evaluates an object (or expression) for expected results.
+
+For example, is this value = that value? is this object nil? does this line of code throw an exception? is the user's password greater than 5 characters?
+
+==== B: The Test ====
+
+A 'Test' is method that contains assertions which represent a particular testing scenario.
+
+For example, we may have a test method called `test_valid_password`. In order for this test to pass, we might need to check a few things:
+
+ * password is 4 or more characters
+ * password isn't the word ‘password'
+ * password isn't all spaces
+
+If all of these assertions are successful, the test will pass.
+
+==== C: The Test Case ====
+A 'Test Case' is a class inherited from `Test::Unit::TestCase` containing a testing ``strategy'' comprised of contextually related tests.
+
+For example, I may have a test case called UserTestCase which has a bunch of tests that check that:
+
+ * the password is valid (`test_password`)
+ * the username doesn't have any forbidden words (`test_username_cussin`)
+ * a user is under the age of 150 (`test_shriveled_raisin`)
+
+==== D: The Test Suite ====
+A 'Test Suite' is a collection of test cases. When you run a test suite, it will, in turn, execute each test that belongs to it.
+
+For example, instead of running each test unit individually, you can run them all by creating a suite and including them. This is good for stuff like continuous-build integration.
+
+We won't get into test suites in this article.
+
+
+==== The Hierarchy ====
+
+The relationship of these objects to one-another looks like this:
+
+ * a test suite
+ * has many test cases
+ * which have many tests
+ * which have many assertions
+
+
+== Hello World on Steroids ==
+
+=== The Victim ===
+
+In the last episode, we learned that we write tests to prove our code works properly. Let's create a really simple class for us to test.
+
+[source,ruby]
+--------------------------------------------------
+# secret_agent.rb
+class SecretAgent
+
+ # simple public properties
+ attr_accessor :username
+ attr_accessor :password
+
+ # our "constructor"
+ def initialize(username,password)
+ @username = username
+ @password = password
+ end
+
+ # the logic to determine if the password is
+ # good enough for the user to use
+ def is_password_secure?
+ return false if @password.nil?
+ return false if @password.empty?
+ return false if @password.size < 4
+ return false if @password 'stirred'
+ return false if @password ‘password'
+ return false if @password == @username
+ true
+ end
+end
+--------------------------------------------------
+
+Ok. So, we've got a class which represents a secret agent. What we're about to do is test a user to make sure that their password is secure enough to use in our top-secret database.
+
+The `is_password_secure?` method will answer that for us. If the user's password passes our stringent set of rules, then the function will return a true value and the secret agent granted access.
+
+
+=== The Assault ===
+
+Let's build upon the last test we wrote and add in a few more things, specifically, let's test out this brand new SecretAgent.
+
+[source,ruby]
+-----------------------------------------------
+require 'test/unit'
+require 'secret_agent'
+
+class HelloTestCase < Test::Unit::TestCase
+
+ def test_hello
+ assert true
+ end
+
+ # our new test will exercise the new SecretAgent class
+ def test_these_passwords
+ # first, let's try a few passwords that should fail
+ assert !SecretAgent.new("bond","abc").is_password_secure?
+ assert !SecretAgent.new("bond","007").is_password_secure?
+ assert !SecretAgent.new("bond","stirred").is_password_secure?
+ assert !SecretAgent.new("bond","password").is_password_secure?
+ assert !SecretAgent.new("bond","bond").is_password_secure?
+ assert !SecretAgent.new("bond","").is_password_secure?
+ assert !SecretAgent.new("bond",nil).is_password_secure?
+
+ # now, let's try passwords that should succeed
+ assert SecretAgent.new("bond","goldfinger").is_password_secure?
+ assert SecretAgent.new("bond","1234").is_password_secure?
+ assert SecretAgent.new("bond","shaken").is_password_secure?
+ end
+end
+-----------------------------------------------
+
+In this example, we've expanded our test case by adding another test called `test_these_passwords` with 10 assertions. Let's run it, cross our fingers, and hope it passes.
+
+-----------------------------------------------
+Started
+..
+Finished in 0.01 seconds.
+
+2 tests, 11 assertions, 0 failures, 0 errors
+-----------------------------------------------
+
+Sweet! It passed!
+
+A couple of new things to notice about the results. See the line right underneath the word Started? Notice it has 2 dots instead of only 1 before? Each . represents a test. You can have 1 of 3 values where the dots are.
+
+ * *.* means successful test (pass)
+ * *F* means failed test
+ * *E* means an error has occurred
+
+=== Failure, Error and General Discomfort ===
+
+Let's add another in 2 more tests. This time, we'll make one of them fail and the other throw an error. Yes, on purpose. Yes, I'm a trained professional.
+
+[source,ruby]
+---------------------------------------------------------------
+...
+# this will result in a failure because the assertion fails... plus everyone
+# knows batman really exists
+def test_bam_zap_sock_pow
+ batman = nil
+ assert_not_nil batman
+end
+
+# this will result in an error because it contains an undefined symbol
+def test_uh_oh_hotdog
+ assert_not_nil does_this_var_speak_korean
+end
+...
+---------------------------------------------------------------
+
+Ok... Let's run this puppy.
+
+---------------------------------------------------------------
+Started
+F..E
+Finished in 0.07 seconds.
+
+1) Failure:
+test_bam_zap_sock_pow(HelloTestCase) [/example/test.rb:62]:
+ expected to not be nil.
+
+2) Error:
+test_uh_oh_hotdog(HelloTestCase):
+NameError: undefined local variable or method 'does_this_var_speak_korean' for
+# /example/test.rb:68:in 'test_uh_oh_hotdog'
+
+4 tests, 12 assertions, 1 failures, 1 errors
+---------------------------------------------------------------
+
+Wow. Nasty.
+
+Take a look at the 2nd line. This time we have 'F..E' which means failure, pass, pass, error. In the details underneath the total elapsed time, you see what exactly went wrong and where.
+
+The incredibly observant will notice that even though the two new methods were added as the 3rd and 4th tests, they showed up in the results as 1st and 4th. That's because the tests are sorted alphabetically.
+
+So, that's what errors and failures look like. The difference is, a failure represents an assertion attempt that gave us the wrong results whereas an error is a Ruby problem.
+
+
+=== This Side Up ^ ===
+
+Another thing about assertions. They're fragile. If the test finds an assertion that fails, it will stop execution of that method and move on to the next test.
+
+If I had a test with 4 assertions, of which numbers 2, 3, and 4 were all going to fail, you'd only see #2 as the cause of the failed test. If you were to correct that failure, then rerun the test, it would be #3 thats causes grief.
+
+Got it? Good, there will be a pop quiz on Monday.
+
+
+== The Test Case Life Cycle ==
+
+=== 4.1 A Quick Recap ===
+
+You already saw a simple test case in action. It looked something like this:
+
+[source,ruby]
+---------------------------------------------------------
+require 'test/unit'
+
+class MyTestCase < Test::Unit::TestCase
+
+ def test_1
+ assert true
+ end
+
+ def test_2
+ end
+
+ def test_3
+ end
+
+end
+---------------------------------------------------------
+
+You saw that:
+
+ * we need to use `require ‘test/unit'`
+ * we need to inherit from `Test::Unit::TestCase`
+ * we need to define methods that start with `test`
+ * we need assertions to prove our code works
+
+Next, we're going to look at the flow of how test cases are run.
+
+
+=== The Flow ===
+
+When a test case is run, the testing framework creates a ‘fresh' object before running each test. That allows the test to not have to worry about what state the other test methods leave the object in. So for the above example, the testing flow looks like this when run:
+
+ * an object of class `MyTestCase` is created
+ * method `test_1` is run
+ * the test case object is destroyed
+
+then...
+
+ * a brand new object of class `MyTestCase` is created
+ * method `test_2` is run
+ * the test case object is destroyed
+
+then...
+
+ * one last object of class `MyTestCase` is created
+ * method `test_3` is run
+ * the test case object is destroyed
+
+
+=== Setup and Teardown Exposed ===
+
+Now, there are 2 special methods that you can use to hook into this process. One is called setup and the other is called teardown.
+
+Let's rewrite our test.
+
+[source,ruby]
+------------------------------------------
+require 'test/unit'
+
+class MyTestCase < Test::Unit::TestCase
+
+ # called before every single test
+ def setup
+ @name = 'jimmy'
+ @age = 150
+ end
+
+ # called after every single test
+ def teardown
+ end
+
+ # our tests
+ def test_1
+ assert true
+ end
+
+ def test_2
+ end
+
+ def test_3
+ end
+end
+------------------------------------------
+
+The `setup` method will always be called just before the test method. Comparatively, the `teardown` method will always be called always be called just after the test method. So now, the flow looks like this:
+
+ * an object of class `MyTestCase` is created
+ * method `setup` is run
+ * method `test_1` is run
+ * method `teardown` is run
+ * the test case object is destroyed
+
+then...
+
+ * a brand new object of class `MyTestCase` is created
+ * method `setup` is run
+ * method `test_2` is run
+ * method `teardown` is run
+ * the test case object is destroyed
+
+then...
+
+ * one last object of class `MyTestCase` is created
+ * method `setup` is run
+ * method `test_3` is run
+ * method `teardown` is run
+ * the test case object is destroyed
+
+What can you do with this? Well, the `setup` method is good for stuff like creating objects that each method uses. For example, maybe we need to create a user and populate her with sample data before running each of the tests?
+
+As you'll see later, Rails uses the `setup` and `teardown` methods extensively.
+
+
+== Hey Test/Unit. Assert This! ==
+
+=== The Assertion Lineup ===
+
+By now you've caught a glimpse of some of the assertions that are available. Assertions are the worker bees of testing. They are the ones that actually perform the checks to ensure things are going as planned.
+
+There are a bunch of different types of assertions you can use. Here's the complete list of assertions that ship with test/unit. The [msg] is an optional string message you can specify to make your test failure messages clearer. It's not required.
+
+`assert( boolean, [msg] )`::
+ Ensures that the object/expression is true.
+
+`assert_equal( obj1, obj2, [msg] )`::
+ Ensures that `obj1 == obj2` is true.
+
+`assert_not_equal( obj1, obj2, [msg] )`::
+ Ensures that `obj1 == obj2` is false.
+
+`assert_same( obj1, obj2, [msg] )`::
+ Ensures that `obj1.equal?(obj2)` is true.
+
+`assert_not_same( obj1, obj2, [msg] )`::
+ Ensures that `obj1.equal?(obj2)` is false.
+
+`assert_nil( obj, [msg] )`::
+ Ensures that `obj.nil?` is true.
+
+`assert_not_nil( obj, [msg] )`::
+ Ensures that `obj.nil?` is false.
+
+`assert_match( regexp, string, [msg] )`::
+ Ensures that a string matches the regular expression.
+
+`assert_no_match( regexp, string, [msg] )`::
+ Ensures that a string doesn't matches the regular expression.
+
+`assert_in_delta( expecting, actual, delta, [msg] )`::
+ Ensures that the numbers `expecting` and `actual` are within `delta` of each other.
+
+`assert_throws( symbol, [msg] ) { block }`::
+ Ensures that the given block throws the symbol.
+
+`assert_raises( exception1, exception2, ... ) { block }`::
+ Ensures that the given block raises one of the given exceptions.
+
+`assert_nothing_raised( exception1, exception2, ... ) { block }`::
+ Ensures that the given block doesn't raise one of the given exceptions.
+
+`assert_instance_of( class, obj, [msg] )`::
+ Ensures that `obj` is of the `class` type.
+
+`assert_kind_of( class, obj, [msg] )`::
+ Ensures that `obj` is or descends from `class`.
+
+`assert_respond_to( obj, symbol, [msg] )`::
+ Ensures that obj has a method called symbol.
+
+`assert_operator( obj1, operator, obj2, [msg] )`::
+ Ensures that `obj1.operator(obj2)` is true.
+
+`assert_send( array, [msg] )`::
+ Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?
+
+`flunk( [msg] )`::
+ Ensures failure... like me and high school chemistry exams.
+
+Because of the modular nature of the testing framework, it is possible to create your own assertions. In fact, that's exactly what Rails does. It has some specialized assertions to make your life easier.
+
+Creating your own assertions is more of an advanced topic we won't cover in this tutorial.
+
+
+== The Rails Fly-By ==
+
+=== It's About Frikkin' Time ===
+
+Finally we get to testing in with Ruby on Rails! By this point, I'm going to assume a few things.
+
+ * you're familiar with the basic building blocks of 'test/unit'
+ * you know that there are a bunch of assertions you can use
+ * you know what the special methods `setup` and `teardown` are
+ * you're conscious
+
+Rails promotes testing. The Ruby on Rails framework itself was built using these testing methodologies.
+
+In fact, the features built-in to Rails makes it exceptionally easy to do testing. For every model and controller you create using the `script/generate model` and `script/generate controller` scripts, corresponding test stubs are created.
+
+=== Where They Live ===
+
+Your tests, quite surprisingly, go in your 'test' directory under your rails application. In the test directory, you'll see 4 sub-directories; one for controller tests ('functional'), one for model tests ('unit'), one for mock objects ('mocks') and one that only holds sample data ('fixtures').
+
+ * test
+ - /unit
+ - /functional
+ - /fixtures
+ - /mocks
+
+=== How to Turn Them On ===
+
+To run these tests, you simply run the test script directly:
+
+---------------------------------------------
+ruby test/unit/my_good_old_test_unit.rb
+---------------------------------------------
+
+Another way to run your tests is to have the main rakefile run it for you. `rake test_functional` will run all your controller tests and `rake test_units` will run all your model tests.
+
+=== The 3 Environments ===
+
+Testing support was woven into the Rails fabric from the beginning. It wasn't a ``oh! let's bolt on support for running tests because they're new and cool'' epiphany.
+
+Each Rails application you build has 3 sides. A side for production, a side for development and a side for testing.
+
+Let's take a closer look at the Rails 'config/database.yml' file. This YAML configuration file has 3 different sections defining 3 unique database setups:
+
+ * production
+ * development
+ * test
+
+Why 3 different databases? Well, there's one for testing where you can use sample data, there's one for development where you'll be most of the time as you develop your application, and then production for the ``real deal'', or when it goes live.
+
+Every new Rails application should have these 3 sections filled out. They should point to different databases. You may end up not having a local database for your production environment, but development and test should both exist and be different.
+
+=== Why Make This Distinction? ===
+
+If you stop and think about it for a second, it makes sense.
+
+By segregating your development database and your testing database, you're not in any danger of losing any data where it matters.
+
+For example, you need to test your new `delete_this_user_and_every_everything_associated_with_it` function. Wouldn't you want to run this in an environment which makes no difference if you destroy data or not?
+
+When you do end up destroying your testing database (and it will happen, trust me), simply run a task in your rakefile to rebuild it from scratch according to the specs defined in the development database. You can do this by running `rake db:test:prepare`.
+
+
+== The Lo-Down on Fixtures ==
+
+=== What They Are ===
+
+The structure is one thing, but what about when I want to automatically create sample data?
+
+Enter fixtures. Fixtures is a fancy word for ‘sample data'. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and assume one of two formats: *YAML* or *CSV*.
+
+You'll find fixtures under your 'test/fixtures' directory. When you run `script/generate model` to create a new model, fixture stubs will be automatically created and placed in this directory.
+
+=== YAML the Camel is a Mammal with Enamel ===
+
+YAML-formatted fixtures are a very human-friendly way to describe your sample data. These types of fixtures have the *.yml* file extension (as in 'users.yml').
+
+On any given Sunday, a YAML fixture file may look like this:
+
+---------------------------------------------
+# low & behold! I am a YAML comment!
+david:
+ id: 1
+ name: David Heinemeier Hansson
+ birthday: 1979-10-15
+ profession: Systems development
+
+steve:
+ id: 2
+ name: Steve Ross Kellock
+ birthday: 1974-09-27
+ profession: guy with keyboard
+---------------------------------------------
+
+Each fixture is given a 'name' followed by an indented list of colon-separated key/value pairs. Records are separated by a blank space. You can place comments by using the # character in the first column.
+
+=== Comma Seperated ===
+
+Fixtures can also be described using the all-too-familiar comma-separated value file format. These files, just like YAML fixtures are placed in the 'test/fixtures directory', but these end with the *.csv* file extension (as in 'celebrity_holiday_figures.csv').
+
+A CSV fixture looks like this:
+
+--------------------------------------------------------------
+id, username, password, stretchable, comments
+1, sclaus, ihatekids, false, I like to say ""Ho! Ho! Ho!""
+2, ebunny, ihateeggs, true, Hoppity hop y'all
+3, tfairy, ilovecavities, true, "Pull your teeth, I will"
+--------------------------------------------------------------
+
+The first line is the header. It is a comma-separated list of fields. The rest of the file is the payload: 1 record per line. A few notes about this format:
+
+ * each cell is stripped of outward facing spaces
+ * if you use a comma as data, the cell must be encased in quotes
+ * if you use a quote as data, you must escape it with a 2nd quote
+ * don't use blank lines
+ * nulls can be achived by just placing a comma, for example, (1,sclaus,,false,) minus the parenthesis of course.
+
+Unlike the YAML format where you give each fixture a name, CSV fixture names are automatically generated. They follow a pattern of ``model-name''-''counter''. In the above example, you would have:
+
+--------------------------------------------------------------
+celebrity-holiday-figures-1
+celebrity-holiday-figures-2
+celebrity-holiday-figures-3
+--------------------------------------------------------------
+
+The CSV format is great to use if you have existing data in a spreadsheet or database and you are able to save it (or export it) as a CSV.
+
+=== ERb'in It Up ===
+
+ERb allows you embed ruby code within templates. Both the YAML and CSV fixture formats are pre-processed with ERb. This allows you to use Ruby to help you generate some sample data.
+
+I'll demonstrate with a YAML file:
+
+--------------------------------------------------------------
+<% earth_size = 20 -%>
+mercury:
+ id: 1
+ size: <%= earth_size / 50 %>
+
+venus:
+ id: 2
+ size: <%= earth_size / 2 %>
+
+mars:
+ id: 3
+ size: <%= earth_size - 69 %>
+--------------------------------------------------------------
+
+Anything encased within the
+
+-------------
+<% -%>
+-------------
+
+tag is considered Ruby code. To actually print something out, you must use the
+
+-------------
+<%= %>
+-------------
+
+tag.
+
+=== Fixtures in Action ===
+
+Rails makes no assumptions when it comes to fixtures. You must explicitly load them yourself by using the fixtures method within your TestCase. For example, a users model unit test might look like this:
+
+[source, ruby]
+--------------------------------------------------------------
+# Allow this test to hook into the Rails framework.
+require File.dirname(__FILE__) + '/../test_helper'
+
+class UserTest < Test::Unit::TestCase
+ fixtures :users
+
+ # Count the fixtures.
+ def test_count_my_fixtures
+ assert_equal 5, User.count
+ end
+end
+--------------------------------------------------------------
+
+Using the fixtures method and placing the symbol name of the model, Rails will automatically load up the fixtures for you at the start of each test method.
+
+[source, ruby]
+--------------------------------------------------------------
+fixtures :users
+--------------------------------------------------------------
+
+What exactly does this line of code do? It does 3 things:
+
+ * it nukes any existing data living in the users table
+* it loads the fixture data (if any) into the users table
+ * it dumps the data into a variable in case you want to access it directly
+
+So, in the above example, if we had another test method, we wouldn't have 10 users on the 2nd test because they would be wiped out before being created.
+
+You can load multiple fixtures by including them on the same line separated by commas.
+
+[source, ruby]
+--------------------------------------------------------------
+fixtures :users, :losers, :bruisers, :cruisers
+--------------------------------------------------------------
+
+=== Hashes with Special Powers ===
+
+Fixtures are basically Hash objects. As mentioned in point #3 above, you can access the hash object directly because it is automatically setup as a local variable of the test case.
+
+[source, ruby]
+--------------------------------------------------------------
+...
+ fixtures :users
+
+ def test_user
+ # this will return the Hash for the fixture named david
+ users(:david)
+
+ # this will return the property for david called id
+ users(:david).id
+ end
+...
+--------------------------------------------------------------
+
+But, by there's another side to fixtures... at night, if the moon is full and the wind completely still, fixtures can also transform themselves into the form of the original class!
+
+Now you can get at the methods only available to that class.
+
+[source, ruby]
+--------------------------------------------------------------
+...
+ fixtures :users
+
+ def test_user
+ # using the find method, we grab the "real" david as a User
+ david = users(:david).find
+
+ # an now we have access to methods only available to a User class
+ email( david.girlfriend.email, david.illegitimate_children )
+ end
+...
+--------------------------------------------------------------
+
+
+== Testing Your Models ==
+
+=== A Unit Test ===
+
+Unit tests are what we use to test our models. Generally, there is one test for each model. The test stubs are created automatically when you use `script/generate model SecretAgent` (for example).
+
+Let's take a look at what a unit test might look like.
+
+[source, ruby]
+-------------------------------------------------------
+# hook into the Rails environment
+require File.dirname(__FILE__) + '/../test_helper'
+
+class SecretAgent < ActiveSupport::TestCase
+ fixtures :secret_agents
+
+ # ensure the SecretAgent plays well with the database
+ def test_create_read_update_delete
+ # create a brand new secret agent
+ jimmy = SecretAgent.new("jagent", "unbelievablysecretpassword")
+
+ # save him
+ assert jimmy.save
+
+ # read him back
+ agent = SecretAgent.find(jimmy.id)
+
+ # compare the usernames
+ assert_equal jimmy.username, agent.username
+
+ # change the password by using hi-tech encryption
+ agent.username = agent.username.reverse
+
+ # save the changes
+ assert agent.save
+
+ # the agent gets killed
+ assert agent.destroy
+ end
+end
+-------------------------------------------------------
+
+In this basic unit test, we're exercising the 'SecretAgent' model. In our solitary test, we're proving a bunch of things about our 'SecretAgent' model. We try 4 different assertions to test that we can do the basics with the database such as create, read, update and delete (creatively known as CRUD).
+
+It is up to you to decide just how much you want to test of your model. Ideally you test anything that could possibly break, however, it is really trial and error. Only you know what's best to test.
+
+You most certainly want to test the validation code, and additionally, you probably want a least 1 test for every method in your model.
+
+
+== Testing Your Controllers ==
+
+=== What Is It? ===
+
+The goal of functional testing is to test your controllers. When you get into the realm of testing controllers, we're operating at a higher level than the model. At this level, we test for things such as:
+
+ * was the web request successful?
+ * were we redirected to the right page?
+ * were we successfully authenticated?
+ * was the correct object stored in the response template?
+
+Just as there is a one-to-one ratio between unit tests and models, so there is between functional tests and controllers. For a controller named `HomeController`, you would have a test case named `HomeControllerTest`.
+
+=== An Anatomy Lesson ===
+
+So let's take a look at an example of a functional test.
+
+[source, ruby]
+----------------------------------------------------
+require File.dirname(__FILE__) + '/../test_helper'
+
+class HomeControllerTest < ActionController::TestCase
+ # let's test our main index page
+ def test_index
+ get :index
+ assert_response :success
+ end
+end
+----------------------------------------------------
+
+==== Making the moves ====
+
+In the one test we have called `test_index`, we are simulating a request on the action called index and making sure the request was successful.
+
+The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments.
+
+ * The action of the controller you are requesting. It can be in the form of a string or a symbol. Cool people use symbols. ;)
+ * An optional hash of request parameters to pass into the action (eg. query string parameters or post variables).
+ * An optional hash of session variables to pass along with the request.
+ * An optional hash of flash to stash your goulash.
+
+*Example:* Calling the `:show` action, passing an `id` of 12 as the params and setting `user_id` of 5 in the session.
+
+[source, ruby]
+----------------------------------------------------
+get(:show, {'id' => "12"}, {'user_id' => 5})
+----------------------------------------------------
+
+*Another example:* Calling the `:view` action, passing an `id` of 12 as the params, this time with no session, but with a flash message.
+
+[source, ruby]
+----------------------------------------------------
+get(:view, {'id' => '12'}, nil, {'message' => 'booya!'})
+----------------------------------------------------
+
+==== Available at your disposal ====
+
+For those of you familiar with HTTP protocol, you'll know that get is a type of request. There are 5 request types supported in Rails:
+
+ * get
+ * post
+ * put
+ * head
+ * delete
+
+All of request types are methods that you can use, however, you'll probably end up using the first two more ofter than the others.
+
+=== The 4 Hashes of the Apocolypse ===
+
+After the request has been made by using one of the 5 methods (get, post, etc...), you will have 4 Hash objects ready for use.
+
+They are (starring in alphabetical order):
+
+`assigns`::
+ Any objects that are stored as instance variables in actions for use in views.
+`cookies`::
+ Any objects cookies that are set.
+`flash`::
+ Any objects living in the flash.
+`session`::
+ Any object living in session variables.
+
+For example, let's say we have a `MoviesController` with an action called `movie`. The code for that action might look something like:
+
+[source, ruby]
+----------------------------------------------------
+def movie
+ @movie = Movie.find(params[:id])
+ if @movie.nil?
+ flash['message'] = "That movie has been burned."
+ redirect_to :controller => 'error', :action => 'missing'
+ end
+end
+----------------------------------------------------
+
+Now, to test out if the proper movie is being set, we could have a series of tests that look like this:
+
+[source, ruby]
+----------------------------------------------------
+# this test proves that fetching a movie works
+def test_successfully_finding_a_movie
+ get :movie, "id" => "1"
+ assert_not_nil assigns["movie"]
+ assert_equal 1, assigns["movie"].id
+ assert flash.empty?
+end
+
+# and when we can't find a movie...
+def test_movie_not_found
+ get :movie, "id" => "666999"
+ assert_nil assigns["movie"]
+ assert flash.has_key?("message")
+ assert assigns.empty?
+end
+----------------------------------------------------
+
+As is the case with normal Hash objects, you can access the values by referencing the keys by string. You can also reference them by symbol name... except assigns. Check it out:
+
+[source, ruby]
+----------------------------------------------------
+flash["gordon"] flash[:gordon]
+session["shmession"] session[:shmession]
+cookies["are_good_for_u"] cookies[:are_good_for_u]
+
+# Because you can't use assigns[:something] for historical reasons:
+assigns["something"] assigns(:something)
+----------------------------------------------------
+
+Keep an eye out for that. mmmm kay?
+
+=== Response-Related Assertions ===
+
+There are 3 assertions that deal with the overall response to a request. They are:
+
+`assert_template( expected_template, [msg] )`::
+Ensures the expected template was responsible for rendering. For example:
++
+[source, ruby]
+--------------------------------------
+assert_template "user/profile"
+--------------------------------------
++
+This code will fail unless the template located at app/views/user/profile.rhtml was rendered.
+
+`assert_response( type_or_code, [msg] )`::
+Ensures the response type/status code is as expected. For example:
++
+[source, ruby]
+--------------------------------------
+assert_response :success # page rendered ok
+assert_response :redirect # we've been redirected
+assert_response :missing # not found
+assert_response 505 # status code was 505
+--------------------------------------
++
+The possible options are:
++
+ * `:success` (status code is 200)
+ * `:redirect` (status code is within 300..399)
+ * `:missing` (status code is 404)
+ * `:error` (status code is within 500..599)
+ * any number (to specifically reference a particular status code)
+
+`assert_redirected_to ( options={}, [msg] )`::
+Ensures we've been redirected to a specific place within our application.
++
+[source, ruby]
+--------------------------
+assert_redirected_to :controller => 'widget', :action => 'view', :id => 555
+--------------------------
+
+=== Tag-Related Assertions ===
+
+The assert_tag and assert_no_tag assertions are for analysing the html returned from a request.
+
+==== assert_tag( options ) ====
+Ensures that a tag or text exists. There are a whole whack o' options you can use to discover what you are looking for. Some of the conditions are like XPATH in concept, but this is sexier. In fact, let's call it SEXPATH.
+
+The following description is lifted verbatim from the rails assertion docs.
+
+Asserts that there is a tag/node/element in the body of the response that meets all of the given conditions. The conditions parameter must be a hash of any of the following keys (all are optional):
+
+ * `:tag` : the node type must match the corresponding value
+ * `:attributes` : a hash. The node's attributes must match the corresponding values in the hash.
+ * `:parent` : a hash. The node's parent must match the corresponding hash.
+ * `:child` : a hash. At least one of the node's immediate children must meet the criteria described by the hash.
+ * `:ancestor` : a hash. At least one of the node's ancestors must meet the criteria described by the hash.
+ * `:descendant` : a hash. At least one of the node's descendants must meet the criteria described by the hash.
+ * `:children` : a hash, for counting children of a node. Accepts the keys:
+ - `:count` : either a number or a range which must equal (or include) the number of children that match.
+ - `:less_than` : the number of matching children must be less than this number.
+ - `:greater_than` : the number of matching children must be greater than this number.
+ - `:only` : another hash consisting of the keys to use to match on the children, and only matching children will be counted.
+ - `:content` : (text nodes only). The content of the node must match the given value.
+
+Conditions are matched using the following algorithm:
+
+ * if the condition is a *string*, it must be a substring of the value.
+ * if the condition is a *regexp*, it must match the value.
+ * if the condition is a *number*, the value must match `number.to_s`.
+ * if the condition is *true*, the value must not be `nil`.
+ * if the condition is *false* or *nil*, the value must be `nil`.
+
+These examples are taken from the same docs too:
+
+[source, ruby]
+-------------------------------------------------------------
+ # assert that there is a "span" tag
+ assert_tag :tag => "span"
+
+ # assert that there is a "span" inside of a "div"
+ assert_tag :tag => "span", :parent => { :tag => "div" }
+
+ # assert that there is a "span" somewhere inside a table
+ assert_tag :tag => "span", :ancestor => { :tag => "table" }
+
+ # assert that there is a "span" with at least one "em" child
+ assert_tag :tag => "span", :child => { :tag => "em" }
+
+ # assert that there is a "span" containing a (possibly nested)
+ # "strong" tag.
+ assert_tag :tag => "span", :descendant => { :tag => "strong" }
+
+ # assert that there is a "span" containing between 2 and 4 "em" tags
+ # as immediate children
+ assert_tag :tag => "span",
+ :children => { :count => 2..4, :only => { :tag => "em" } }
+
+ # get funky: assert that there is a "div", with an "ul" ancestor
+ # and an "li" parent (with "class" = "enum"), and containing a
+ # "span" descendant that contains text matching /hello world/
+ assert_tag :tag => "div",
+ :ancestor => { :tag => "ul" },
+ :parent => { :tag => "li",
+ :attributes => { :class => "enum" } },
+ :descendant => { :tag => "span",
+ :child => /hello world/ }
+-------------------------------------------------------------
+
+==== assert_no_tag( options ) ====
+This is the exact opposite of assert_tag. It ensures that the tag does not exist.
+
+=== Routing-Related Assertions ===
+
+==== assert_generates( expected_path, options, defaults={}, extras = {}, [msg] ) ====
+Ensures that the options map to the expected_path.
+
+[source, ruby]
+-------------------------------------------------------------
+opts = {:controller => "movies", :action => "movie", :id => "69"}
+assert_generates "movies/movie/69", opts
+-------------------------------------------------------------
+
+==== assert_recognizes( expected_options, path, extras={}, [msg] ) ====
+Ensures that when the path is chopped up into pieces, it is equal to expected_options. Essentially, the opposite of assert_generates.
+
+[source, ruby]
+-------------------------------------------------------------
+opts = {:controller => "movies", :action => "movie", :id => "69"}
+assert_recognizes opts, "/movies/movie/69
+
+# also, let's say i had a line in my config/routes.rb
+# that looked like:
+#
+# map.connect (
+# 'calendar/:year/:month',
+# :controller => 'content',
+# :action => 'calendar',
+# :year => nil,
+# :month => nil,
+# :requirements => {:year => /\d{4}/, :month => /\d{1,2}/}
+# }
+#
+# Then, this would work too:
+opts = {
+ :controller => 'content',
+ :action => 'calendar',
+ :year => '2005',
+ :month => '5'
+}
+assert_recognizes opts, 'calendar/2005/5'
+-------------------------------------------------------------
+
+==== assert_routing( path, options, defaults={}, extras={}, [msg] ) ====
+Ensures that the path resolves into options, and the options, resolves into path. It's a two-way check to make sure your routing maps work as expected.
+
+This assertion is simply a wrapper around `assert_generates` and `assert_recognizes`.
+
+If you're going to test your routes, this assertion might be your best bet for robustness (yes, the overused buzzword of the 90's).
+
+[source, ruby]
+-------------------------------------------------------------
+opts = {:controller => "movies", :action => "movie", :id => "69"}
+assert_routing "movies/movie/69", opts
+-------------------------------------------------------------
+
+=== Testing File Uploads ===
+So your web app supports file uploads eh? Here's what you can do to test your uploads.
+
+This tip is brought to you by Chris Brinker, the letter R and the number 12.
+
+Chris says, '``In order to test a file being uploaded you have to mirror what cgi.rb is doing with a multipart post. Unfortunately what it does is quite long and complex, this code takes a file on your system, and turns it into what normally comes out of cgi.rb.'''
+
+Here are some helper methods based on Chris' work that you'll need to squirrel away either in a new unit, or cut ‘n' pasted right into your test. Any errors with this are my fault.
+
+[source, ruby]
+-------------------------------------------------------------
+# get us an object that represents an uploaded file
+def uploaded_file(path, content_type="application/octet-stream", filename=nil)
+ filename ||= File.basename(path)
+ t = Tempfile.new(filename)
+ FileUtils.copy_file(path, t.path)
+ (class << t; self; end;).class_eval do
+ alias local_path path
+ define_method(:original_filename) { filename }
+ define_method(:content_type) { content_type }
+ end
+ return t
+end
+
+# a JPEG helper
+def uploaded_jpeg(path, filename=nil)
+ uploaded_file(path, 'image/jpeg', filename)
+end
+
+# a GIF helper
+def uploaded_gif(path, filename=nil)
+ uploaded_file(path, 'image/gif', filename)
+end
+-------------------------------------------------------------
+
+And to use this code, you'd have a test that would looks something like this:
+
+[source, ruby]
+-------------------------------------------------------------
+def test_a_file_upload
+ assert_equal 0, GalleryImage.count
+ heman = uploaded_jpeg("#{File.expand_path(RAILS_ROOT)}/text/fixtures/heman.jpg")
+ post :imageupload, 'imagefile' => heman
+ assert_redirected_to :controller => 'gallery', :action => 'view'
+ assert_equal 1, GalleryImage.count
+end
+-------------------------------------------------------------
+
+
+== Testing Your Mailers ==
+
+=== Keeping the postman in check ===
+
+Your ActionMailer -- like every other part of your Rails application -- should be tested to ensure that it is working as expected.
+
+The goal of testing your ActionMailer is to ensure that:
+
+ * emails are being processed (created and sent)
+ * the email content is correct (subject, sender, body, etc)
+ * the right emails are being sent at the righ times
+
+==== From all sides ====
+
+There are two aspects of testing your mailer, the unit tests and the functional tests.
+Unit tests
+
+In the unit tests, we run the mailer in isolation with tightly controlled inputs and compare the output to a known-value -- a fixture -- yay! more fixtures!
+
+==== Functional tests ====
+
+In the functional tests we don't so much test the minute details produced by the mailer, instead we test that our controllers and models are using the mailer in the right way. We test to prove that the right email was sent at the right time.
+
+=== Unit Testing ===
+
+In order to test that your mailer is working as expected, we can use unit tests to compare the actual results of the mailer with pre-writen examples of what should be produced.
+
+==== Revenge of the fixtures ====
+
+For the purposes of unit testing a mailer, fixtures are used to provide an example of how output ``should'' look. Because these are example emails, and not Active Record data like the other fixtures, they are kept in their own subdirectory from the other fixtures. Don't tease them about it though, they hate that.
+
+When you generated your mailer (you did that right?) the generator created stub fixtures for each of the mailers actions. If you didn't use the generator you'll have to make those files yourself.
+
+==== The basic test case ====
+
+Here is an example of what you start with.
+
+[source, ruby]
+-------------------------------------------------
+require File.dirname(__FILE__) + '/. ./test_helper'
+
+class MyMailerTest < Test::Unit::TestCase
+ FIXTURES_PATH = File.dirname(__FILE__) + '/. ./fixtures'
+
+ def setup
+ ActionMailer::Base.delivery_method = :test
+ ActionMailer::Base.perform_deliveries = true
+ ActionMailer::Base.deliveries = []
+
+ @expected = TMail::Mail.new
+ end
+
+ def test_signup
+ @expected.subject = 'MyMailer#signup'
+ @expected.body = read_fixture('signup')
+ @expected.date = Time.now
+
+ assert_equal @expected.encoded, MyMailer.create_signup(@expected.date).encoded
+ end
+
+ private
+ def read_fixture(action)
+ IO.readlines("#{FIXTURES_PATH}/my_mailer/#{action}")
+ end
+end
+-------------------------------------------------
+
+The `setup` method is mostly concerned with setting up a blank slate for the next test. However it is worth describing what each statement does
+
+[source, ruby]
+-------------------------------------------------
+ActionMailer::Base.delivery_method = :test
+-------------------------------------------------
+
+sets the delivery method to test mode so that email will not actually be delivered (useful to avoid spamming your users while testing) but instead it will be appended to an array (ActionMailer::Base.deliveries).
+
+[source, ruby]
+-------------------------------------------------
+ActionMailer::Base.perform_deliveries = true
+-------------------------------------------------
+
+Ensures the mail will be sent using the method specified by ActionMailer::Base.delivery_method, and finally
+
+[source, ruby]
+-------------------------------------------------
+ActionMailer::Base.deliveries = []
+-------------------------------------------------
+
+sets the array of sent messages to an empty array so we can be sure that anything we find there was sent as part of our current test.
+
+However often in unit tests, mails will not actually be sent, simply constructed, as in the example above, where the precise content of the email is checked against what it should be. Dave Thomas suggests an alternative approach, which is just to check the part of the email that is likely to break, i.e. the dynamic content. The following example assumes we have some kind of user table, and we might want to mail those users new passwords:
+
+[source, ruby]
+-------------------------------------------------
+require File.dirname(__FILE__) + '/../test_helper'
+require 'my_mailer'
+
+class MyMailerTest < Test::Unit::TestCase
+ fixtures :users
+ FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures'
+ CHARSET = "utf-8"
+
+ include ActionMailer::Quoting
+
+ def setup
+ ActionMailer::Base.delivery_method = :test
+ ActionMailer::Base.perform_deliveries = true
+ ActionMailer::Base.deliveries = []
+
+ @expected = TMail::Mail.new
+ @expected.set_content_type "text", "plain", { "charset" => CHARSET }
+ end
+
+ def test_reset_password
+ user = User.find(:first)
+ newpass = 'newpass'
+ response = MyMailer.create_reset_password(user,newpass)
+ assert_equal 'Your New Password', response.subject
+ assert_match /Dear #{user.full_name},/, response.body
+ assert_match /New Password: #{newpass}/, response.body
+ assert_equal user.email, response.to[0]
+ end
+
+ private
+ def read_fixture(action)
+ IO.readlines("#{FIXTURES_PATH}/community_mailer/#{action}")
+ end
+
+ def encode(subject)
+ quoted_printable(subject, CHARSET)
+ end
+end
+-------------------------------------------------
+
+and here we check the dynamic parts of the mail, specifically that we use the users' correct full name and that we give them the correct password.
+
+=== Functional Testing ===
+
+Functional testing involves more than just checking that the email body, recipients and so forth are correct. In functional mail tests we call the mail deliver methods and check that the appropriate emails have been appended to the delivery list. It is fairly safe to assume that the deliver methods themselves do their job -- what we are probably more interested in is whether our own business logic is sending emails when we expect them to. For example the password reset operation we used an example in the previous section will probably be called in response to a user requesting a password reset through some sort of controller.
+
+[source, ruby]
+----------------------------------------------------------------
+require File.dirname(__FILE__) + '/../test_helper'
+require 'my_controller'
+
+# Raise errors beyond the default web-based presentation
+class MyController; def rescue_action(e) raise e end; end
+
+class MyControllerTest < Test::Unit::TestCase
+
+ def setup
+ @controller = MyController.new
+ @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
+ end
+
+ def test_reset_password
+ num_deliveries = ActionMailer::Base.deliveries.size
+ post :reset_password, :email => 'bob@test.com'
+
+ assert_equal num_deliveries+1, ActionMailer::Base.deliveries.size
+ end
+
+end
+----------------------------------------------------------------
+
+=== Filtering emails in development ===
+
+Sometimes you want to be somewhere inbetween the `:test` and `:smtp` settings. Say you're working on your development site, and you have a few testers working with you. The site isn't in production yet, but you'd like the testers to be able to receive emails from the site, but no one else. Here's a handy way to handle that situation, add this to your 'environment.rb' or 'development.rb' file
+
+[source, ruby]
+----------------------------------------------------------------
+class ActionMailer::Base
+
+ def perform_delivery_fixed_email(mail)
+ destinations = mail.destinations
+ if destinations.nil?
+ destinations = ["mymail@me.com"]
+ mail.subject = '[TEST-FAILURE]:'+mail.subject
+ else
+ mail.subject = '[TEST]:'+mail.subject
+ end
+ approved = ["testerone@me.com","testertwo@me.com"]
+ destinations = destinations.collect{|x| approved.collect{|y| (x==y ? x : nil)}}.flatten.compact
+ mail.to = destinations
+ if destinations.size > 0
+ mail.ready_to_send
+ Net::SMTP.start(server_settings[:address], server_settings[:port], server_settings[:domain],
+ server_settings[:user_name], server_settings[:password], server_settings[:authentication]) do |smtp|
+ smtp.sendmail(mail.encoded, mail.from, destinations)
+ end
+ end
+
+ end
+
+end
+----------------------------------------------------------------
+
--
cgit v1.2.3
From e0513e33c4da60255e7c1aa71babcc9414f26858 Mon Sep 17 00:00:00 2001
From: Joshua Peek
Date: Mon, 28 Jul 2008 13:38:20 -0500
Subject: Routing whitespace cleanup
---
.../assertions/routing_assertions.rb | 44 ++++++++--------
actionpack/lib/action_controller/routing.rb | 58 +++++++++++-----------
.../lib/action_controller/routing/optimisations.rb | 22 ++++----
.../routing/recognition_optimisation.rb | 2 -
actionpack/lib/action_controller/routing/route.rb | 15 +++---
.../lib/action_controller/routing/route_set.rb | 4 +-
.../lib/action_controller/routing/routing_ext.rb | 1 -
.../lib/action_controller/routing/segments.rb | 5 +-
actionpack/test/controller/routing_test.rb | 1 +
actionpack/test/controller/test_test.rb | 21 ++++----
10 files changed, 84 insertions(+), 89 deletions(-)
diff --git a/actionpack/lib/action_controller/assertions/routing_assertions.rb b/actionpack/lib/action_controller/assertions/routing_assertions.rb
index 491b72d586..312b4e228b 100644
--- a/actionpack/lib/action_controller/assertions/routing_assertions.rb
+++ b/actionpack/lib/action_controller/assertions/routing_assertions.rb
@@ -2,7 +2,7 @@ module ActionController
module Assertions
# Suite of assertions to test routes generated by Rails and the handling of requests made to them.
module RoutingAssertions
- # Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
+ # Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
# match +path+. Basically, it asserts that Rails recognizes the route given by +expected_options+.
#
# Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
@@ -14,16 +14,16 @@ module ActionController
#
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
# to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
- # extras argument, appending the query string on the path directly will not work. For example:
+ # extras argument, appending the query string on the path directly will not work. For example:
#
# # assert that a path of '/items/list/1?view=print' returns the correct options
- # assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
+ # assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
#
- # The +message+ parameter allows you to pass in an error message that is displayed upon failure.
+ # The +message+ parameter allows you to pass in an error message that is displayed upon failure.
#
# ==== Examples
# # Check the default route (i.e., the index action)
- # assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
+ # assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
#
# # Test a specific action
# assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list')
@@ -44,16 +44,16 @@ module ActionController
request_method = nil
end
- clean_backtrace do
- ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
+ clean_backtrace do
+ ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
request = recognized_request_for(path, request_method)
-
+
expected_options = expected_options.clone
extras.each_key { |key| expected_options.delete key } unless extras.nil?
-
+
expected_options.stringify_keys!
routing_diff = expected_options.diff(request.path_parameters)
- msg = build_message(message, "The recognized options > did not match >, difference: >",
+ msg = build_message(message, "The recognized options > did not match >, difference: >",
request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
assert_block(msg) { request.path_parameters == expected_options }
end
@@ -64,7 +64,7 @@ module ActionController
# a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
#
# The +defaults+ parameter is unused.
- #
+ #
# ==== Examples
# # Asserts that the default action is generated for a route with no action
# assert_generates("/items", :controller => "items", :action => "index")
@@ -73,34 +73,34 @@ module ActionController
# assert_generates("/items/list", :controller => "items", :action => "list")
#
# # Tests the generation of a route with a parameter
- # assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" })
+ # assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" })
#
# # Asserts that the generated route gives us our custom route
# assert_generates "changesets/12", { :controller => 'scm', :action => 'show_diff', :revision => "12" }
def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
- clean_backtrace do
+ clean_backtrace do
expected_path = "/#{expected_path}" unless expected_path[0] == ?/
# Load routes.rb if it hasn't been loaded.
- ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
-
+ ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
+
generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, defaults)
found_extras = options.reject {|k, v| ! extra_keys.include? k}
msg = build_message(message, "found extras >, not >", found_extras, extras)
assert_block(msg) { found_extras == extras }
-
- msg = build_message(message, "The generated path > did not match >", generated_path,
+
+ msg = build_message(message, "The generated path > did not match >", generated_path,
expected_path)
assert_block(msg) { expected_path == generated_path }
end
end
- # Asserts that path and options match both ways; in other words, it verifies that path generates
+ # Asserts that path and options match both ways; in other words, it verifies that path generates
# options and then that options generates path . This essentially combines +assert_recognizes+
# and +assert_generates+ into one step.
#
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
- # +message+ parameter allows you to specify a custom error message to display upon failure.
+ # +message+ parameter allows you to specify a custom error message to display upon failure.
#
# ==== Examples
# # Assert a basic route: a controller with the default action (index)
@@ -119,12 +119,12 @@ module ActionController
# assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
def assert_routing(path, options, defaults={}, extras={}, message=nil)
assert_recognizes(options, path, extras, message)
-
- controller, default_controller = options[:controller], defaults[:controller]
+
+ controller, default_controller = options[:controller], defaults[:controller]
if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
options[:controller] = "/#{controller}"
end
-
+
assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message)
end
diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb
index dfbaa53b7c..8d51e823a6 100644
--- a/actionpack/lib/action_controller/routing.rb
+++ b/actionpack/lib/action_controller/routing.rb
@@ -201,7 +201,7 @@ module ActionController
# With conditions you can define restrictions on routes. Currently the only valid condition is :method .
#
# * :method - Allows you to specify which method can access the route. Possible values are :post ,
- # :get , :put , :delete and :any . The default value is :any ,
+ # :get , :put , :delete and :any . The default value is :any ,
# :any means that any method can access the route.
#
# Example:
@@ -213,7 +213,7 @@ module ActionController
#
# Now, if you POST to /posts/:id , it will route to the create_comment action. A GET on the same
# URL will route to the show action.
- #
+ #
# == Reloading routes
#
# You can reload routes if you feel you must:
@@ -281,9 +281,9 @@ module ActionController
end
class << self
- # Expects an array of controller names as the first argument.
- # Executes the passed block with only the named controllers named available.
- # This method is used in internal Rails testing.
+ # Expects an array of controller names as the first argument.
+ # Executes the passed block with only the named controllers named available.
+ # This method is used in internal Rails testing.
def with_controllers(names)
prior_controllers = @possible_controllers
use_controllers! names
@@ -292,10 +292,10 @@ module ActionController
use_controllers! prior_controllers
end
- # Returns an array of paths, cleaned of double-slashes and relative path references.
- # * "\\\" and "//" become "\\" or "/".
- # * "/foo/bar/../config" becomes "/foo/config".
- # The returned array is sorted by length, descending.
+ # Returns an array of paths, cleaned of double-slashes and relative path references.
+ # * "\\\" and "//" become "\\" or "/".
+ # * "/foo/bar/../config" becomes "/foo/config".
+ # The returned array is sorted by length, descending.
def normalize_paths(paths)
# do the hokey-pokey of path normalization...
paths = paths.collect do |path|
@@ -314,7 +314,7 @@ module ActionController
paths = paths.uniq.sort_by { |path| - path.length }
end
- # Returns the array of controller names currently available to ActionController::Routing.
+ # Returns the array of controller names currently available to ActionController::Routing.
def possible_controllers
unless @possible_controllers
@possible_controllers = []
@@ -339,28 +339,27 @@ module ActionController
@possible_controllers
end
- # Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
- # ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
+ # Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
+ # ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
def use_controllers!(controller_names)
@possible_controllers = controller_names
end
- # Returns a controller path for a new +controller+ based on a +previous+ controller path.
- # Handles 4 scenarios:
- #
- # * stay in the previous controller:
- # controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
- #
- # * stay in the previous namespace:
- # controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
- #
- # * forced move to the root namespace:
- # controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
- #
- # * previous namespace is root:
- # controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
- #
-
+ # Returns a controller path for a new +controller+ based on a +previous+ controller path.
+ # Handles 4 scenarios:
+ #
+ # * stay in the previous controller:
+ # controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
+ #
+ # * stay in the previous namespace:
+ # controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
+ #
+ # * forced move to the root namespace:
+ # controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
+ #
+ # * previous namespace is root:
+ # controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
+ #
def controller_relative_to(controller, previous)
if controller.nil? then previous
elsif controller[0] == ?/ then controller[1..-1]
@@ -369,12 +368,11 @@ module ActionController
end
end
end
-
Routes = RouteSet.new
ActiveSupport::Inflector.module_eval do
- # Ensures that routes are reloaded when Rails inflections are updated.
+ # Ensures that routes are reloaded when Rails inflections are updated.
def inflections_with_route_reloading(&block)
returning(inflections_without_route_reloading(&block)) {
ActionController::Routing::Routes.reload! if block_given?
diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb
index 4b70ea13f2..b44ebd5ca2 100644
--- a/actionpack/lib/action_controller/routing/optimisations.rb
+++ b/actionpack/lib/action_controller/routing/optimisations.rb
@@ -1,14 +1,14 @@
module ActionController
module Routing
- # Much of the slow performance from routes comes from the
+ # Much of the slow performance from routes comes from the
# complexity of expiry, :requirements matching, defaults providing
- # and figuring out which url pattern to use. With named routes
- # we can avoid the expense of finding the right route. So if
+ # and figuring out which url pattern to use. With named routes
+ # we can avoid the expense of finding the right route. So if
# they've provided the right number of arguments, and have no
# :requirements , we can just build up a string and return it.
- #
- # To support building optimisations for other common cases, the
- # generation code is separated into several classes
+ #
+ # To support building optimisations for other common cases, the
+ # generation code is separated into several classes
module Optimisation
def generate_optimisation_block(route, kind)
return "" unless route.optimise?
@@ -53,12 +53,12 @@ module ActionController
# map.person '/people/:id'
#
# If the user calls person_url(@person) , we can simply
- # return a string like "/people/#{@person.to_param}"
+ # return a string like "/people/#{@person.to_param}"
# rather than triggering the expensive logic in +url_for+.
class PositionalArguments < Optimiser
def guard_condition
number_of_arguments = route.segment_keys.size
- # if they're using foo_url(:id=>2) it's one
+ # if they're using foo_url(:id=>2) it's one
# argument, but we don't want to generate /foos/id2
if number_of_arguments == 1
"(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)"
@@ -94,14 +94,14 @@ module ActionController
end
# This case is mostly the same as the positional arguments case
- # above, but it supports additional query parameters as the last
+ # above, but it supports additional query parameters as the last
# argument
class PositionalArgumentsWithAdditionalParams < PositionalArguments
def guard_condition
"(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)"
end
- # This case uses almost the same code as positional arguments,
+ # This case uses almost the same code as positional arguments,
# but add an args.last.to_query on the end
def generation_code
super.insert(-2, '?#{args.last.to_query}')
@@ -110,7 +110,7 @@ module ActionController
# To avoid generating "http://localhost/?host=foo.example.com" we
# can't use this optimisation on routes without any segments
def applicable?
- super && route.segment_keys.size > 0
+ super && route.segment_keys.size > 0
end
end
diff --git a/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/actionpack/lib/action_controller/routing/recognition_optimisation.rb
index cf8f5232c1..67d354a943 100644
--- a/actionpack/lib/action_controller/routing/recognition_optimisation.rb
+++ b/actionpack/lib/action_controller/routing/recognition_optimisation.rb
@@ -51,7 +51,6 @@ module ActionController
# 3) segm test for /users/:id
# (jump to list index = 5)
# 4) full test for /users/:id => here we are!
-
class RouteSet
def recognize_path(path, environment={})
result = recognize_optimized(path, environment) and return result
@@ -152,7 +151,6 @@ module ActionController
segments << nil
segments
end
-
end
end
end
diff --git a/actionpack/lib/action_controller/routing/route.rb b/actionpack/lib/action_controller/routing/route.rb
index a0d108ba03..a05c8c10ac 100644
--- a/actionpack/lib/action_controller/routing/route.rb
+++ b/actionpack/lib/action_controller/routing/route.rb
@@ -226,15 +226,14 @@ module ActionController
end
end
- protected
- def requirement_for(key)
- return requirements[key] if requirements.key? key
- segments.each do |segment|
- return segment.regexp if segment.respond_to?(:key) && segment.key == key
+ protected
+ def requirement_for(key)
+ return requirements[key] if requirements.key? key
+ segments.each do |segment|
+ return segment.regexp if segment.respond_to?(:key) && segment.key == key
+ end
+ nil
end
- nil
- end
-
end
end
end
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
index 5bc13cf268..4a9bc259f3 100644
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ b/actionpack/lib/action_controller/routing/route_set.rb
@@ -1,6 +1,6 @@
module ActionController
module Routing
- class RouteSet #:nodoc:
+ class RouteSet #:nodoc:
# Mapper instances are used to build routes. The object passed to the draw
# block in config/routes.rb is a Mapper instance.
#
@@ -432,4 +432,4 @@ module ActionController
end
end
end
-end
\ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/routing/routing_ext.rb b/actionpack/lib/action_controller/routing/routing_ext.rb
index 2ad20ee699..5f4ba90d0c 100644
--- a/actionpack/lib/action_controller/routing/routing_ext.rb
+++ b/actionpack/lib/action_controller/routing/routing_ext.rb
@@ -1,4 +1,3 @@
-
class Object
def to_param
to_s
diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb
index f0ad066bad..18e76b6b82 100644
--- a/actionpack/lib/action_controller/routing/segments.rb
+++ b/actionpack/lib/action_controller/routing/segments.rb
@@ -130,6 +130,7 @@ module ActionController
def extract_value
"#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
end
+
def value_check
if default # Then we know it won't be nil
"#{value_regexp.inspect} =~ #{local_name}" if regexp
@@ -141,6 +142,7 @@ module ActionController
"#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
end
end
+
def expiry_statement
"expired, hash = true, options if !expired && expire_on[:#{key}]"
end
@@ -175,7 +177,7 @@ module ActionController
end
def regexp_chunk
- if regexp
+ if regexp
if regexp_has_modifiers?
"(#{regexp.to_s})"
else
@@ -214,7 +216,6 @@ module ActionController
def regexp_has_modifiers?
regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
end
-
end
class ControllerSegment < DynamicSegment #:nodoc:
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 84996fd6b1..22e394dc95 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -834,6 +834,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
puts "#{1 / per_url} url/s\n\n"
end
end
+
def test_time_generation
n = 5000
if RunTimeTests
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index b624005a57..61b8c83ee0 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -64,7 +64,7 @@ class TestTest < Test::Unit::TestCase