aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md34
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb7
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb214
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb34
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb31
-rw-r--r--actionpack/lib/action_view/locale/en.yml98
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb2
-rw-r--r--actionpack/test/controller/helper_test.rb30
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb4
-rw-r--r--actionpack/test/dispatch/routing_test.rb2
-rw-r--r--actionpack/test/fixtures/helpers1_pack/pack1_helper.rb5
-rw-r--r--actionpack/test/fixtures/helpers2_pack/pack2_helper.rb5
-rw-r--r--actionpack/test/template/number_helper_test.rb11
-rw-r--r--actionpack/test/template/text_helper_test.rb67
-rw-r--r--actionpack/test/template/url_helper_test.rb7
-rw-r--r--activemodel/CHANGELOG.md2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb8
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb4
-rw-r--r--activemodel/lib/active_model/translation.rb4
-rw-r--r--activemodel/lib/active_model/validations/validates.rb1
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb22
-rw-r--r--activemodel/test/cases/translation_test.rb5
-rw-r--r--activemodel/test/cases/validations_test.rb5
-rw-r--r--activerecord/CHANGELOG.md20
-rw-r--r--activerecord/README.rdoc8
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb237
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb14
-rw-r--r--activerecord/lib/active_record/core.rb9
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb1
-rw-r--r--activerecord/lib/active_record/railtie.rb6
-rw-r--r--activerecord/lib/active_record/railties/databases.rake5
-rw-r--r--activerecord/lib/active_record/relation/batches.rb27
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb19
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb25
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb27
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb44
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb36
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb20
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb22
-rw-r--r--activerecord/test/cases/base_test.rb9
-rw-r--r--activerecord/test/cases/calculations_test.rb16
-rw-r--r--activerecord/test/cases/helper.rb16
-rw-r--r--activesupport/CHANGELOG.md9
-rw-r--r--activesupport/lib/active_support/cache.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb24
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb65
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb48
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/numeric.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb135
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb4
-rw-r--r--activesupport/lib/active_support/json/decoding.rb5
-rw-r--r--activesupport/lib/active_support/json/encoding.rb24
-rw-r--r--activesupport/lib/active_support/json/variable.rb9
-rw-r--r--activesupport/lib/active_support/locale/en.yml99
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb2
-rw-r--r--activesupport/lib/active_support/number_helper.rb531
-rw-r--r--activesupport/lib/active_support/testing/performance.rb8
-rw-r--r--activesupport/lib/active_support/testing/setup_and_teardown.rb12
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb5
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb5
-rw-r--r--activesupport/test/core_ext/file_test.rb6
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb119
-rw-r--r--activesupport/test/core_ext/module_test.rb2
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb261
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb4
-rw-r--r--activesupport/test/json/encoding_test.rb8
-rw-r--r--activesupport/test/number_helper_test.rb375
-rw-r--r--activesupport/test/test_case_test.rb58
-rw-r--r--activesupport/test/testing/performance_test.rb40
-rw-r--r--guides/Rakefile78
-rw-r--r--guides/rails_guides/generator.rb9
-rw-r--r--guides/source/action_view_overview.textile66
-rw-r--r--guides/source/active_record_querying.textile48
-rw-r--r--guides/source/active_support_core_extensions.textile70
-rw-r--r--guides/source/active_support_instrumentation.textile4
-rw-r--r--guides/source/command_line.textile198
-rw-r--r--guides/source/contributing_to_ruby_on_rails.textile36
-rw-r--r--guides/source/engines.textile6
-rw-r--r--guides/source/getting_started.textile54
-rw-r--r--guides/source/initialization.textile159
-rw-r--r--guides/source/migrations.textile3
-rw-r--r--guides/source/rails_on_rack.textile62
-rw-r--r--guides/source/routing.textile10
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.textile24
-rw-r--r--guides/source/security.textile79
-rw-r--r--guides/source/upgrading_ruby_on_rails.textile20
-rw-r--r--railties/CHANGELOG.md4
-rw-r--r--railties/lib/rails/application.rb13
-rw-r--r--railties/lib/rails/application/finisher.rb2
-rw-r--r--railties/lib/rails/application/route_inspector.rb2
-rw-r--r--railties/lib/rails/commands/runner.rb1
-rw-r--r--railties/lib/rails/engine.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb24
-rw-r--r--railties/lib/rails/info_controller.rb32
-rw-r--r--railties/lib/rails/railtie.rb10
-rw-r--r--railties/lib/rails/tasks/engine.rake2
-rw-r--r--railties/lib/rails/tasks/routes.rake2
-rw-r--r--railties/lib/rails/templates/layouts/application.html.erb32
-rw-r--r--railties/lib/rails/templates/rails/info/properties.html.erb1
-rw-r--r--railties/lib/rails/templates/rails/info/routes.html.erb9
-rw-r--r--railties/test/application/configuration_test.rb6
-rw-r--r--railties/test/application/rake/notes_test.rb40
-rw-r--r--railties/test/application/rake_test.rb23
-rw-r--r--railties/test/application/routing_test.rb12
-rw-r--r--railties/test/application/runner_test.rb10
-rw-r--r--railties/test/commands/console_test.rb8
-rw-r--r--railties/test/commands/dbconsole_test.rb8
-rw-r--r--railties/test/generators_test.rb4
-rw-r--r--railties/test/paths_test.rb4
-rw-r--r--railties/test/rails_info_controller_test.rb17
-rw-r--r--railties/test/railties/engine_test.rb4
-rw-r--r--railties/test/railties/railtie_test.rb16
132 files changed, 3479 insertions, 909 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 920858d8c0..b907eea3c1 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,39 @@
## Rails 4.0.0 (unreleased) ##
+* Make possible to use a block in button_to helper if button text is hard
+ to fit into the name parameter, e.g.:
+
+ <%= button_to [:make_happy, @user] do %>
+ Make happy <strong><%= @user.name %></strong>
+ <% end %>
+ # => "<form method="post" action="/users/1/make_happy" class="button_to">
+ # <div>
+ # <button type="submit">
+ # Make happy <strong>Name</strong>
+ # </button>
+ # </div>
+ # </form>"
+
+ *Sergey Nartimov*
+
+* change a way of ordering helpers from several directories. Previously,
+ when loading helpers from multiple paths, all of the helpers files were
+ gathered into one array an then they were sorted. Helpers from different
+ directories should not be mixed before loading them to make loading more
+ predictable. The most common use case for such behavior is loading helpers
+ from engines. When you load helpers from application and engine Foo, in
+ that order, first rails will load all of the helpers from application,
+ sorted alphabetically and then it will do the same for Foo engine.
+
+ *Piotr Sarnacki*
+
+* `truncate` now always returns an escaped HTMl-safe string. The option `:escape` can be used as
+ false to not escape the result.
+
+ *Li Ellis Gallardo + Rafael Mendonça França*
+
+* `truncate` now accepts a block to show extra content when the text is truncated. *Li Ellis Gallardo*
+
* Add `week_field`, `week_field_tag`, `month_field`, `month_field_tag`, `datetime_local_field`,
`datetime_local_field_tag`, `datetime_field` and `datetime_field_tag` helpers. *Carlos Galdino*
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 86d061e3b7..66cdfd40ff 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -95,9 +95,9 @@ module ActionController
helpers = []
Array(path).each do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
- helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ helpers += names.sort
end
- helpers.sort!
helpers.uniq!
helpers
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 67a208263b..e43e897783 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1318,7 +1318,7 @@ module ActionDispatch
def draw(name)
path = @draw_paths.find do |_path|
- _path.join("#{name}.rb").file?
+ File.exists? "#{_path}/#{name}.rb"
end
unless path
@@ -1328,8 +1328,8 @@ module ActionDispatch
raise ArgumentError, msg
end
- route_path = path.join("#{name}.rb")
- instance_eval(route_path.read, route_path.to_s)
+ route_path = "#{path}/#{name}.rb"
+ instance_eval(File.read(route_path), route_path.to_s)
end
# match 'path' => 'controller#action'
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 08fd28d72d..3fdc6688c2 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -17,8 +17,8 @@ module ActionDispatch
# a Hash, or a String that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or
# <tt>multipart/form-data</tt>).
- # - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
- # automatically be upcased, with the prefix 'HTTP_' added if needed.
+ # - +headers+: Additional headers to pass, as a Hash. The headers will be
+ # merged into the Rack env hash.
#
# This method returns an Response object, which one can use to
# inspect the details of the response. Furthermore, if this method was
@@ -73,8 +73,7 @@ module ActionDispatch
#
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
- # string; the headers are a hash. Keys are automatically upcased and
- # prefixed with 'HTTP_' if not already.
+ # string; the headers are a hash.
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index dfc26acfad..8f97d1f014 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -1,8 +1,8 @@
# encoding: utf-8
-require 'active_support/core_ext/big_decimal/conversions'
-require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/string/output_safety'
+require 'active_support/number_helper'
module ActionView
# = Action View Number Helpers
@@ -16,9 +16,6 @@ module ActionView
# unchanged if can't be converted into a valid number.
module NumberHelper
- DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
- :precision => 2, :significant => false, :strip_insignificant_zeros => false }
-
# Raised when argument +number+ param given to the helpers is invalid and
# the option :raise is set to +true+.
class InvalidNumberError < StandardError
@@ -63,25 +60,7 @@ module ActionView
options = options.symbolize_keys
parse_float(number, true) if options[:raise]
-
- number = number.to_s.strip
- area_code = options[:area_code]
- delimiter = options[:delimiter] || "-"
- extension = options[:extension]
- country_code = options[:country_code]
-
- if area_code
- number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
- else
- number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
- number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
- end
-
- str = ''
- str << "+#{country_code}#{delimiter}" unless country_code.blank?
- str << number
- str << " x #{extension}" unless extension.blank?
- ERB::Util.html_escape(str)
+ ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options))
end
# Formats a +number+ into a currency string (e.g., $13.65). You
@@ -128,34 +107,9 @@ module ActionView
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
return unless number
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- currency = translations_for('currency', options[:locale])
- currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
-
- defaults = DEFAULT_CURRENCY_VALUES.merge(defaults_translations(options[:locale])).merge!(currency)
- defaults[:negative_format] = "-" + options[:format] if options[:format]
- options = defaults.merge!(options)
-
- unit = options.delete(:unit)
- format = options.delete(:format)
-
- if number.to_f < 0
- format = options.delete(:negative_format)
- number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
- end
-
- begin
- value = number_with_precision(number, options.merge(:raise => true))
- format.gsub('%n', value).gsub('%u', unit).html_safe
- rescue InvalidNumberError => e
- if options[:raise]
- raise
- else
- formatted_number = format.gsub('%n', e.number).gsub('%u', unit)
- e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
- end
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_currency(number, options) }
end
# Formats a +number+ as a percentage string (e.g., 65%). You can
@@ -196,24 +150,9 @@ module ActionView
# number_to_percentage("98a", :raise => true) # => InvalidNumberError
def number_to_percentage(number, options = {})
return unless number
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- defaults = format_translations('percentage', options[:locale])
- options = defaults.merge!(options)
-
- format = options[:format] || "%n%"
-
- begin
- value = number_with_precision(number, options.merge(:raise => true))
- format.gsub(/%n/, value).html_safe
- rescue InvalidNumberError => e
- if options[:raise]
- raise
- else
- formatted_number = format.gsub(/%n/, e.number)
- e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
- end
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_percentage(number, options) }
end
# Formats a +number+ with grouped thousands using +delimiter+
@@ -246,15 +185,9 @@ module ActionView
#
# number_with_delimiter("112a", :raise => true) # => raise InvalidNumberError
def number_with_delimiter(number, options = {})
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- parse_float(number, options[:raise]) or return number
-
- options = defaults_translations(options[:locale]).merge(options)
-
- parts = number.to_s.to_str.split('.')
- parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
- safe_join(parts, options[:separator])
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_delimited(number, options) }
end
# Formats a +number+ with the specified level of
@@ -299,41 +232,11 @@ module ActionView
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
# # => 1.111,23
def number_with_precision(number, options = {})
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- number = (parse_float(number, options[:raise]) or return number)
-
- defaults = format_translations('precision', options[:locale])
- options = defaults.merge!(options)
-
- precision = options.delete :precision
- significant = options.delete :significant
- strip_insignificant_zeros = options.delete :strip_insignificant_zeros
-
- if significant and precision > 0
- if number == 0
- digits, rounded_number = 1, 0
- else
- digits = (Math.log10(number.abs) + 1).floor
- rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
- digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
- end
- precision -= digits
- precision = precision > 0 ? precision : 0 #don't let it be negative
- else
- rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
- rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros
- end
- formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)
- if strip_insignificant_zeros
- escaped_separator = Regexp.escape(options[:separator])
- formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '').html_safe
- else
- formatted_number
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_rounded(number, options) }
end
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
# Formats the bytes in +number+ into a more understandable
# representation (e.g., giving it 1500 yields 1.5 KB). This
@@ -383,40 +286,11 @@ module ActionView
# number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
# number_to_human_size(524288000, :precision => 5) # => "500 MB"
def number_to_human_size(number, options = {})
- options = options.symbolize_keys
-
- number = (parse_float(number, options[:raise]) or return number)
-
- defaults = format_translations('human', options[:locale])
- options = defaults.merge!(options)
-
- #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
- options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
-
- storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- base = options[:prefix] == :si ? 1000 : 1024
-
- if number.to_i < base
- unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
- storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit).html_safe
- else
- max_exp = STORAGE_UNITS.size - 1
- exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
- exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
- number /= base ** exponent
-
- unit_key = STORAGE_UNITS[exponent]
- unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
-
- formatted_number = number_with_precision(number, options)
- storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).html_safe
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_human_size(number, options) }
end
- DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
- -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze
-
# Pretty prints (formats and approximates) a number in a way it
# is more readable by humans (eg.: 1200000000 becomes "1.2
# Billion"). This is useful for numbers that can get very large
@@ -516,60 +390,34 @@ module ActionView
# number_to_human(0.34, :units => :distance) # => "34 centimeters"
#
def number_to_human(number, options = {})
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- number = (parse_float(number, options[:raise]) or return number)
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_human(number, options) }
+ end
- defaults = format_translations('human', options[:locale])
- options = defaults.merge!(options)
+ private
- #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
- options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
+ def escape_unsafe_delimiters_and_separators(options)
+ options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe?
+ options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe?
+ options
+ end
- inverted_du = DECIMAL_UNITS.invert
+ def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
+ valid_float = valid_float?(number)
+ raise InvalidNumberError, number if raise_on_invalid && !valid_float
- units = options.delete :units
- unit_exponents = case units
- when Hash
- units
- when String, Symbol
- I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
- when nil
- I18n.translate(:"number.human.decimal_units.units", :locale => options[:locale], :raise => true)
- else
- raise ArgumentError, ":units must be a Hash or String translation scope."
- end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}
+ formatted_number = yield
- number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
- display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
- number /= 10 ** display_exponent
-
- unit = case units
- when Hash
- units[DECIMAL_UNITS[display_exponent]]
- when String, Symbol
- I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ if valid_float || number.html_safe?
+ formatted_number.html_safe
else
- I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ formatted_number
end
-
- decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u")
- formatted_number = number_with_precision(number, options)
- decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip.html_safe
- end
-
- private
-
- def format_translations(namespace, locale)
- defaults_translations(locale).merge(translations_for(namespace, locale))
- end
-
- def defaults_translations(locale)
- I18n.translate(:'number.format', :locale => locale, :default => {})
end
- def translations_for(namespace, locale)
- I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {})
+ def valid_float?(number)
+ !parse_float(number, false).nil?
end
def parse_float(number, raise_error)
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 8cd7cf0052..0cc0d069ea 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -62,9 +62,11 @@ module ActionView
#
# Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
#
- # The result is not marked as HTML-safe, so will be subject to the default escaping when
- # used in views, unless wrapped by <tt>raw()</tt>. Care should be taken if +text+ contains HTML tags
- # or entities, because truncation may produce invalid HTML (such as unbalanced or incomplete tags).
+ # Pass a block if you want to show extra content when the text is truncated.
+ #
+ # The result is marked as HTML-safe, but it is escaped by default, unless <tt>:escape</tt> is
+ # +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation
+ # may produce invalid HTML (such as unbalanced or incomplete tags).
#
# truncate("Once upon a time in a world far far away")
# # => "Once upon a time in a world..."
@@ -80,8 +82,18 @@ module ActionView
#
# truncate("<p>Once upon a time in a world far far away</p>")
# # => "<p>Once upon a time in a wo..."
- def truncate(text, options = {})
- text.truncate(options.fetch(:length, 30), options) if text
+ #
+ # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" }
+ # # => "Once upon a time in a wo...<a href="#">Continue</a>"
+ def truncate(text, options = {}, &block)
+ if text
+ length = options.fetch(:length, 30)
+
+ content = text.truncate(length, options)
+ content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content)
+ content << capture(&block) if block_given? && text.length > length
+ content
+ end
end
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
@@ -102,7 +114,7 @@ module ActionView
# # => You searched for: <a href="search?q=rails">rails</a>
def highlight(text, phrases, options = {})
highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
-
+
text = sanitize(text) if options.fetch(:sanitize, true)
if text.blank? || phrases.blank?
text
@@ -165,12 +177,12 @@ module ActionView
# pluralize(0, 'person')
# # => 0 people
def pluralize(count, singular, plural = nil)
- word = if (count == 1 || count =~ /^1(\.0+)?$/)
- singular
+ word = if (count == 1 || count =~ /^1(\.0+)?$/)
+ singular
else
plural || singular.pluralize
end
-
+
"#{count || 0} #{word}"
end
@@ -215,7 +227,7 @@ module ActionView
#
# simple_format(my_text)
# # => "<p>Here is some basic text...\n<br />...with a line break.</p>"
- #
+ #
# simple_format(my_text, {}, :wrapper_tag => "div")
# # => "<div>Here is some basic text...\n<br />...with a line break.</div>"
#
@@ -231,7 +243,7 @@ module ActionView
# # => "<p><span>I'm allowed!</span> It's true.</p>"
def simple_format(text, html_options = {}, options = {})
wrapper_tag = options.fetch(:wrapper_tag, :p)
-
+
text = sanitize(text) if options.fetch(:sanitize, true)
paragraphs = split_paragraphs(text)
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 7e69547dab..a4b10bc68a 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -294,6 +294,16 @@ module ActionView
# # <div><input value="New" type="submit" /></div>
# # </form>"
#
+ # <%= button_to [:make_happy, @user] do %>
+ # Make happy <strong><%= @user.name %></strong>
+ # <% end %>
+ # # => "<form method="post" action="/users/1/make_happy" class="button_to">
+ # # <div>
+ # # <button type="submit">
+ # # Make happy <strong><%= @user.name %></strong>
+ # # </button>
+ # # </div>
+ # # </form>"
#
# <%= button_to "New", :action => "new", :form_class => "new-thing" %>
# # => "<form method="post" action="/controller/new" class="new-thing">
@@ -331,7 +341,16 @@ module ActionView
# # </div>
# # </form>"
# #
- def button_to(name, options = {}, html_options = {})
+ def button_to(*args, &block)
+ if block_given?
+ options = args[0] || {}
+ html_options = args[1] || {}
+ else
+ name = args[0]
+ options = args[1] || {}
+ html_options = args[2] || {}
+ end
+
html_options = html_options.stringify_keys
convert_boolean_attributes!(html_options, %w(disabled))
@@ -350,9 +369,15 @@ module ActionView
request_token_tag = form_method == 'post' ? token_tag : ''
html_options = convert_options_to_data_attributes(options, html_options)
- html_options.merge!("type" => "submit", "value" => name || url)
+ html_options['type'] = 'submit'
+
+ button = if block_given?
+ content_tag('button', html_options, &block)
+ else
+ tag('input', html_options.merge('value' => name || url))
+ end
- inner_tags = method_tag.safe_concat tag('input', html_options).safe_concat request_token_tag
+ inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
content_tag('form', content_tag('div', inner_tags), form_options)
end
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index 8e9db634fb..8a56f147b8 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -1,102 +1,4 @@
"en":
- number:
- # Used in number_with_delimiter()
- # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
- format:
- # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
- separator: "."
- # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
- delimiter: ","
- # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
- precision: 3
- # If set to true, precision will mean the number of significant digits instead
- # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
- significant: false
- # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
- strip_insignificant_zeros: false
-
- # Used in number_to_currency()
- currency:
- format:
- # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
- format: "%u%n"
- unit: "$"
- # These five are to override number.format and are optional
- separator: "."
- delimiter: ","
- precision: 2
- significant: false
- strip_insignificant_zeros: false
-
- # Used in number_to_percentage()
- percentage:
- format:
- # These five are to override number.format and are optional
- # separator:
- delimiter: ""
- # precision:
- # significant: false
- # strip_insignificant_zeros: false
- format: "%n%"
-
- # Used in number_to_precision()
- precision:
- format:
- # These five are to override number.format and are optional
- # separator:
- delimiter: ""
- # precision:
- # significant: false
- # strip_insignificant_zeros: false
-
- # Used in number_to_human_size() and number_to_human()
- human:
- format:
- # These five are to override number.format and are optional
- # separator:
- delimiter: ""
- precision: 3
- significant: true
- strip_insignificant_zeros: true
- # Used in number_to_human_size()
- storage_units:
- # Storage units output formatting.
- # %u is the storage unit, %n is the number (default: 2 MB)
- format: "%n %u"
- units:
- byte:
- one: "Byte"
- other: "Bytes"
- kb: "KB"
- mb: "MB"
- gb: "GB"
- tb: "TB"
- # Used in number_to_human()
- decimal_units:
- format: "%n %u"
- # Decimal units output formatting
- # By default we will only quantify some of the exponents
- # but the commented ones might be defined or overridden
- # by the user.
- units:
- # femto: Quadrillionth
- # pico: Trillionth
- # nano: Billionth
- # micro: Millionth
- # mili: Thousandth
- # centi: Hundredth
- # deci: Tenth
- unit: ""
- # ten:
- # one: Ten
- # other: Tens
- # hundred: Hundred
- thousand: Thousand
- million: Million
- billion: Billion
- trillion: Trillion
- quadrillion: Quadrillion
-
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
datetime:
distance_in_words:
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index ae923de24e..82892593f8 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -35,7 +35,7 @@ module ActionView
end
end
- # Renders the given template. An string representing the layout can be
+ # Renders the given template. A string representing the layout can be
# supplied as well.
def render_template(template, layout_name = nil, locals = {}) #:nodoc:
view, locals = @view, locals || {}
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 757661d8d0..deb234b04f 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -46,12 +46,42 @@ end
class MeTooController < JustMeController
end
+class HelpersPathsController < ActionController::Base
+ paths = ["helpers2_pack", "helpers1_pack"].map do |path|
+ File.join(File.expand_path('../../fixtures', __FILE__), path)
+ end
+ $:.unshift(*paths)
+
+ self.helpers_path = paths
+ helper :all
+
+ def index
+ render :inline => "<%= conflicting_helper %>"
+ end
+end
+
module LocalAbcHelper
def a() end
def b() end
def c() end
end
+class HelperPathsTest < ActiveSupport::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_helpers_paths_priority
+ request = ActionController::TestRequest.new
+ responses = HelpersPathsController.action(:index).call(request.env)
+
+ # helpers1_pack was given as a second path, so pack1_helper should be
+ # included as the second one
+ assert_equal "pack1", responses.last.body
+ end
+end
+
class HelperTest < ActiveSupport::TestCase
class TestController < ActionController::Base
attr_accessor :delegate_attr
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 066cd523be..0289f4070b 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -9,7 +9,7 @@ module RequestForgeryProtectionActions
end
def show_button
- render :inline => "<%= button_to('New', '/') {} %>"
+ render :inline => "<%= button_to('New', '/') %>"
end
def external_form
@@ -79,7 +79,7 @@ class FreeCookieController < RequestForgeryProtectionController
end
def show_button
- render :inline => "<%= button_to('New', '/') {} %>"
+ render :inline => "<%= button_to('New', '/') %>"
end
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 00d09282ca..fa4cb301eb 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -2331,7 +2331,7 @@ class TestDrawExternalFile < ActionDispatch::IntegrationTest
end
end
- DRAW_PATH = Pathname.new(File.expand_path('../../fixtures/routes', __FILE__))
+ DRAW_PATH = File.expand_path('../../fixtures/routes', __FILE__)
DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw_paths << DRAW_PATH
diff --git a/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb b/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb
new file mode 100644
index 0000000000..9faa427736
--- /dev/null
+++ b/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb
@@ -0,0 +1,5 @@
+module Pack1Helper
+ def conflicting_helper
+ "pack1"
+ end
+end
diff --git a/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb b/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb
new file mode 100644
index 0000000000..cf56697dfb
--- /dev/null
+++ b/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb
@@ -0,0 +1,5 @@
+module Pack2Helper
+ def conflicting_helper
+ "pack2"
+ end
+end
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 14ca6d9879..057cb47f53 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -33,6 +33,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("+18005551212", number_to_phone(8005551212, :country_code => 1, :delimiter => ''))
assert_equal("22-555-1212", number_to_phone(225551212))
assert_equal("+45-22-555-1212", number_to_phone(225551212, :country_code => 45))
+ assert_equal '111&lt;script&gt;&lt;/script&gt;111&lt;script&gt;&lt;/script&gt;1111', number_to_phone(1111111111, :delimiter => "<script></script>")
end
def test_number_to_currency
@@ -47,6 +48,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50"))
assert_equal("1,234,567,890.50 K&#269;", number_to_currency("1234567890.50", {:unit => "K&#269;", :format => "%n %u"}))
assert_equal("1,234,567,890.50 - K&#269;", number_to_currency("-1234567890.50", {:unit => "K&#269;", :format => "%n %u", :negative_format => "%n - %u"}))
+ assert_equal '$1&lt;script&gt;&lt;/script&gt;01', number_to_currency(1.01, :separator => "<script></script>")
+ assert_equal '$1&lt;script&gt;&lt;/script&gt;000.00', number_to_currency(1000, :delimiter => "<script></script>")
end
def test_number_to_percentage
@@ -58,6 +61,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("123.4%", number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true))
assert_equal("1.000,000%", number_to_percentage(1000, :delimiter => '.', :separator => ','))
assert_equal("1000.000 %", number_to_percentage(1000, :format => "%n %"))
+ assert_equal '1&lt;script&gt;&lt;/script&gt;010%', number_to_percentage(1.01, :separator => "<script></script>")
+ assert_equal '1&lt;script&gt;&lt;/script&gt;000.000%', number_to_percentage(1000, :delimiter => "<script></script>")
end
def test_number_with_delimiter
@@ -104,6 +109,8 @@ class NumberHelperTest < ActionView::TestCase
def test_number_with_precision_with_custom_delimiter_and_separator
assert_equal '31,83', number_with_precision(31.825, :precision => 2, :separator => ',')
assert_equal '1.231,83', number_with_precision(1231.825, :precision => 2, :separator => ',', :delimiter => '.')
+ assert_equal '1&lt;script&gt;&lt;/script&gt;010', number_with_precision(1.01, :separator => "<script></script>")
+ assert_equal '1&lt;script&gt;&lt;/script&gt;000.000', number_with_precision(1000, :delimiter => "<script></script>")
end
def test_number_with_precision_with_significant_digits
@@ -193,6 +200,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal '1.0 KB', number_to_human_size(kilobytes(1.0123), :precision => 2, :strip_insignificant_zeros => false)
assert_equal '1.012 KB', number_to_human_size(kilobytes(1.0123), :precision => 3, :significant => false)
assert_equal '1 KB', number_to_human_size(kilobytes(1.0123), :precision => 0, :significant => true) #ignores significant it precision is 0
+ assert_equal '9&lt;script&gt;&lt;/script&gt;86 KB', number_to_human_size(10100, :separator => "<script></script>")
end
def test_number_to_human_size_with_custom_delimiter_and_separator
@@ -253,6 +261,9 @@ class NumberHelperTest < ActionView::TestCase
#Spaces are stripped from the resulting string
assert_equal '4', number_to_human(4, :units => {:unit => "", :ten => 'tens '})
assert_equal '4.5 tens', number_to_human(45, :units => {:unit => "", :ten => ' tens '})
+
+ assert_equal '1&lt;script&gt;&lt;/script&gt;01', number_to_human(1.01, :separator => "<script></script>")
+ assert_equal '100&lt;script&gt;&lt;/script&gt;000 Quadrillion', number_to_human(10**20, :delimiter => "<script></script>")
end
def test_number_to_human_with_custom_format
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index f58e474759..a3ab091c6c 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -60,14 +60,14 @@ class TextHelperTest < ActionView::TestCase
simple_format(text)
assert_equal text_clone, text
end
-
+
def test_simple_format_does_not_modify_the_html_options_hash
options = { :class => "foobar"}
passed_options = options.dup
simple_format("some text", passed_options)
assert_equal options, passed_options
end
-
+
def test_simple_format_does_not_modify_the_options_hash
options = { :wrapper_tag => :div, :sanitize => false }
passed_options = options.dup
@@ -75,19 +75,11 @@ class TextHelperTest < ActionView::TestCase
assert_equal options, passed_options
end
- def test_truncate_should_not_be_html_safe
- assert !truncate("Hello World!", :length => 12).html_safe?
- end
-
def test_truncate
assert_equal "Hello World!", truncate("Hello World!", :length => 12)
assert_equal "Hello Wor...", truncate("Hello World!!", :length => 12)
end
- def test_truncate_should_not_escape_input
- assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12)
- end
-
def test_truncate_should_use_default_length_of_30
str = "This is a string that will go longer then the default truncate length of 30"
assert_equal str[0...27] + "...", truncate(str)
@@ -106,7 +98,7 @@ class TextHelperTest < ActionView::TestCase
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'), :length => 10)
end
-
+
def test_truncate_does_not_modify_the_options_hash
options = { :length => 10 }
passed_options = options.dup
@@ -114,6 +106,53 @@ class TextHelperTest < ActionView::TestCase
assert_equal options, passed_options
end
+ def test_truncate_with_link_options
+ assert_equal "Here's a long test and I...<a href=\"#\">Continue</a>",
+ truncate("Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' }
+ end
+
+ def test_truncate_should_be_html_safe
+ assert truncate("Hello World!", :length => 12).html_safe?
+ end
+
+ def test_truncate_should_escape_the_input
+ assert_equal "Hello &lt;sc...", truncate("Hello <script>code!</script>World!!", :length => 12)
+ end
+
+ def test_truncate_should_not_escape_the_input_with_escape_false
+ assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12, :escape => false)
+ end
+
+ def test_truncate_with_escape_false_should_be_html_safe
+ truncated = truncate("Hello <script>code!</script>World!!", :length => 12, :escape => false)
+ assert truncated.html_safe?
+ end
+
+ def test_truncate_with_block_should_be_html_safe
+ truncated = truncate("Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' }
+ assert truncated.html_safe?
+ end
+
+ def test_truncate_with_block_should_escape_the_input
+ assert_equal "&lt;script&gt;code!&lt;/script&gt;He...<a href=\"#\">Continue</a>",
+ truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' }
+ end
+
+ def test_truncate_with_block_should_not_escape_the_input_with_escape_false
+ assert_equal "<script>code!</script>He...<a href=\"#\">Continue</a>",
+ truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27, :escape => false) { link_to 'Continue', '#' }
+ end
+
+ def test_truncate_with_block_with_escape_false_should_be_html_safe
+ truncated = truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27, :escape => false) { link_to 'Continue', '#' }
+ assert truncated.html_safe?
+ end
+
+ def test_truncate_with_block_should_escape_the_block
+ assert_equal "Here's a long test and I...&lt;script&gt;alert('foo');&lt;/script&gt;",
+ truncate("Here's a long test and I need a continue to read link", :length => 27) { "<script>alert('foo');</script>" }
+ end
+
def test_highlight_should_be_html_safe
assert highlight("This is a beautiful morning", "beautiful").html_safe?
end
@@ -203,7 +242,7 @@ class TextHelperTest < ActionView::TestCase
highlight("<div>abc div</div>", "div", :highlighter => '<b>\1</b>')
)
end
-
+
def test_highlight_does_not_modify_the_options_hash
options = { :highlighter => '<b>\1</b>', :sanitize => false }
passed_options = options.dup
@@ -256,7 +295,7 @@ class TextHelperTest < ActionView::TestCase
def test_excerpt_with_utf8
assert_equal("...\357\254\203ciency could not be...".force_encoding('UTF-8'), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding('UTF-8'), 'could', :radius => 8))
end
-
+
def test_excerpt_does_not_modify_the_options_hash
options = { :omission => "[...]",:radius => 5 }
passed_options = options.dup
@@ -271,7 +310,7 @@ class TextHelperTest < ActionView::TestCase
def test_word_wrap_with_extra_newlines
assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", :line_width => 15))
end
-
+
def test_word_wrap_does_not_modify_the_options_hash
options = { :line_width => 15 }
passed_options = options.dup
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index fb5b35bac6..365a86ab91 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -144,6 +144,13 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
+ def test_button_to_with_block
+ assert_dom_equal(
+ "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><button type=\"submit\"><span>Hello</span></button></div></form>",
+ button_to("http://www.example.com") { content_tag(:span, 'Hello') }
+ )
+ end
+
def test_link_tag_with_straight_url
assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", "http://www.example.com")
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 789cff0673..eb34edb91b 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* Passing false hash values to `validates` will no longer enable the corresponding validators *Steve Purcell*
+
* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella*
* Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran*
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 99918fdb96..846d0d7f86 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -196,7 +196,7 @@ module ActiveModel
attribute_method_matchers.each do |matcher|
matcher_new = matcher.method_name(new_name).to_s
matcher_old = matcher.method_name(old_name).to_s
- define_optimized_call self, matcher_new, matcher_old
+ define_proxy_call false, self, matcher_new, matcher_old
end
end
@@ -238,7 +238,7 @@ module ActiveModel
if respond_to?(generate_method, true)
send(generate_method, attr_name)
else
- define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
+ define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
end
end
end
@@ -293,7 +293,7 @@ module ActiveModel
# Define a method `name` in `mod` that dispatches to `send`
# using the given `extra` args. This fallbacks `define_method`
# and `send` if the given names cannot be compiled.
- def define_optimized_call(mod, name, send, *extra) #:nodoc:
+ def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc:
if name =~ NAME_COMPILABLE_REGEXP
defn = "def #{name}(*args)"
else
@@ -303,7 +303,7 @@ module ActiveModel
extra = (extra.map(&:inspect) << "*args").join(", ")
if send =~ CALL_COMPILABLE_REGEXP
- target = "#{send}(#{extra})"
+ target = "#{"self." unless include_private}#{send}(#{extra})"
else
target = "send(:'#{send}', #{extra})"
end
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index 893fbf92c3..cfce1542b1 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -62,7 +62,7 @@ module ActiveModel
# Attributes named in this macro are protected from mass-assignment
# whenever attributes are sanitized before assignment. A role for the
# attributes is optional, if no role is provided then :default is used.
- # A role can be defined by using the :as option.
+ # A role can be defined by using the :as option with a symbol or an array of symbols as the value.
#
# Mass-assignment to these attributes will simply be ignored, to assign
# to them you can use direct writer methods. This is meant to protect
@@ -128,7 +128,7 @@ module ActiveModel
#
# Like +attr_protected+, a role for the attributes is optional,
# if no role is provided then :default is used. A role can be defined by
- # using the :as option.
+ # using the :as option with a symbol or an array of symbols as the value.
#
# This is the opposite of the +attr_protected+ macro: Mass-assignment
# will only set attributes in this list, to assign to the rest of
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 6f0ca92e2a..7a86701f73 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -42,9 +42,9 @@ module ActiveModel
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
options = { :count => 1 }.merge!(options)
- parts = attribute.to_s.split(".", 2)
+ parts = attribute.to_s.split(".")
attribute = parts.pop
- namespace = parts.pop
+ namespace = parts.join("/") unless parts.empty?
attributes_scope = "#{self.i18n_scope}.attributes"
if namespace
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index d94c4e3f4f..6c13d2b4a2 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -88,6 +88,7 @@ module ActiveModel
defaults.merge!(:attributes => attributes)
validations.each do |key, options|
+ next unless options
key = "#{key.to_s.camelize}Validator"
begin
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index a9db29ee21..e2f2cecc09 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -76,6 +76,19 @@ private
end
end
+class ModelWithRubyKeywordNamedAttributes
+ include ActiveModel::AttributeMethods
+
+ def attributes
+ { :begin => 'value of begin', :end => 'value of end' }
+ end
+
+private
+ def attribute(name)
+ attributes[name.to_sym]
+ end
+end
+
class ModelWithoutAttributesMethod
include ActiveModel::AttributeMethods
end
@@ -155,6 +168,15 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.foo_bar
end
+ test '#alias_attribute works with attributes named as a ruby keyword' do
+ ModelWithRubyKeywordNamedAttributes.define_attribute_methods([:begin, :end])
+ ModelWithRubyKeywordNamedAttributes.alias_attribute(:from, :begin)
+ ModelWithRubyKeywordNamedAttributes.alias_attribute(:to, :end)
+
+ assert_equal "value of begin", ModelWithRubyKeywordNamedAttributes.new.from
+ assert_equal "value of end", ModelWithRubyKeywordNamedAttributes.new.to
+ end
+
test '#undefine_attribute_methods removes attribute methods' do
ModelWithAttributes.define_attribute_methods(:foo)
ModelWithAttributes.undefine_attribute_methods
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index 4999583802..fd833cdd06 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -56,6 +56,11 @@ class ActiveModelI18nTests < ActiveModel::TestCase
assert_equal 'person gender attribute', Person::Gender.human_attribute_name('attribute')
end
+ def test_translated_deeply_nested_model_attributes
+ I18n.backend.store_translations 'en', :activemodel => {:attributes => {:"person/contacts/addresses" => {:street => 'Deeply Nested Address Street'}}}
+ assert_equal 'Deeply Nested Address Street', Person.human_attribute_name('contacts.addresses.street')
+ end
+
def test_translated_nested_model_attributes
I18n.backend.store_translations 'en', :activemodel => {:attributes => {:"person/addresses" => {:street => 'Person Address Street'}}}
assert_equal 'Person Address Street', Person.human_attribute_name('addresses.street')
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 1f5023bf76..8ea9745fbf 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -330,6 +330,11 @@ class ValidationsTest < ActiveModel::TestCase
end
end
+ def test_validates_with_false_hash_value
+ Topic.validates :title, :presence => false
+ assert Topic.new.valid?
+ end
+
def test_strict_validation_error_message
Topic.validates :title, :strict => true, :presence => true
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 8f1f315e42..4f4e087acd 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,25 @@
## Rails 4.0.0 (unreleased) ##
+* Allow blocks for `count` with `ActiveRecord::Relation`, to work similar as
+ `Array#count`:
+
+ Person.where("age > 26").count { |person| gender == 'female' }
+
+ *Chris Finne & Carlos Antonio da Silva*
+
+* Added support to `CollectionAssociation#delete` for passing `fixnum`
+ or `string` values as record ids. This finds the records responding
+ to the `id` and executes delete on them.
+
+ class Person < ActiveRecord::Base
+ has_many :pets
+ end
+
+ person.pets.delete("1") # => [#<Pet id: 1>]
+ person.pets.delete(2, 3) # => [#<Pet id: 2>, #<Pet id: 3>]
+
+ *Francesco Rodriguez*
+
* Deprecated most of the 'dynamic finder' methods. All dynamic methods
except for `find_by_...` and `find_by_...!` are deprecated. Here's
how you can rewrite the code:
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index 30a66ff5f0..d080e0b0f5 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -61,10 +61,10 @@ A short rundown of some of the major features:
* Validation rules that can differ for new or existing objects.
class Account < ActiveRecord::Base
- validates_presence_of :subdomain, :name, :email_address, :password
- validates_uniqueness_of :subdomain
- validates_acceptance_of :terms_of_service, :on => :create
- validates_confirmation_of :password, :email_address, :on => :create
+ validates :subdomain, :name, :email_address, :password, presence: true
+ validates :subdomain, uniqueness: true
+ validates :terms_of_service, acceptance: true, on: :create
+ validates :password, :email_address, confirmation: true, on: :create
end
{Learn more}[link:classes/ActiveRecord/Validations.html]
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 210820062b..f8526bb691 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -80,6 +80,7 @@ module ActiveRecord
autoload :Sanitization
autoload :Schema
autoload :SchemaDumper
+ autoload :SchemaMigration
autoload :Scoping
autoload :Serialization
autoload :SessionStore
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 4ec176e641..e94fe35170 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -230,6 +230,7 @@ module ActiveRecord
delete_records(:all, dependent)
end
else
+ records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
delete_or_destroy(records, dependent)
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 100fb38dec..2fb80fdc4c 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -343,6 +343,9 @@ module ActiveRecord
##
# :method: delete_all
#
+ # :call-seq:
+ # delete_all()
+ #
# Deletes all the records from the collection. For +has_many+ asssociations,
# the deletion is done according to the strategy specified by the <tt>:dependent</tt>
# option. Returns an array with the deleted records.
@@ -435,6 +438,9 @@ module ActiveRecord
##
# :method: destroy_all
#
+ # :call-seq:
+ # destroy_all()
+ #
# Deletes the records of the collection directly from the database.
# This will _always_ remove the records ignoring the +:dependent+
# option.
@@ -459,12 +465,135 @@ module ActiveRecord
# Pet.find(1) # => Couldn't find Pet with id=1
##
+ # :method: delete
+ #
+ # :call-seq:
+ # delete(*records)
+ # delete(*fixnum_ids)
+ # delete(*string_ids)
+ #
+ # Deletes the +records+ supplied and removes them from the collection. For
+ # +has_many+ associations, the deletion is done according to the strategy
+ # specified by the <tt>:dependent</tt> option. Returns an array with the
+ # deleted records.
+ #
+ # If no <tt>:dependent</tt> option is given, then it will follow the default
+ # strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign
+ # keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default
+ # strategy is +delete_all+.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets # dependent: :nullify option by default
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.delete(Pet.find(1))
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
+ #
+ # person.pets.size # => 2
+ # person.pets
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # Pet.find(1)
+ # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
+ #
+ # If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
+ # their +destroy+ method. See +destroy+ for more information.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets, dependent: :destroy
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.delete(Pet.find(1), Pet.find(3))
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.size # => 1
+ # person.pets
+ # # => [#<Pet id: 2, name: "Spook", person_id: 1>]
+ #
+ # Pet.find(1, 3)
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
+ #
+ # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
+ # *without* calling their +destroy+ method.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets, dependent: :delete_all
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.delete(Pet.find(1))
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
+ #
+ # person.pets.size # => 2
+ # person.pets
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # Pet.find(1)
+ # # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
+ #
+ # You can pass +Fixnum+ or +String+ values, it finds the records
+ # responding to the +id+ and executes delete on them.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.delete("1")
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
+ #
+ # person.pets.delete(2, 3)
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+
+ ##
# :method: destroy
#
# :call-seq:
# destroy(*records)
#
- # Destroy the +records+ supplied and remove them from the collection.
+ # Destroys the +records+ supplied and removes them from the collection.
# This method will _always_ remove record from the database ignoring
# the +:dependent+ option. Returns an array with the removed records.
#
@@ -534,8 +663,52 @@ module ActiveRecord
# Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
##
+ # :method: uniq
+ #
+ # :call-seq:
+ # uniq()
+ #
+ # Specifies whether the records should be unique or not.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.select(:name)
+ # # => [
+ # # #<Pet name: "Fancy-Fancy">,
+ # # #<Pet name: "Fancy-Fancy">
+ # # ]
+ #
+ # person.pets.select(:name).uniq
+ # # => [#<Pet name: "Fancy-Fancy">]
+
+ ##
+ # :method: count
+ #
+ # :call-seq:
+ # count()
+ #
+ # Count all records using SQL.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.count # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+
+ ##
# :method: size
#
+ # :call-seq:
+ # size()
+ #
# Returns the size of the collection. If the collection hasn't been loaded,
# it executes a <tt>SELECT COUNT(*)</tt> query.
#
@@ -560,6 +733,9 @@ module ActiveRecord
##
# :method: length
#
+ # :call-seq:
+ # length()
+ #
# Returns the size of the collection calling +size+ on the target.
# If the collection has been already loaded, +length+ and +size+ are
# equivalent.
@@ -699,7 +875,7 @@ module ActiveRecord
:any?, :many?, :include?,
:to => :@association
- def initialize(association)
+ def initialize(association) #:nodoc:
@association = association
super association.klass, association.klass.arel_table
merge! association.scoped
@@ -731,10 +907,67 @@ module ActiveRecord
end
end
+ # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
+ # contain the same number of elements and if each element is equal
+ # to the corresponding element in the other array, otherwise returns
+ # +false+.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
+ # # ]
+ #
+ # other = person.pets.to_ary
+ #
+ # person.pets == other
+ # # => true
+ #
+ # other = [Pet.new(id: 1), Pet.new(id: 2)]
+ #
+ # person.pets == other
+ # # => false
def ==(other)
load_target == other
end
+ # Returns a new array of objects from the collection. If the collection
+ # hasn't been loaded, it fetches the records from the database.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets
+ # # => [
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
+ # # ]
+ #
+ # other_pets = person.pets.to_ary
+ # # => [
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
+ # # ]
+ #
+ # other_pets.replace([Pet.new(name: 'BooGoo')])
+ #
+ # other_pets
+ # # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
+ #
+ # person.pets
+ # # This is not affected by replace
+ # # => [
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
+ # # ]
def to_ary
load_target.dup
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 165785c8fb..706fbf0546 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -72,12 +72,13 @@ module ActiveRecord
self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
end
- def initialize_attributes(attributes) #:nodoc:
- super
+ def initialize_attributes(attributes, options = {}) #:nodoc:
+ serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
+ super(attributes, options)
serialized_attributes.each do |key, coder|
if attributes.key?(key)
- attributes[key] = Attribute.new(coder, attributes[key], :serialized)
+ attributes[key] = Attribute.new(coder, attributes[key], serialized)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 62b0f51bb2..5758ac4569 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1,5 +1,4 @@
require 'active_support/deprecation/reporting'
-require 'active_record/schema_migration'
require 'active_record/migration/join_table'
module ActiveRecord
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 9794c5663e..692473abc5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -313,10 +313,10 @@ module ActiveRecord
sql = "SHOW TABLES"
end
- select_all(sql).map { |table|
+ select_all(sql, 'SCHEMA').map { |table|
table.delete('Table_type')
sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
- exec_without_stmt(sql).first['Create Table'] + ";\n\n"
+ exec_without_stmt(sql, 'SCHEMA').first['Create Table'] + ";\n\n"
}.join
end
@@ -508,7 +508,7 @@ module ActiveRecord
# SHOW VARIABLES LIKE 'name'
def show_variable(name)
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
variables.first['Value'] unless variables.empty?
end
@@ -630,7 +630,7 @@ module ActiveRecord
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
end
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
add_column_options!(rename_column_sql, options)
rename_column_sql
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 350ccce03d..8fc172f6e8 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -268,7 +268,7 @@ module ActiveRecord
# increase timeout so mysql server doesn't disconnect us
wait_timeout = @config[:wait_timeout]
- wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
variable_assignments << "@@wait_timeout = #{wait_timeout}"
execute("SET #{variable_assignments.join(', ')}", :skip_logging)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 0bd7403c32..03c318f5f7 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -524,7 +524,7 @@ module ActiveRecord
# Returns the configured supported identifier length supported by PostgreSQL
def table_alias_length
- @table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
+ @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
end
# QUOTING ==================================================
@@ -965,28 +965,28 @@ module ActiveRecord
binds = [[nil, table]]
binds << [nil, schema] if schema
- exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind in ('v','r')
- AND c.relname = $1
- AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'}
+ AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
+ AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
SQL
end
# Returns true if schema exists.
def schema_exists?(name)
- exec_query(<<-SQL, 'SCHEMA', [[nil, name]]).rows.first[0].to_i > 0
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
SELECT COUNT(*)
FROM pg_namespace
- WHERE nspname = $1
+ WHERE nspname = '#{name}'
SQL
end
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil)
- result = query(<<-SQL, name)
+ result = query(<<-SQL, 'SCHEMA')
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
@@ -998,7 +998,6 @@ module ActiveRecord
ORDER BY i.relname
SQL
-
result.map do |row|
index_name = row[0]
unique = row[1] == 't'
@@ -1037,7 +1036,7 @@ module ActiveRecord
# Returns the current database name.
def current_database
- query('select current_database()')[0][0]
+ query('select current_database()', 'SCHEMA')[0][0]
end
# Returns the current schema name.
@@ -1047,7 +1046,7 @@ module ActiveRecord
# Returns the current database encoding format.
def encoding
- query(<<-end_sql)[0][0]
+ query(<<-end_sql, 'SCHEMA')[0][0]
SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
WHERE pg_database.datname LIKE '#{current_database}'
end_sql
@@ -1055,7 +1054,7 @@ module ActiveRecord
# Returns an array of schema names.
def schema_names
- query(<<-SQL).flatten
+ query(<<-SQL, 'SCHEMA').flatten
SELECT nspname
FROM pg_namespace
WHERE nspname !~ '^pg_.*'
@@ -1111,8 +1110,8 @@ module ActiveRecord
end
def serial_sequence(table, column)
- result = exec_query(<<-eosql, 'SCHEMA', [[nil, table], [nil, column]])
- SELECT pg_get_serial_sequence($1, $2)
+ result = exec_query(<<-eosql, 'SCHEMA')
+ SELECT pg_get_serial_sequence('#{table}', '#{column}')
eosql
result.rows.first.first
end
@@ -1189,13 +1188,13 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
- row = exec_query(<<-end_sql, 'SCHEMA', [[nil, table]]).rows.first
+ row = exec_query(<<-end_sql, 'SCHEMA').rows.first
SELECT DISTINCT(attr.attname)
FROM pg_attribute attr
INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
WHERE cons.contype = 'p'
- AND dep.refobjid = $1::regclass
+ AND dep.refobjid = '#{table}'::regclass
end_sql
row && row.first
@@ -1472,7 +1471,7 @@ module ActiveRecord
end
def last_insert_id_result(sequence_name) #:nodoc:
- exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]])
+ exec_query("SELECT currval('#{sequence_name}')", 'SQL')
end
# Executes a SELECT query and returns the results, performing any data type
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index d4ffa82b17..a0c7e559ce 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -208,7 +208,6 @@ module ActiveRecord
true
end
-
# QUOTING ==================================================
def quote(value, column = nil)
@@ -220,7 +219,6 @@ module ActiveRecord
end
end
-
def quote_string(s) #:nodoc:
@connection.class.quote(s)
end
@@ -359,7 +357,7 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def tables(name = 'SCHEMA', table_name = nil) #:nodoc:
+ def tables(name = nil, table_name = nil) #:nodoc:
sql = <<-SQL
SELECT name
FROM sqlite_master
@@ -367,13 +365,13 @@ module ActiveRecord
SQL
sql << " AND name = #{quote_table_name(table_name)}" if table_name
- exec_query(sql, name).map do |row|
+ exec_query(sql, 'SCHEMA').map do |row|
row['name']
end
end
- def table_exists?(name)
- name && tables('SCHEMA', name).any?
+ def table_exists?(table_name)
+ table_name && tables(nil, table_name).any?
end
# Returns an array of +SQLite3Column+ objects for the table specified by +table_name+.
@@ -394,12 +392,12 @@ module ActiveRecord
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil) #:nodoc:
- exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
+ exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
IndexDefinition.new(
table_name,
row['name'],
row['unique'] != 0,
- exec_query("PRAGMA index_info('#{row['name']}')").map { |col|
+ exec_query("PRAGMA index_info('#{row['name']}')", "Columns for index #{row['name']} on #{table_name}").map { |col|
col['name']
})
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index f2833fbf3c..1fa6c701bb 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -10,9 +10,10 @@ module ActiveRecord
included do
##
# :singleton-method:
- # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
- # which is then passed on to any new database connections made and which can be retrieved on both
- # a class and instance level by calling +logger+.
+ #
+ # Accepts a logger conforming to the interface of Log4r which is then
+ # passed on to any new database connections made and which can be
+ # retrieved on both a class and instance level by calling +logger+.
config_attribute :logger, :global => true
##
@@ -240,7 +241,7 @@ module ActiveRecord
##
def initialize_dup(other) # :nodoc:
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
- self.class.initialize_attributes(cloned_attributes)
+ self.class.initialize_attributes(cloned_attributes, :serialized => false)
cloned_attributes.delete(self.class.primary_key)
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index a3412582fa..05e052b953 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -168,7 +168,7 @@ module ActiveRecord
# start the lock version at zero. Note we can't use
# <tt>locking_enabled?</tt> at this point as
# <tt>@attributes</tt> may not have been initialized yet.
- def initialize_attributes(attributes) #:nodoc:
+ def initialize_attributes(attributes, options = {}) #:nodoc:
if attributes.key?(locking_column) && lock_optimistically
attributes[locking_column] ||= 0
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 2a9139749d..ac4f53c774 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,7 +1,6 @@
require "active_support/core_ext/module/delegation"
require "active_support/core_ext/class/attribute_accessors"
require 'active_support/deprecation'
-require 'active_record/schema_migration'
require 'set'
module ActiveRecord
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index eb2769f1ef..1e497b2a79 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -30,6 +30,7 @@ module ActiveRecord
)
rake_tasks do
+ require "active_record/base"
load "active_record/railties/databases.rake"
end
@@ -38,10 +39,15 @@ module ActiveRecord
# first time. Also, make it output to STDERR.
console do |app|
require "active_record/railties/console_sandbox" if app.sandbox?
+ require "active_record/base"
console = ActiveSupport::Logger.new(STDERR)
Rails.logger.extend ActiveSupport::Logger.broadcast console
end
+ runner do |app|
+ require "active_record/base"
+ end
+
initializer "active_record.initialize_timezone" do
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index f26e18b1e0..d8d4834d22 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -406,10 +406,11 @@ db_namespace = namespace :db do
set_psql_env(abcs[Rails.env])
search_path = abcs[Rails.env]['schema_search_path']
unless search_path.blank?
- search_path = search_path.split(",").map{|search_path_part| "--schema=#{search_path_part.strip}" }.join(" ")
+ search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ")
end
- `pg_dump -i -s -x -O -f #{filename} #{search_path} #{abcs[Rails.env]['database']}`
+ `pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(abcs[Rails.env]['database'])}`
raise 'Error dumping database' if $?.exitstatus == 1
+ File.open(filename, "a") { |f| f << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" }
when /sqlite/
dbfile = abcs[Rails.env]['database']
`sqlite3 #{dbfile} .schema > #{filename}`
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 15f838a5ab..fb4388d4b2 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -2,20 +2,26 @@ require 'active_support/core_ext/object/blank'
module ActiveRecord
module Batches
- # Yields each record that was found by the find +options+. The find is
- # performed by find_in_batches with a batch size of 1000 (or as
+ # Looping through a collection of records from the database
+ # (using the +all+ method, for example) is very inefficient
+ # since it will try to instantiate all the objects at once.
+ #
+ # In that case, batch processing methods allow you to work
+ # with the records in batches, thereby greatly reducing memory consumption.
+ #
+ # The <tt>find_each</tt> method uses <tt>find_in_batches</tt> with a batch size of 1000 (or as
# specified by the <tt>:batch_size</tt> option).
#
- # Example:
+ # Person.all.find_each do |person|
+ # person.do_awesome_stuff
+ # end
#
# Person.where("age > 21").find_each do |person|
# person.party_all_night!
# end
#
- # Note: This method is only intended to use for batch processing of
- # large amounts of records that wouldn't fit in memory all at once. If
- # you just need to loop over less than 1000 records, it's probably
- # better just to use the regular find methods.
+ # You can also pass the <tt>:start</tt> option to specify
+ # an offset to control the starting point.
def find_each(options = {})
find_in_batches(options) do |records|
records.each { |record| yield record }
@@ -39,12 +45,15 @@ module ActiveRecord
# primary keys. You can't set the limit either, that's used to control
# the batch sizes.
#
- # Example:
- #
# Person.where("age > 21").find_in_batches do |group|
# sleep(50) # Make sure it doesn't get too crowded in there!
# group.each { |person| person.party_all_night! }
# end
+ #
+ # # Let's process the next 2000 records
+ # Person.all.find_in_batches(start: 2000, batch_size: 2000) do |group|
+ # group.each { |person| person.party_all_night! }
+ # end
def find_in_batches(options = {})
options.assert_valid_keys(:start, :batch_size)
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 31d99f0192..ad49c80e4f 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -16,9 +16,16 @@ module ActiveRecord
#
# Person.count(:age, distinct: true)
# # => counts the number of different age values
+ #
+ # Person.where("age > 26").count { |person| gender == 'female' }
+ # # => queries people where "age > 26" then count the loaded results filtering by gender
def count(column_name = nil, options = {})
- column_name, options = nil, column_name if column_name.is_a?(Hash)
- calculate(:count, column_name, options)
+ if block_given?
+ self.to_a.count { |item| yield item }
+ else
+ column_name, options = nil, column_name if column_name.is_a?(Hash)
+ calculate(:count, column_name, options)
+ end
end
# Calculates the average value on a given column. Returns +nil+ if there's
@@ -52,9 +59,13 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.sum('age') # => 4562
+ # # => returns the total sum of all people's age
+ #
+ # Person.where('age > 100').sum { |person| person.age - 100 }
+ # # queries people where "age > 100" then perform a sum calculation with the block returns
def sum(*args)
if block_given?
- self.to_a.sum(*args) {|*block_args| yield(*block_args)}
+ self.to_a.sum(*args) { |item| yield item }
else
calculate(:sum, *args)
end
@@ -118,7 +129,7 @@ module ActiveRecord
# Person.all.map(&:name)
#
# Pluck returns an <tt>Array</tt> of attribute values type-casted to match
- # the plucked column name, if it can be deduced. Plucking a SQL fragment
+ # the plucked column name, if it can be deduced. Plucking an SQL fragment
# returns String values by default.
#
# Examples:
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 4fedd33d64..5f6898b45a 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -7,8 +7,6 @@ module ActiveRecord
# If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
# is an integer, find by id coerces its arguments using +to_i+.
#
- # ==== Examples
- #
# Person.find(1) # returns the object for ID = 1
# Person.find("1") # returns the object for ID = 1
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
@@ -49,7 +47,6 @@ module ActiveRecord
#
# Post.find_by name: 'Spartacus', rating: 4
# Post.find_by "published_at < ?", 2.weeks.ago
- #
def find_by(*args)
where(*args).take
end
@@ -64,8 +61,6 @@ module ActiveRecord
# order. The order will depend on the database implementation.
# If an order is supplied it will be respected.
#
- # Examples:
- #
# Person.take # returns an object fetched by SELECT * FROM people
# Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
# Person.where(["name LIKE '%?'", name]).take
@@ -82,12 +77,11 @@ module ActiveRecord
# Find the first record (or first N records if a parameter is supplied).
# If no order is defined it will order by primary key.
#
- # Examples:
- #
# Person.first # returns the first object fetched by SELECT * FROM people
# Person.where(["user_name = ?", user_name]).first
# Person.where(["user_name = :u", { :u => user_name }]).first
# Person.order("created_on DESC").offset(5).first
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
def first(limit = nil)
if limit
if order_values.empty? && primary_key
@@ -109,11 +103,18 @@ module ActiveRecord
# Find the last record (or last N records if a parameter is supplied).
# If no order is defined it will order by primary key.
#
- # Examples:
- #
# Person.last # returns the last object fetched by SELECT * FROM people
# Person.where(["user_name = ?", user_name]).last
# Person.order("created_on DESC").offset(5).last
+ # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
+ #
+ # Take note that in that last case, the results are sorted in ascending order:
+ #
+ # [#<Person id:2>, #<Person id:3>, #<Person id:4>]
+ #
+ # and not:
+ #
+ # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
def last(limit = nil)
if limit
if order_values.empty? && primary_key
@@ -132,7 +133,8 @@ module ActiveRecord
last or raise RecordNotFound
end
- # Examples:
+ # Runs the query on the database and returns records with the used query
+ # methods.
#
# Person.all # returns an array of objects for all the rows fetched by SELECT * FROM people
# Person.where(["category IN (?)", categories]).limit(50).all
@@ -163,11 +165,10 @@ module ActiveRecord
# 'Jamie'</tt>), since it would be sanitized and then queried against
# the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
#
- # ==== Examples
# Person.exists?(5)
# Person.exists?('5')
- # Person.exists?(:name => "David")
# Person.exists?(['name LIKE ?', "%#{query}%"])
+ # Person.exists?(:name => "David")
# Person.exists?
def exists?(id = false)
id = id.id if ActiveRecord::Model === id
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 684c7f5929..276c499276 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -4,6 +4,13 @@ class MysqlConnectionTest < ActiveRecord::TestCase
def setup
super
@connection = ActiveRecord::Model.connection
+ @connection.extend(LogIntercepter)
+ @connection.intercepted = true
+ end
+
+ def teardown
+ @connection.intercepted = false
+ @connection.logged = []
end
def test_no_automatic_reconnection_after_timeout
@@ -45,6 +52,26 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_logs_name_structure_dump
+ @connection.structure_dump
+ assert_equal "SCHEMA", @connection.logged[0][1]
+ assert_equal "SCHEMA", @connection.logged[2][1]
+ end
+
+ def test_logs_name_show_variable
+ @connection.show_variable 'foo'
+ assert_equal "SCHEMA", @connection.logged[0][1]
+ end
+
+ def test_logs_name_rename_column_sql
+ @connection.execute "CREATE TABLE `bar_baz` (`foo` varchar(255))"
+ @connection.logged = []
+ @connection.send(:rename_column_sql, 'bar_baz', 'foo', 'foo2')
+ assert_equal "SCHEMA", @connection.logged[0][1]
+ ensure
+ @connection.execute "DROP TABLE `bar_baz`"
+ end
+
private
def run_without_connection
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 4baec749ff..adb2cef010 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -8,6 +8,13 @@ module ActiveRecord
def setup
super
@connection = ActiveRecord::Base.connection
+ @connection.extend(LogIntercepter)
+ @connection.intercepted = true
+ end
+
+ def teardown
+ @connection.intercepted = false
+ @connection.logged = []
end
def test_encoding
@@ -25,5 +32,42 @@ module ActiveRecord
expect = NonExistentTable.connection.query('show geqo').first.first
assert_equal 'off', expect
end
+
+ def test_tables_logs_name
+ @connection.tables('hello')
+ assert_equal 'SCHEMA', @connection.logged[0][1]
+ end
+
+ def test_indexes_logs_name
+ @connection.indexes('items', 'hello')
+ assert_equal 'SCHEMA', @connection.logged[0][1]
+ end
+
+ def test_table_exists_logs_name
+ @connection.table_exists?('items')
+ assert_equal 'SCHEMA', @connection.logged[0][1]
+ end
+
+ def test_table_alias_length_logs_name
+ @connection.instance_variable_set("@table_alias_length", nil)
+ @connection.table_alias_length
+ assert_equal 'SCHEMA', @connection.logged[0][1]
+ end
+
+ def test_current_database_logs_name
+ @connection.current_database
+ assert_equal 'SCHEMA', @connection.logged[0][1]
+ end
+
+ def test_encoding_logs_name
+ @connection.encoding
+ assert_equal 'SCHEMA', @connection.logged[0][1]
+ end
+
+ def test_schema_names_logs_name
+ @connection.schema_names
+ assert_equal 'SCHEMA', @connection.logged[0][1]
+ end
+
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 8a7f44d0a3..5e947799cc 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -20,6 +20,14 @@ module ActiveRecord
number integer
)
eosql
+
+ @conn.extend(LogIntercepter)
+ @conn.intercepted = true
+ end
+
+ def teardown
+ @conn.intercepted = false
+ @conn.logged = []
end
def test_column_types
@@ -232,13 +240,23 @@ module ActiveRecord
end
def test_tables_logs_name
- name = "hello"
- assert_logged [[name, []]] do
- @conn.tables(name)
+ assert_logged [['SCHEMA', []]] do
+ @conn.tables('hello')
assert_not_nil @conn.logged.first.shift
end
end
+ def test_indexes_logs_name
+ assert_logged [["PRAGMA index_list(\"items\")", 'SCHEMA', []]] do
+ @conn.indexes('items', 'hello')
+ end
+ end
+
+ def test_table_exists_logs_name
+ assert @conn.table_exists?('items')
+ assert_equal 'SCHEMA', @conn.logged[0][1]
+ end
+
def test_columns
columns = @conn.columns('items').sort_by { |x| x.name }
assert_equal 2, columns.length
@@ -274,7 +292,6 @@ module ActiveRecord
end
def test_indexes_logs
- intercept_logs_on @conn
assert_difference('@conn.logged.length') do
@conn.indexes('items')
end
@@ -326,21 +343,10 @@ module ActiveRecord
private
def assert_logged logs
- intercept_logs_on @conn
yield
assert_equal logs, @conn.logged
end
- def intercept_logs_on ctx
- @conn.extend(Module.new {
- attr_accessor :logged
- def log sql, name, binds = []
- @logged << [sql, name, binds]
- yield
- end
- })
- @conn.logged = []
- end
end
end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 8b384c2513..0d8f311117 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -936,10 +936,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, summit.client_of
end
- def test_deleting_type_mismatch
+ def test_deleting_by_fixnum_id
david = Developer.find(1)
- david.projects.reload
- assert_raise(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(1) }
+
+ assert_difference 'david.projects.count', -1 do
+ assert_equal 1, david.projects.delete(1).size
+ end
+
+ assert_equal 1, david.projects.size
+ end
+
+ def test_deleting_by_string_id
+ david = Developer.find(1)
+
+ assert_difference 'david.projects.count', -1 do
+ assert_equal 1, david.projects.delete('1').size
+ end
+
+ assert_equal 1, david.projects.size
end
def test_deleting_self_type_mismatch
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index ecc676f300..783b83631c 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -578,7 +578,27 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_deleting_junk_from_has_many_through_should_raise_type_mismatch
- assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete("Uhh what now?") }
+ assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete(Object.new) }
+ end
+
+ def test_deleting_by_fixnum_id_from_has_many_through
+ post = posts(:thinking)
+
+ assert_difference 'post.tags.count', -1 do
+ assert_equal 1, post.tags.delete(1).size
+ end
+
+ assert_equal 0, post.tags.size
+ end
+
+ def test_deleting_by_string_id_from_has_many_through
+ post = posts(:thinking)
+
+ assert_difference 'post.tags.count', -1 do
+ assert_equal 1, post.tags.delete('1').size
+ end
+
+ assert_equal 0, post.tags.size
end
def test_has_many_through_sum_uses_calculations
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 619fb881fa..f95230ff50 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1309,6 +1309,15 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal({ :foo => :bar }, t.content_before_type_cast)
end
+ def test_serialized_attribute_calling_dup_method
+ klass = Class.new(ActiveRecord::Base)
+ klass.table_name = "topics"
+ klass.serialize :content, JSON
+
+ t = klass.new(:content => { :foo => :bar }).dup
+ assert_equal({ :foo => :bar }, t.content_before_type_cast)
+ end
+
def test_serialized_attribute_declared_in_subclass
hash = { 'important1' => 'value1', 'important2' => 'value2' }
important_topic = ImportantTopic.create("important" => hash)
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 041f8ffb7c..a279b0e77c 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -376,6 +376,22 @@ class CalculationsTest < ActiveRecord::TestCase
Company.where(:type => "Firm").from('companies').count(:type)
end
+ def test_count_with_block_acts_as_array
+ accounts = Account.where('id > 0')
+ assert_equal Account.count, accounts.count { true }
+ assert_equal 0, accounts.count { false }
+ assert_equal Account.where('credit_limit > 50').size, accounts.count { |account| account.credit_limit > 50 }
+ assert_equal Account.count, Account.count { true }
+ assert_equal 0, Account.count { false }
+ end
+
+ def test_sum_with_block_acts_as_array
+ accounts = Account.where('id > 0')
+ assert_equal Account.sum(:credit_limit), accounts.sum { |account| account.credit_limit }
+ assert_equal Account.sum(:credit_limit) + Account.count, accounts.sum{ |account| account.credit_limit + 1 }
+ assert_equal 0, accounts.sum { |account| 0 }
+ end
+
def test_sum_with_from_option
assert_equal Account.sum(:credit_limit), Account.from('accounts').sum(:credit_limit)
assert_equal Account.where("credit_limit > 50").sum(:credit_limit),
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 37fa13f771..afff020561 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -121,3 +121,19 @@ class << Time
@now = nil
end
end
+
+module LogIntercepter
+ attr_accessor :logged, :intercepted
+ def self.extended(base)
+ base.logged = []
+ end
+ def log(sql, name, binds = [], &block)
+ if @intercepted
+ @logged << [sql, name, binds]
+ yield
+ else
+ super(sql, name,binds, &block)
+ end
+ end
+end
+
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 62b8a789c7..273dc347ce 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,9 @@
## Rails 4.0.0 (unreleased) ##
+* ActionView::Helpers::NumberHelper methods have been moved to ActiveSupport::NumberHelper and are now available via
+ Numeric#to_s. Numeric#to_s now accepts the formatting options :phone, :currency, :percentage, :delimited,
+ :rounded, :human, and :human_size. *Andrew Mutz*
+
* Add `Hash#transform_keys`, `Hash#transform_keys!`, `Hash#deep_transform_keys`, and `Hash#deep_transform_keys!`. *Mark McSpadden*
* Changed xml type `datetime` to `dateTime` (with upper case letter `T`). *Angelo Capilleri*
@@ -53,12 +57,17 @@
* Adds `encode_big_decimal_as_string` option to force JSON serialization of BigDecimals as numeric instead
of wrapping them in strings for safety.
+* Remove deprecated ActiveSupport::JSON::Variable. *Erich Menge*
+
## Rails 3.2.4 (unreleased) ##
* Added #beginning_of_hour and #end_of_hour to Time and DateTime core
extensions. *Mark J. Titorenko*
+* ActiveSupport::JSON::Variable is deprecated. Define your own #as_json and #encode_json methods
+ for custom JSON string literals. *Erich Menge*
+
## Rails 3.2.3 (March 30, 2012) ##
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 55791bfa56..a62214d604 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -405,7 +405,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support increment")
end
- # Increment an integer value in the cache.
+ # Decrement an integer value in the cache.
#
# Options are passed to the underlying cache implementation.
#
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 44d90ef732..a8f9dddae5 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -1,40 +1,48 @@
class Array
# Returns the tail of the array from +position+.
#
- # %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) # => %w()
- # %w().from(0) # => %w()
+ # %w( a b c d ).from(0) # => ["a", "b", "c", "d"]
+ # %w( a b c d ).from(2) # => ["c", "d"]
+ # %w( a b c d ).from(10) # => []
+ # %w().from(0) # => []
def from(position)
self[position, length] || []
end
# Returns the beginning of the array up to +position+.
#
- # %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()
+ # %w( a b c d ).to(0) # => ["a"]
+ # %w( a b c d ).to(2) # => ["a", "b", "c"]
+ # %w( a b c d ).to(10) # => ["a", "b", "c", "d"]
+ # %w().to(0) # => []
def to(position)
first position + 1
end
# Equal to <tt>self[1]</tt>.
+ #
+ # %w( a b c d e).second # => "b"
def second
self[1]
end
# Equal to <tt>self[2]</tt>.
+ #
+ # %w( a b c d e).third # => "c"
def third
self[2]
end
# Equal to <tt>self[3]</tt>.
+ #
+ # %w( a b c d e).fourth # => "d"
def fourth
self[3]
end
# Equal to <tt>self[4]</tt>.
+ #
+ # %w( a b c d e).fifth # => "e"
def fifth
self[4]
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 24aa28b895..1e0de651c7 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -4,10 +4,55 @@ require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/string/inflections'
class Array
- # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
- # * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ")
- # * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
- # * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
+ # Converts the array to a comma-separated sentence where the last element is
+ # joined by the connector word.
+ #
+ # You can pass the following options to change the default behaviour. If you
+ # pass an option key that doesn't exist in the list below, it will raise an
+ # <tt>ArgumentError</tt>.
+ #
+ # Options:
+ #
+ # * <tt>:words_connector</tt> - The sign or word used to join the elements
+ # in arrays with two or more elements (default: ", ").
+ # * <tt>:two_words_connector</tt> - The sign or word used to join the elements
+ # in arrays with two elements (default: " and ").
+ # * <tt>:last_word_connector</tt> - The sign or word used to join the last element
+ # in arrays with three or more elements (default: ", and ").
+ # * <tt>:locale</tt> - If +i18n+ is available, you can set a locale and use
+ # the connector options defined on the 'support.array' namespace in the
+ # corresponding dictionary file.
+ #
+ # [].to_sentence # => ""
+ # ['one'].to_sentence # => "one"
+ # ['one', 'two'].to_sentence # => "one and two"
+ # ['one', 'two', 'three'].to_sentence # => "one, two, and three"
+ #
+ # ['one', 'two'].to_sentence(passing: 'invalid option')
+ # # => ArgumentError: Unknown key :passing
+ #
+ # ['one', 'two'].to_sentence(two_words_connector: '-')
+ # # => "one-two"
+ #
+ # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ')
+ # # => "one or two or at least three"
+ #
+ # Examples using <tt>:locale</tt> option:
+ #
+ # # Given this locale dictionary:
+ # # 
+ # # es:
+ # # support:
+ # # array:
+ # # words_connector: " o "
+ # # two_words_connector: " y "
+ # # last_word_connector: " o al menos "
+ #
+ # ['uno', 'dos'].to_sentence(locale: :es)
+ # # => "uno y dos"
+ #
+ # ['uno', 'dos', 'tres'].to_sentence(locale: :es)
+ # # => "uno o dos o al menos tres"
def to_sentence(options = {})
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
@@ -39,7 +84,17 @@ class Array
end
# Converts a collection of elements into a formatted string by calling
- # <tt>to_s</tt> on all elements and joining them:
+ # <tt>to_s</tt> on all elements and joining them. Having this model:
+ #
+ # class Blog < ActiveRecord::Base
+ # def to_s
+ # title
+ # end
+ # end
+ #
+ # Blog.all.map(&:title) #=> ["First Post", "Second Post", "Third post"]
+ #
+ # <tt>to_formatted_s</tt> shows us:
#
# Blog.all.to_formatted_s # => "First PostSecond PostThird Post"
#
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index ac1ae53db0..a184eb492a 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -2,18 +2,21 @@ class Array
# Splits or iterates over the array in groups of size +number+,
# padding any remaining slots with +fill_with+ unless it is +false+.
#
- # %w(1 2 3 4 5 6 7).in_groups_of(3) {|group| p group}
+ # %w(1 2 3 4 5 6 7 8 9 10).in_groups_of(3) {|group| p group}
# ["1", "2", "3"]
# ["4", "5", "6"]
- # ["7", nil, nil]
+ # ["7", "8", "9"]
+ # ["10", nil, nil]
#
- # %w(1 2 3).in_groups_of(2, '&nbsp;') {|group| p group}
+ # %w(1 2 3 4 5).in_groups_of(2, '&nbsp;') {|group| p group}
# ["1", "2"]
- # ["3", "&nbsp;"]
+ # ["3", "4"]
+ # ["5", "&nbsp;"]
#
- # %w(1 2 3).in_groups_of(2, false) {|group| p group}
+ # %w(1 2 3 4 5).in_groups_of(2, false) {|group| p group}
# ["1", "2"]
- # ["3"]
+ # ["3", "4"]
+ # ["5"]
def in_groups_of(number, fill_with = nil)
if fill_with == false
collection = self
@@ -42,10 +45,10 @@ class Array
# ["5", "6", "7", nil]
# ["8", "9", "10", nil]
#
- # %w(1 2 3 4 5 6 7).in_groups(3, '&nbsp;') {|group| p group}
- # ["1", "2", "3"]
- # ["4", "5", "&nbsp;"]
- # ["6", "7", "&nbsp;"]
+ # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3, '&nbsp;') {|group| p group}
+ # ["1", "2", "3", "4"]
+ # ["5", "6", "7", "&nbsp;"]
+ # ["8", "9", "10", "&nbsp;"]
#
# %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group}
# ["1", "2", "3"]
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index 3ec7e576c8..5dc5710c53 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -17,8 +17,13 @@ class BigDecimal
end
DEFAULT_STRING_FORMAT = 'F'
- def to_formatted_s(format = DEFAULT_STRING_FORMAT)
- _original_to_s(format)
+ def to_formatted_s(*args)
+ if args[0].is_a?(Symbol)
+ super
+ else
+ format = args[0] || DEFAULT_STRING_FORMAT
+ _original_to_s(format)
+ end
end
alias_method :_original_to_s, :to_s
alias_method :to_s, :to_formatted_s
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index 74ea047c24..c2e0ebb3d4 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -1,11 +1,11 @@
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/reachable'
-class Class #:nodoc:
+class Class
begin
ObjectSpace.each_object(Class.new) {}
- def descendants
+ def descendants # :nodoc:
descendants = []
ObjectSpace.each_object(singleton_class) do |k|
descendants.unshift k unless k == self
@@ -13,7 +13,7 @@ class Class #:nodoc:
descendants
end
rescue StandardError # JRuby
- def descendants
+ def descendants # :nodoc:
descendants = []
ObjectSpace.each_object(Class) do |k|
descendants.unshift k if k < self
@@ -25,7 +25,13 @@ class Class #:nodoc:
# Returns an array with the direct children of +self+.
#
- # Integer.subclasses # => [Bignum, Fixnum]
+ # Integer.subclasses # => [Fixnum, Bignum]
+ #
+ # class Foo; end
+ # class Bar < Foo; end
+ # class Baz < Foo; end
+ #
+ # Foo.subclasses # => [Baz, Bar]
def subclasses
subclasses, chain = [], descendants
chain.each do |k|
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 3e36c54eba..8a7eb6bc6b 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -202,7 +202,7 @@ class Date
acts_like?(:time) ? result.change(:hour => 0) : result
end
- # Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00)
+ # Returns a new Date/DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00)
def beginning_of_month
acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1)
end
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 02d5a7080f..03efe6a19a 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -65,11 +65,15 @@ class Range #:nodoc:
# Optimize range sum to use arithmetic progression if a block is not given and
# we have a range of numeric values.
def sum(identity = 0)
- if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer))
+ if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
super
else
actual_last = exclude_end? ? (last - 1) : last
- (actual_last - first + 1) * (actual_last + first) / 2
+ if actual_last >= first
+ (actual_last - first + 1) * (actual_last + first) / 2
+ else
+ identity
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 43ba05a256..7c72ead36c 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -129,7 +129,7 @@ class Hash
else
xml_value = Hash[value.map { |k,v| [k, typecast_xml_value(v)] }]
- # Turn { :files => { :file => #<StringIO> } into { :files => #<StringIO> } so it is compatible with
+ # Turn { :files => { :file => #<StringIO> } } into { :files => #<StringIO> } so it is compatible with
# how multipart uploaded files from HTML appear
xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
end
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index 362d584ba1..8e728691c6 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,8 +1,10 @@
class Hash
# Return a new hash with all keys converted using the block operation.
#
- # { :name => 'Rob', :years => '28' }.transform_keys{ |key| key.to_s.upcase }
- # # => { "NAME" => "Rob", "YEARS" => "28" }
+ # hash = { name: 'Rob', age: '28' }
+ #
+ # hash.transform_keys{ |key| key.to_s.upcase }
+ # # => { "NAME" => "Rob", "AGE" => "28" }
def transform_keys
result = {}
keys.each do |key|
@@ -22,8 +24,10 @@ class Hash
# Return a new hash with all keys converted to strings.
#
- # { :name => 'Rob', :years => '28' }.stringify_keys
- # #=> { "name" => "Rob", "years" => "28" }
+ # hash = { name: 'Rob', age: '28' }
+ #
+ # hash.stringify_keys
+ # #=> { "name" => "Rob", "age" => "28" }
def stringify_keys
transform_keys{ |key| key.to_s }
end
@@ -37,8 +41,10 @@ class Hash
# Return a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+.
#
- # { 'name' => 'Rob', 'years' => '28' }.symbolize_keys
- # #=> { :name => "Rob", :years => "28" }
+ # hash = { 'name' => 'Rob', 'age' => '28' }
+ #
+ # hash.symbolize_keys
+ # #=> { name: "Rob", age: "28" }
def symbolize_keys
transform_keys{ |key| key.to_sym rescue key }
end
@@ -69,8 +75,10 @@ class Hash
# This includes the keys from the root hash and from all
# nested hashes.
#
- # { :person => { :name => 'Rob', :years => '28' } }.deep_transform_keys{ |key| key.to_s.upcase }
- # # => { "PERSON" => { "NAME" => "Rob", "YEARS" => "28" } }
+ # hash = { person: { name: 'Rob', age: '28' } }
+ #
+ # hash.deep_transform_keys{ |key| key.to_s.upcase }
+ # # => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } }
def deep_transform_keys(&block)
result = {}
each do |key, value|
@@ -93,6 +101,11 @@ class Hash
# Return a new hash with all keys converted to strings.
# This includes the keys from the root hash and from all
# nested hashes.
+ #
+ # hash = { person: { name: 'Rob', age: '28' } }
+ #
+ # hash.deep_stringify_keys
+ # # => { "person" => { "name" => "Rob", "age" => "28" } }
def deep_stringify_keys
deep_transform_keys{ |key| key.to_s }
end
@@ -104,17 +117,22 @@ class Hash
deep_transform_keys!{ |key| key.to_s }
end
- # Destructively convert all keys to symbols, as long as they respond
- # to +to_sym+. This includes the keys from the root hash and from all
- # nested hashes.
- def deep_symbolize_keys!
- deep_transform_keys!{ |key| key.to_sym rescue key }
- end
-
# Return a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+. This includes the keys from the root hash
# and from all nested hashes.
+ #
+ # hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
+ #
+ # hash.deep_symbolize_keys
+ # # => { person: { name: "Rob", age: "28" } }
def deep_symbolize_keys
deep_transform_keys{ |key| key.to_sym rescue key }
end
+
+ # Destructively convert all keys to symbols, as long as they respond
+ # to +to_sym+. This includes the keys from the root hash and from all
+ # nested hashes.
+ def deep_symbolize_keys!
+ deep_transform_keys!{ |key| key.to_sym rescue key }
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index f914425827..672cc0256f 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -46,19 +46,19 @@ class Module
# Extends the module object with module and instance accessors for class attributes,
# just like the native attr* accessors for instance attributes.
#
- # module AppConfiguration
- # mattr_accessor :google_api_key
- # self.google_api_key = "123456789"
+ # module AppConfiguration
+ # mattr_accessor :google_api_key
#
- # mattr_accessor :paypal_url
- # self.paypal_url = "www.sandbox.paypal.com"
- # end
+ # self.google_api_key = "123456789"
+ # end
#
- # AppConfiguration.google_api_key = "overriding the api key!"
+ # AppConfiguration.google_api_key # => "123456789"
+ # AppConfiguration.google_api_key = "overriding the api key!"
+ # AppConfiguration.google_api_key # => "overriding the api key!"
#
- # To opt out of the instance writer method, pass :instance_writer => false.
- # To opt out of the instance reader method, pass :instance_reader => false.
- # To opt out of both instance methods, pass :instance_accessor => false.
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
def mattr_accessor(*syms)
mattr_reader(*syms)
mattr_writer(*syms)
diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb
index 3805cf7990..a6bc0624be 100644
--- a/activesupport/lib/active_support/core_ext/numeric.rb
+++ b/activesupport/lib/active_support/core_ext/numeric.rb
@@ -1,2 +1,3 @@
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
+require 'active_support/core_ext/numeric/conversions'
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
new file mode 100644
index 0000000000..2bbfa78639
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -0,0 +1,135 @@
+require 'active_support/core_ext/big_decimal/conversions'
+require 'active_support/number_helper'
+
+class Numeric
+
+ # Provides options for converting numbers into formatted strings.
+ # Options are provided for phone numbers, currency, percentage,
+ # precision, positional notation, file size and pretty printing.
+ #
+ # ==== Options
+ #
+ # For details on which formats use which options, see ActiveSupport::NumberHelper
+ #
+ # ==== Examples
+ #
+ # Phone Numbers:
+ # 5551234.to_s(:phone) # => 555-1234
+ # 1235551234.to_s(:phone) # => 123-555-1234
+ # 1235551234.to_s(:phone, :area_code => true) # => (123) 555-1234
+ # 1235551234.to_s(:phone, :delimiter => " ") # => 123 555 1234
+ # 1235551234.to_s(:phone, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
+ # 1235551234.to_s(:phone, :country_code => 1) # => +1-123-555-1234
+ # 1235551234.to_s(:phone, :country_code => 1, :extension => 1343, :delimiter => ".")
+ # # => +1.123.555.1234 x 1343
+ #
+ # Currency:
+ # 1234567890.50.to_s(:currency) # => $1,234,567,890.50
+ # 1234567890.506.to_s(:currency) # => $1,234,567,890.51
+ # 1234567890.506.to_s(:currency, :precision => 3) # => $1,234,567,890.506
+ # 1234567890.506.to_s(:currency, :locale => :fr) # => 1 234 567 890,51 €
+ # -1234567890.50.to_s(:currency, :negative_format => "(%u%n)")
+ # # => ($1,234,567,890.50)
+ # 1234567890.50.to_s(:currency, :unit => "&pound;", :separator => ",", :delimiter => "")
+ # # => &pound;1234567890,50
+ # 1234567890.50.to_s(:currency, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
+ # # => 1234567890,50 &pound;
+ #
+ # Percentage:
+ # 100.to_s(:percentage) # => 100.000%
+ # 100.to_s(:percentage, :precision => 0) # => 100%
+ # 1000.to_s(:percentage, :delimiter => '.', :separator => ',') # => 1.000,000%
+ # 302.24398923423.to_s(:percentage, :precision => 5) # => 302.24399%
+ # 1000.to_s(:percentage, :locale => :fr) # => 1 000,000%
+ # 100.to_s(:percentage, :format => "%n %") # => 100 %
+ #
+ # Delimited:
+ # 12345678.to_s(:delimited) # => 12,345,678
+ # 12345678.05.to_s(:delimited) # => 12,345,678.05
+ # 12345678.to_s(:delimited, :delimiter => ".") # => 12.345.678
+ # 12345678.to_s(:delimited, :delimiter => ",") # => 12,345,678
+ # 12345678.05.to_s(:delimited, :separator => " ") # => 12,345,678 05
+ # 12345678.05.to_s(:delimited, :locale => :fr) # => 12 345 678,05
+ # 98765432.98.to_s(:delimited, :delimiter => " ", :separator => ",")
+ # # => 98 765 432,98
+ #
+ # Rounded:
+ # 111.2345.to_s(:rounded) # => 111.235
+ # 111.2345.to_s(:rounded, :precision => 2) # => 111.23
+ # 13.to_s(:rounded, :precision => 5) # => 13.00000
+ # 389.32314.to_s(:rounded, :precision => 0) # => 389
+ # 111.2345.to_s(:rounded, :significant => true) # => 111
+ # 111.2345.to_s(:rounded, :precision => 1, :significant => true) # => 100
+ # 13.to_s(:rounded, :precision => 5, :significant => true) # => 13.000
+ # 111.234.to_s(:rounded, :locale => :fr) # => 111,234
+ # 13.to_s(:rounded, :precision => 5, :significant => true, :strip_insignificant_zeros => true)
+ # # => 13
+ # 389.32314.to_s(:rounded, :precision => 4, :significant => true) # => 389.3
+ # 1111.2345.to_s(:rounded, :precision => 2, :separator => ',', :delimiter => '.')
+ # # => 1.111,23
+ #
+ # Human-friendly size in Bytes:
+ # 123.to_s(:human_size) # => 123 Bytes
+ # 1234.to_s(:human_size) # => 1.21 KB
+ # 12345.to_s(:human_size) # => 12.1 KB
+ # 1234567.to_s(:human_size) # => 1.18 MB
+ # 1234567890.to_s(:human_size) # => 1.15 GB
+ # 1234567890123.to_s(:human_size) # => 1.12 TB
+ # 1234567.to_s(:human_size, :precision => 2) # => 1.2 MB
+ # 483989.to_s(:human_size, :precision => 2) # => 470 KB
+ # 1234567.to_s(:human_size, :precision => 2, :separator => ',') # => 1,2 MB
+ # 1234567890123.to_s(:human_size, :precision => 5) # => "1.1229 TB"
+ # 524288000.to_s(:human_size, :precision => 5) # => "500 MB"
+ #
+ # Human-friendly format:
+ # 123.to_s(:human) # => "123"
+ # 1234.to_s(:human) # => "1.23 Thousand"
+ # 12345.to_s(:human) # => "12.3 Thousand"
+ # 1234567.to_s(:human) # => "1.23 Million"
+ # 1234567890.to_s(:human) # => "1.23 Billion"
+ # 1234567890123.to_s(:human) # => "1.23 Trillion"
+ # 1234567890123456.to_s(:human) # => "1.23 Quadrillion"
+ # 1234567890123456789.to_s(:human) # => "1230 Quadrillion"
+ # 489939.to_s(:human, :precision => 2) # => "490 Thousand"
+ # 489939.to_s(:human, :precision => 4) # => "489.9 Thousand"
+ # 1234567.to_s(:human, :precision => 4,
+ # :significant => false) # => "1.2346 Million"
+ # 1234567.to_s(:human, :precision => 1,
+ # :separator => ',',
+ # :significant => false) # => "1,2 Million"
+ def to_formatted_s(format = :default, options = {})
+ case format
+ when :phone
+ return ActiveSupport::NumberHelper.number_to_phone(self, options)
+ when :currency
+ return ActiveSupport::NumberHelper.number_to_currency(self, options)
+ when :percentage
+ return ActiveSupport::NumberHelper.number_to_percentage(self, options)
+ when :delimited
+ return ActiveSupport::NumberHelper.number_to_delimited(self, options)
+ when :rounded
+ return ActiveSupport::NumberHelper.number_to_rounded(self, options)
+ when :human
+ return ActiveSupport::NumberHelper.number_to_human(self, options)
+ when :human_size
+ return ActiveSupport::NumberHelper.number_to_human_size(self, options)
+ else
+ self.to_default_s
+ end
+ end
+
+ [Float, Fixnum, Bignum, BigDecimal].each do |klass|
+ klass.send(:alias_method, :to_default_s, :to_s)
+
+ klass.send(:define_method, :to_s) do |*args|
+ if args[0].is_a?(Symbol)
+ format = args[0]
+ options = args[1] || {}
+
+ self.to_formatted_s(format, options)
+ else
+ to_default_s(*args)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
index 883f5f556c..f55fbc282e 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/duplicable'
+
class Object
# Returns a deep copy of object if it's duplicable. If it's
# not duplicable, returns +self+.
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index 5c32a2453d..8fa8157d65 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -1,5 +1,3 @@
-require 'active_support/multibyte'
-
class String
# If you pass a single Fixnum, returns a substring of one character at that
# position. The first character of the string is at position 0, the next at
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index 2478f42290..8644529806 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/string/multibyte'
-
class String
# Returns the string, first removing all whitespace on both ends of
# the string, and then changing remaining consecutive whitespace
@@ -33,7 +31,7 @@ class String
# # => "Once upon a time in a..."
#
# The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
- # for a total length not exceeding <tt>:length</tt>:
+ # for a total length not exceeding <tt>length</tt>:
#
# 'And they found that many people were sleeping better.'.truncate(25, :omission => '... (continued)')
# # => "And they f... (continued)"
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index 986a764479..72fd97ceee 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -8,6 +8,11 @@ module ActiveSupport
module JSON
class << self
+ # Parses a JSON string (JavaScript Object Notation) into a hash.
+ # See www.json.org for more info.
+ #
+ # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
+ # => {"team" => "rails", "players" => "36"}
def decode(json, options ={})
data = MultiJson.load(json, options)
if ActiveSupport.parse_json_times
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index a6e4e7ced2..6ed253e141 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/object/to_json'
require 'active_support/core_ext/module/delegation'
-require 'active_support/json/variable'
require 'bigdecimal'
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
@@ -25,7 +24,10 @@ module ActiveSupport
# matches YAML-formatted dates
DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/
- # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
+ # Dumps objects in JSON (JavaScript Object Notation). See www.json.org for more info.
+ #
+ # ActiveSupport::JSON.encode({team: 'rails', players: '36'})
+ # # => "{\"team\":\"rails\",\"players\":\"36\"}"
def self.encode(value, options = nil)
Encoding::Encoder.new(options).encode(value)
end
@@ -159,18 +161,18 @@ class Struct #:nodoc:
end
class TrueClass
- AS_JSON = ActiveSupport::JSON::Variable.new('true').freeze
- def as_json(options = nil) AS_JSON end #:nodoc:
+ def as_json(options = nil) self end #:nodoc:
+ def encode_json(encoder) to_s end #:nodoc:
end
class FalseClass
- AS_JSON = ActiveSupport::JSON::Variable.new('false').freeze
- def as_json(options = nil) AS_JSON end #:nodoc:
+ def as_json(options = nil) self end #:nodoc:
+ def encode_json(encoder) to_s end #:nodoc:
end
class NilClass
- AS_JSON = ActiveSupport::JSON::Variable.new('null').freeze
- def as_json(options = nil) AS_JSON end #:nodoc:
+ def as_json(options = nil) self end #:nodoc:
+ def encode_json(encoder) 'null' end #:nodoc:
end
class String
@@ -189,8 +191,8 @@ end
class Float
# Encoding Infinity or NaN to JSON should return "null". The default returns
- # "Infinity" or "NaN" what breaks parsing the JSON. E.g. JSON.parse('[NaN]').
- def as_json(options = nil) finite? ? self : NilClass::AS_JSON end #:nodoc:
+ # "Infinity" or "NaN" breaks parsing the JSON. E.g. JSON.parse('[NaN]').
+ def as_json(options = nil) finite? ? self : nil end #:nodoc:
end
class BigDecimal
@@ -208,7 +210,7 @@ class BigDecimal
if finite?
ActiveSupport.encode_big_decimal_as_string ? to_s : self
else
- NilClass::AS_JSON
+ nil
end
end
end
diff --git a/activesupport/lib/active_support/json/variable.rb b/activesupport/lib/active_support/json/variable.rb
deleted file mode 100644
index 5685ed18b7..0000000000
--- a/activesupport/lib/active_support/json/variable.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module ActiveSupport
- module JSON
- # A string that returns itself as its JSON-encoded form.
- class Variable < String
- def as_json(options = nil) self end #:nodoc:
- def encode_json(encoder) self end #:nodoc:
- end
- end
-end
diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml
index a1499bcc90..18c7d47026 100644
--- a/activesupport/lib/active_support/locale/en.yml
+++ b/activesupport/lib/active_support/locale/en.yml
@@ -34,3 +34,102 @@ en:
words_connector: ", "
two_words_connector: " and "
last_word_connector: ", and "
+ number:
+ # Used in NumberHelper.number_to_delimited()
+ # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
+ format:
+ # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
+ separator: "."
+ # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
+ delimiter: ","
+ # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
+ precision: 3
+ # If set to true, precision will mean the number of significant digits instead
+ # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
+ significant: false
+ # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
+ strip_insignificant_zeros: false
+
+ # Used in NumberHelper.number_to_currency()
+ currency:
+ format:
+ # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
+ format: "%u%n"
+ unit: "$"
+ # These five are to override number.format and are optional
+ separator: "."
+ delimiter: ","
+ precision: 2
+ significant: false
+ strip_insignificant_zeros: false
+
+ # Used in NumberHelper.number_to_percentage()
+ percentage:
+ format:
+ # These five are to override number.format and are optional
+ # separator:
+ delimiter: ""
+ # precision:
+ # significant: false
+ # strip_insignificant_zeros: false
+ format: "%n%"
+
+ # Used in NumberHelper.number_to_rounded()
+ precision:
+ format:
+ # These five are to override number.format and are optional
+ # separator:
+ delimiter: ""
+ # precision:
+ # significant: false
+ # strip_insignificant_zeros: false
+
+ # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human()
+ human:
+ format:
+ # These five are to override number.format and are optional
+ # separator:
+ delimiter: ""
+ precision: 3
+ significant: true
+ strip_insignificant_zeros: true
+ # Used in number_to_human_size()
+ storage_units:
+ # Storage units output formatting.
+ # %u is the storage unit, %n is the number (default: 2 MB)
+ format: "%n %u"
+ units:
+ byte:
+ one: "Byte"
+ other: "Bytes"
+ kb: "KB"
+ mb: "MB"
+ gb: "GB"
+ tb: "TB"
+ # Used in NumberHelper.number_to_human()
+ decimal_units:
+ format: "%n %u"
+ # Decimal units output formatting
+ # By default we will only quantify some of the exponents
+ # but the commented ones might be defined or overridden
+ # by the user.
+ units:
+ # femto: Quadrillionth
+ # pico: Trillionth
+ # nano: Billionth
+ # micro: Millionth
+ # mili: Thousandth
+ # centi: Hundredth
+ # deci: Tenth
+ unit: ""
+ # ten:
+ # one: Ten
+ # other: Tens
+ # hundred: Hundred
+ thousand: Thousand
+ million: Million
+ billion: Billion
+ trillion: Trillion
+ quadrillion: Quadrillion
+
+ \ No newline at end of file
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 4fe925f7f4..87b1d76026 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -76,7 +76,7 @@ module ActiveSupport #:nodoc:
#
# 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
def split(*args)
- @wrapped_string.split(*args).map { |i| i.mb_chars }
+ @wrapped_string.split(*args).map { |i| self.class.new(i) }
end
# Works like like <tt>String#slice!</tt>, but returns an instance of Chars, or nil if the string was not
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
new file mode 100644
index 0000000000..fc97782697
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -0,0 +1,531 @@
+require 'active_support/core_ext/big_decimal/conversions'
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
+require 'active_support/i18n'
+
+module ActiveSupport
+ module NumberHelper
+ extend self
+
+ DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
+ :precision => 2, :significant => false, :strip_insignificant_zeros => false }
+
+ # Formats a +number+ into a US phone number (e.g., (555)
+ # 123-9876). You can customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:area_code</tt> - Adds parentheses around the area code.
+ # * <tt>:delimiter</tt> - Specifies the delimiter to use
+ # (defaults to "-").
+ # * <tt>:extension</tt> - Specifies an extension to add to the
+ # end of the generated number.
+ # * <tt>:country_code</tt> - Sets the country code for the phone
+ # number.
+ # ==== Examples
+ #
+ # number_to_phone(5551234) # => 555-1234
+ # number_to_phone("5551234") # => 555-1234
+ # number_to_phone(1235551234) # => 123-555-1234
+ # number_to_phone(1235551234, :area_code => true) # => (123) 555-1234
+ # number_to_phone(1235551234, :delimiter => " ") # => 123 555 1234
+ # number_to_phone(1235551234, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
+ # number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234
+ # number_to_phone("123a456") # => 123a456
+ #
+ # number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
+ # # => +1.123.555.1234 x 1343
+ def number_to_phone(number, options = {})
+ return unless number
+ options = options.symbolize_keys
+
+ number = number.to_s.strip
+ area_code = options[:area_code]
+ delimiter = options[:delimiter] || "-"
+ extension = options[:extension]
+ country_code = options[:country_code]
+
+ if area_code
+ number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
+ else
+ number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
+ number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
+ end
+
+ str = ''
+ str << "+#{country_code}#{delimiter}" unless country_code.blank?
+ str << number
+ str << " x #{extension}" unless extension.blank?
+ str
+ end
+
+ # Formats a +number+ into a currency string (e.g., $13.65). You
+ # can customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the level of precision (defaults
+ # to 2).
+ # * <tt>:unit</tt> - Sets the denomination of the currency
+ # (defaults to "$").
+ # * <tt>:separator</tt> - Sets the separator between the units
+ # (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to ",").
+ # * <tt>:format</tt> - Sets the format for non-negative numbers
+ # (defaults to "%u%n"). Fields are <tt>%u</tt> for the
+ # currency, and <tt>%n</tt> for the number.
+ # * <tt>:negative_format</tt> - Sets the format for negative
+ # numbers (defaults to prepending an hyphen to the formatted
+ # number given by <tt>:format</tt>). Accepts the same fields
+ # than <tt>:format</tt>, except <tt>%n</tt> is here the
+ # absolute value of the number.
+ #
+ # ==== Examples
+ #
+ # number_to_currency(1234567890.50) # => $1,234,567,890.50
+ # number_to_currency(1234567890.506) # => $1,234,567,890.51
+ # number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506
+ # number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,51 €
+ # number_to_currency("123a456") # => $123a456
+ #
+ # number_to_currency(-1234567890.50, :negative_format => "(%u%n)")
+ # # => ($1,234,567,890.50)
+ # number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "")
+ # # => &pound;1234567890,50
+ # number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
+ # # => 1234567890,50 &pound;
+ def number_to_currency(number, options = {})
+ return unless number
+ options = options.symbolize_keys
+
+ currency = translations_for('currency', options[:locale])
+ currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
+
+ defaults = DEFAULT_CURRENCY_VALUES.merge(defaults_translations(options[:locale])).merge!(currency)
+ defaults[:negative_format] = "-" + options[:format] if options[:format]
+ options = defaults.merge!(options)
+
+ unit = options.delete(:unit)
+ format = options.delete(:format)
+
+ if number.to_f.phase != 0
+ format = options.delete(:negative_format)
+ number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
+ end
+
+ formatted_number = format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit)
+ formatted_number
+ end
+
+ # Formats a +number+ as a percentage string (e.g., 65%). You can
+ # customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +false+).
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +false+).
+ # * <tt>:format</tt> - Specifies the format of the percentage
+ # string The number field is <tt>%n</tt> (defaults to "%n%").
+ #
+ # ==== Examples
+ #
+ # number_to_percentage(100) # => 100.000%
+ # number_to_percentage("98") # => 98.000%
+ # number_to_percentage(100, :precision => 0) # => 100%
+ # number_to_percentage(1000, :delimiter => '.', :separator => ',') # => 1.000,000%
+ # number_to_percentage(302.24398923423, :precision => 5) # => 302.24399%
+ # number_to_percentage(1000, :locale => :fr) # => 1 000,000%
+ # number_to_percentage("98a") # => 98a%
+ # number_to_percentage(100, :format => "%n %") # => 100 %
+ #
+ def number_to_percentage(number, options = {})
+ return unless number
+ options = options.symbolize_keys
+
+ defaults = format_translations('percentage', options[:locale])
+ options = defaults.merge!(options)
+
+ format = options[:format] || "%n%"
+
+ formatted_number = format.gsub('%n', self.number_to_rounded(number, options))
+ formatted_number
+ end
+
+ # Formats a +number+ with grouped thousands using +delimiter+
+ # (e.g., 12,324). You can customize the format in the +options+
+ # hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to ",").
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ #
+ # ==== Examples
+ #
+ # number_to_delimited(12345678) # => 12,345,678
+ # number_to_delimited("123456") # => 123,456
+ # number_to_delimited(12345678.05) # => 12,345,678.05
+ # number_to_delimited(12345678, :delimiter => ".") # => 12.345.678
+ # number_to_delimited(12345678, :delimiter => ",") # => 12,345,678
+ # number_to_delimited(12345678.05, :separator => " ") # => 12,345,678 05
+ # number_to_delimited(12345678.05, :locale => :fr) # => 12 345 678,05
+ # number_to_delimited("112a") # => 112a
+ # number_to_delimited(98765432.98, :delimiter => " ", :separator => ",")
+ # # => 98 765 432,98
+ def number_to_delimited(number, options = {})
+ options = options.symbolize_keys
+
+ return number unless valid_float?(number)
+
+ options = defaults_translations(options[:locale]).merge(options)
+
+ parts = number.to_s.to_str.split('.')
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
+ parts.join(options[:separator])
+ end
+
+ # Formats a +number+ with the specified level of
+ # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
+ # +:significant+ is +false+, and 5 if +:significant+ is +true+).
+ # You can customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +false+).
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +false+).
+ #
+ # ==== Examples
+ #
+ # number_to_rounded(111.2345) # => 111.235
+ # number_to_rounded(111.2345, :precision => 2) # => 111.23
+ # number_to_rounded(13, :precision => 5) # => 13.00000
+ # number_to_rounded(389.32314, :precision => 0) # => 389
+ # number_to_rounded(111.2345, :significant => true) # => 111
+ # number_to_rounded(111.2345, :precision => 1, :significant => true) # => 100
+ # number_to_rounded(13, :precision => 5, :significant => true) # => 13.000
+ # number_to_rounded(111.234, :locale => :fr) # => 111,234
+ #
+ # number_to_rounded(13, :precision => 5, :significant => true, :strip_insignificant_zeros => true)
+ # # => 13
+ #
+ # number_to_rounded(389.32314, :precision => 4, :significant => true) # => 389.3
+ # number_to_rounded(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
+ # # => 1.111,23
+ def number_to_rounded(number, options = {})
+ options = options.symbolize_keys
+
+ return number unless valid_float?(number)
+ number = Float(number)
+
+ defaults = format_translations('precision', options[:locale])
+ options = defaults.merge!(options)
+
+ precision = options.delete :precision
+ significant = options.delete :significant
+ strip_insignificant_zeros = options.delete :strip_insignificant_zeros
+
+ if significant and precision > 0
+ if number == 0
+ digits, rounded_number = 1, 0
+ else
+ digits = (Math.log10(number.abs) + 1).floor
+ rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
+ digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
+ end
+ precision -= digits
+ precision = precision > 0 ? precision : 0 #don't let it be negative
+ else
+ rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
+ rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros
+ end
+ formatted_number = self.number_to_delimited("%01.#{precision}f" % rounded_number, options)
+ if strip_insignificant_zeros
+ escaped_separator = Regexp.escape(options[:separator])
+ formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
+ else
+ formatted_number
+ end
+ end
+
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
+
+ # Formats the bytes in +number+ into a more understandable
+ # representation (e.g., giving it 1500 yields 1.5 KB). This
+ # method is useful for reporting file sizes to users. You can
+ # customize the format in the +options+ hash.
+ #
+ # See <tt>number_to_human</tt> if you want to pretty-print a
+ # generic number.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +true+)
+ # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
+ # prefix (defaults to :binary)
+ #
+ # ==== Examples
+ #
+ # number_to_human_size(123) # => 123 Bytes
+ # number_to_human_size(1234) # => 1.21 KB
+ # number_to_human_size(12345) # => 12.1 KB
+ # number_to_human_size(1234567) # => 1.18 MB
+ # number_to_human_size(1234567890) # => 1.15 GB
+ # number_to_human_size(1234567890123) # => 1.12 TB
+ # number_to_human_size(1234567, :precision => 2) # => 1.2 MB
+ # number_to_human_size(483989, :precision => 2) # => 470 KB
+ # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB
+ #
+ # Non-significant zeros after the fractional separator are
+ # stripped out by default (set
+ # <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
+ # number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
+ # number_to_human_size(524288000, :precision => 5) # => "500 MB"
+ def number_to_human_size(number, options = {})
+ options = options.symbolize_keys
+
+ return number unless valid_float?(number)
+ number = Float(number)
+
+ defaults = format_translations('human', options[:locale])
+ options = defaults.merge!(options)
+
+ #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
+ options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
+
+ storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
+
+ base = options[:prefix] == :si ? 1000 : 1024
+
+ if number.to_i < base
+ unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
+ storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
+ else
+ max_exp = STORAGE_UNITS.size - 1
+ exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
+ exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
+ number /= base ** exponent
+
+ unit_key = STORAGE_UNITS[exponent]
+ unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
+
+ formatted_number = self.number_to_rounded(number, options)
+ storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
+ end
+ end
+
+ DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
+ -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze
+
+ # Pretty prints (formats and approximates) a number in a way it
+ # is more readable by humans (eg.: 1200000000 becomes "1.2
+ # Billion"). This is useful for numbers that can get very large
+ # (and too hard to read).
+ #
+ # See <tt>number_to_human_size</tt> if you want to print a file
+ # size.
+ #
+ # You can also define you own unit-quantifier names if you want
+ # to use other decimal units (eg.: 1500 becomes "1.5
+ # kilometers", 0.150 becomes "150 milliliters", etc). You may
+ # define a wide range of unit quantifiers, even fractional ones
+ # (centi, deci, mili, etc).
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +true+)
+ # * <tt>:units</tt> - A Hash of unit quantifier names. Or a
+ # string containing an i18n scope where to find this hash. It
+ # might have the following keys:
+ # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
+ # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
+ # *<tt>:billion</tt>, <tt>:trillion</tt>,
+ # *<tt>:quadrillion</tt>
+ # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
+ # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
+ # *<tt>:pico</tt>, <tt>:femto</tt>
+ # * <tt>:format</tt> - Sets the format of the output string
+ # (defaults to "%n %u"). The field types are:
+ # * %u - The quantifier (ex.: 'thousand')
+ # * %n - The number
+ #
+ # ==== Examples
+ #
+ # number_to_human(123) # => "123"
+ # number_to_human(1234) # => "1.23 Thousand"
+ # number_to_human(12345) # => "12.3 Thousand"
+ # number_to_human(1234567) # => "1.23 Million"
+ # number_to_human(1234567890) # => "1.23 Billion"
+ # number_to_human(1234567890123) # => "1.23 Trillion"
+ # number_to_human(1234567890123456) # => "1.23 Quadrillion"
+ # number_to_human(1234567890123456789) # => "1230 Quadrillion"
+ # number_to_human(489939, :precision => 2) # => "490 Thousand"
+ # number_to_human(489939, :precision => 4) # => "489.9 Thousand"
+ # number_to_human(1234567, :precision => 4,
+ # :significant => false) # => "1.2346 Million"
+ # number_to_human(1234567, :precision => 1,
+ # :separator => ',',
+ # :significant => false) # => "1,2 Million"
+ #
+ # Non-significant zeros after the decimal separator are stripped
+ # out by default (set <tt>:strip_insignificant_zeros</tt> to
+ # +false+ to change that):
+ # number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion"
+ # number_to_human(500000000, :precision => 5) # => "500 Million"
+ #
+ # ==== Custom Unit Quantifiers
+ #
+ # You can also use your own custom unit quantifiers:
+ # number_to_human(500000, :units => {:unit => "ml", :thousand => "lt"}) # => "500 lt"
+ #
+ # If in your I18n locale you have:
+ # distance:
+ # centi:
+ # one: "centimeter"
+ # other: "centimeters"
+ # unit:
+ # one: "meter"
+ # other: "meters"
+ # thousand:
+ # one: "kilometer"
+ # other: "kilometers"
+ # billion: "gazillion-distance"
+ #
+ # Then you could do:
+ #
+ # number_to_human(543934, :units => :distance) # => "544 kilometers"
+ # number_to_human(54393498, :units => :distance) # => "54400 kilometers"
+ # number_to_human(54393498000, :units => :distance) # => "54.4 gazillion-distance"
+ # number_to_human(343, :units => :distance, :precision => 1) # => "300 meters"
+ # number_to_human(1, :units => :distance) # => "1 meter"
+ # number_to_human(0.34, :units => :distance) # => "34 centimeters"
+ def number_to_human(number, options = {})
+ options = options.symbolize_keys
+
+ return number unless valid_float?(number)
+ number = Float(number)
+
+ defaults = format_translations('human', options[:locale])
+ options = defaults.merge!(options)
+
+ #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
+ options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
+
+ inverted_du = DECIMAL_UNITS.invert
+
+ units = options.delete :units
+ unit_exponents = case units
+ when Hash
+ units
+ when String, Symbol
+ I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
+ when nil
+ I18n.translate(:"number.human.decimal_units.units", :locale => options[:locale], :raise => true)
+ else
+ raise ArgumentError, ":units must be a Hash or String translation scope."
+ end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}
+
+ number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
+ display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
+ number /= 10 ** display_exponent
+
+ unit = case units
+ when Hash
+ units[DECIMAL_UNITS[display_exponent]]
+ when String, Symbol
+ I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ else
+ I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ end
+
+ decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u")
+ formatted_number = self.number_to_rounded(number, options)
+ decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip
+ end
+
+ def self.private_module_and_instance_method(method_name)
+ private method_name
+ private_class_method method_name
+ end
+ private_class_method :private_module_and_instance_method
+
+ def format_translations(namespace, locale)
+ defaults_translations(locale).merge(translations_for(namespace, locale))
+ end
+ private_module_and_instance_method :format_translations
+
+ def defaults_translations(locale)
+ I18n.translate(:'number.format', :locale => locale, :default => {})
+ end
+ private_module_and_instance_method :defaults_translations
+
+ def translations_for(namespace, locale)
+ I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {})
+ end
+ private_module_and_instance_method :translations_for
+
+ def valid_float?(number)
+ Float(number)
+ rescue ArgumentError, TypeError
+ false
+ end
+ private_module_and_instance_method :valid_float?
+
+ end
+end
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index 2bea0f991a..517926c74d 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -3,7 +3,8 @@ require 'rails/version'
require 'active_support/concern'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/string/inflections'
-require 'action_view/helpers/number_helper'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/number_helper'
module ActiveSupport
module Testing
@@ -195,8 +196,7 @@ module ActiveSupport
end
class Base
- include ActionView::Helpers::NumberHelper
- include ActionView::Helpers::OutputSafetyHelper
+ include ActiveSupport::NumberHelper
attr_reader :total
@@ -240,7 +240,7 @@ module ActiveSupport
class Amount < Base
def format(measurement)
- number_with_delimiter(measurement.floor)
+ number_to_delimited(measurement.floor)
end
end
diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb
index 772c7b4209..527fa555b7 100644
--- a/activesupport/lib/active_support/testing/setup_and_teardown.rb
+++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb
@@ -4,6 +4,14 @@ require 'active_support/callbacks'
module ActiveSupport
module Testing
module SetupAndTeardown
+
+ PASSTHROUGH_EXCEPTIONS = [
+ NoMemoryError,
+ SignalException,
+ Interrupt,
+ SystemExit
+ ]
+
extend ActiveSupport::Concern
included do
@@ -28,11 +36,15 @@ module ActiveSupport
run_callbacks :setup do
result = super
end
+ rescue *PASSTHROUGH_EXCEPTIONS
+ raise
rescue Exception => e
result = runner.puke(self.class, method_name, e)
ensure
begin
run_callbacks :teardown
+ rescue *PASSTHROUGH_EXCEPTIONS
+ raise
rescue Exception => e
result = runner.puke(self.class, method_name, e)
end
diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index e24a089650..a5987044b9 100644
--- a/activesupport/test/core_ext/bigdecimal_test.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -14,4 +14,9 @@ class BigDecimalTest < ActiveSupport::TestCase
bd = BigDecimal.new '10'
assert_equal bd, bd.to_d
end
+
+ def test_to_s
+ bd = BigDecimal.new '0.01'
+ assert_equal '0.01', bd.to_s
+ end
end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 0bf48dd378..0a1abac767 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -84,6 +84,11 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal 10, (1..4.5).sum
assert_equal 6, (1...4).sum
assert_equal 'abc', ('a'..'c').sum
+ assert_equal 50_000_005_000_000, (0..10_000_000).sum
+ assert_equal 0, (10..0).sum
+ assert_equal 5, (10..0).sum(5)
+ assert_equal 10, (10..10).sum
+ assert_equal 42, (10...10).sum(42)
end
def test_index_by
diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb
index 50c9c57aa6..128e956a8c 100644
--- a/activesupport/test/core_ext/file_test.rb
+++ b/activesupport/test/core_ext/file_test.rb
@@ -30,7 +30,7 @@ class AtomicWriteTest < ActiveSupport::TestCase
assert File.exist?(file_name)
end
assert File.exist?(file_name)
- assert_equal 0100755, file_mode
+ assert_equal 0100755 & ~File.umask, file_mode
assert_equal contents, File.read(file_name)
File.atomic_write(file_name, Dir.pwd) do |file|
@@ -38,7 +38,7 @@ class AtomicWriteTest < ActiveSupport::TestCase
assert File.exist?(file_name)
end
assert File.exist?(file_name)
- assert_equal 0100755, file_mode
+ assert_equal 0100755 & ~File.umask, file_mode
assert_equal contents, File.read(file_name)
ensure
File.unlink(file_name) rescue nil
@@ -51,7 +51,7 @@ class AtomicWriteTest < ActiveSupport::TestCase
assert !File.exist?(file_name)
end
assert File.exist?(file_name)
- assert_equal 0100666 ^ File.umask, file_mode
+ assert_equal 0100666 & ~File.umask, file_mode
assert_equal contents, File.read(file_name)
ensure
File.unlink(file_name) rescue nil
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index f13fff43d4..5d422ce5ad 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -4,6 +4,7 @@ require 'bigdecimal'
require 'active_support/core_ext/string/access'
require 'active_support/ordered_hash'
require 'active_support/core_ext/object/conversions'
+require 'active_support/core_ext/object/deep_dup'
require 'active_support/inflections'
class HashExtTest < ActiveSupport::TestCase
@@ -61,23 +62,49 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @upcase_strings, @mixed.transform_keys{ |key| key.to_s.upcase }
end
+ def test_transform_keys_not_mutates
+ transformed_hash = @mixed.dup
+ transformed_hash.transform_keys{ |key| key.to_s.upcase }
+ assert_equal @mixed, transformed_hash
+ end
+
def test_deep_transform_keys
assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys{ |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys{ |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys{ |key| key.to_s.upcase }
end
+ def test_deep_transform_keys_not_mutates
+ transformed_hash = @nested_mixed.deep_dup
+ transformed_hash.deep_transform_keys{ |key| key.to_s.upcase }
+ assert_equal @nested_mixed, transformed_hash
+ end
+
def test_transform_keys!
assert_equal @upcase_strings, @symbols.dup.transform_keys!{ |key| key.to_s.upcase }
assert_equal @upcase_strings, @strings.dup.transform_keys!{ |key| key.to_s.upcase }
assert_equal @upcase_strings, @mixed.dup.transform_keys!{ |key| key.to_s.upcase }
end
+ def test_transform_keys_with_bang_mutates
+ transformed_hash = @mixed.dup
+ transformed_hash.transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @upcase_strings, transformed_hash
+ assert_equal @mixed, { :a => 1, "b" => 2 }
+ end
+
def test_deep_transform_keys!
- assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys!{ |key| key.to_s.upcase }
- assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys!{ |key| key.to_s.upcase }
- assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys!{ |key| key.to_s.upcase }
- end
+ assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ end
+
+ def test_deep_transform_keys_with_bang_mutates
+ transformed_hash = @nested_mixed.deep_dup
+ transformed_hash.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, transformed_hash
+ assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } }
+ end
def test_symbolize_keys
assert_equal @symbols, @symbols.symbolize_keys
@@ -85,22 +112,48 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @symbols, @mixed.symbolize_keys
end
+ def test_symbolize_keys_not_mutates
+ transformed_hash = @mixed.dup
+ transformed_hash.symbolize_keys
+ assert_equal @mixed, transformed_hash
+ end
+
def test_deep_symbolize_keys
assert_equal @nested_symbols, @nested_symbols.deep_symbolize_keys
assert_equal @nested_symbols, @nested_strings.deep_symbolize_keys
assert_equal @nested_symbols, @nested_mixed.deep_symbolize_keys
end
+ def test_deep_symbolize_keys_not_mutates
+ transformed_hash = @nested_mixed.deep_dup
+ transformed_hash.deep_symbolize_keys
+ assert_equal @nested_mixed, transformed_hash
+ end
+
def test_symbolize_keys!
assert_equal @symbols, @symbols.dup.symbolize_keys!
assert_equal @symbols, @strings.dup.symbolize_keys!
assert_equal @symbols, @mixed.dup.symbolize_keys!
end
+ def test_symbolize_keys_with_bang_mutates
+ transformed_hash = @mixed.dup
+ transformed_hash.deep_symbolize_keys!
+ assert_equal @symbols, transformed_hash
+ assert_equal @mixed, { :a => 1, "b" => 2 }
+ end
+
def test_deep_symbolize_keys!
- assert_equal @nested_symbols, @nested_symbols.dup.deep_symbolize_keys!
- assert_equal @nested_symbols, @nested_strings.dup.deep_symbolize_keys!
- assert_equal @nested_symbols, @nested_mixed.dup.deep_symbolize_keys!
+ assert_equal @nested_symbols, @nested_symbols.deep_dup.deep_symbolize_keys!
+ assert_equal @nested_symbols, @nested_strings.deep_dup.deep_symbolize_keys!
+ assert_equal @nested_symbols, @nested_mixed.deep_dup.deep_symbolize_keys!
+ end
+
+ def test_deep_symbolize_keys_with_bang_mutates
+ transformed_hash = @nested_mixed.deep_dup
+ transformed_hash.deep_symbolize_keys!
+ assert_equal @nested_symbols, transformed_hash
+ assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } }
end
def test_symbolize_keys_preserves_keys_that_cant_be_symbolized
@@ -110,7 +163,7 @@ class HashExtTest < ActiveSupport::TestCase
def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized
assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_symbolize_keys
- assert_equal @nested_illegal_symbols, @nested_illegal_symbols.dup.deep_symbolize_keys!
+ assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_dup.deep_symbolize_keys!
end
def test_symbolize_keys_preserves_fixnum_keys
@@ -120,7 +173,7 @@ class HashExtTest < ActiveSupport::TestCase
def test_deep_symbolize_keys_preserves_fixnum_keys
assert_equal @nested_fixnums, @nested_fixnums.deep_symbolize_keys
- assert_equal @nested_fixnums, @nested_fixnums.dup.deep_symbolize_keys!
+ assert_equal @nested_fixnums, @nested_fixnums.deep_dup.deep_symbolize_keys!
end
def test_stringify_keys
@@ -129,22 +182,48 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @strings, @mixed.stringify_keys
end
+ def test_stringify_keys_not_mutates
+ transformed_hash = @mixed.dup
+ transformed_hash.stringify_keys
+ assert_equal @mixed, transformed_hash
+ end
+
def test_deep_stringify_keys
assert_equal @nested_strings, @nested_symbols.deep_stringify_keys
assert_equal @nested_strings, @nested_strings.deep_stringify_keys
assert_equal @nested_strings, @nested_mixed.deep_stringify_keys
end
+ def test_deep_stringify_keys_not_mutates
+ transformed_hash = @nested_mixed.deep_dup
+ transformed_hash.deep_stringify_keys
+ assert_equal @nested_mixed, transformed_hash
+ end
+
def test_stringify_keys!
assert_equal @strings, @symbols.dup.stringify_keys!
assert_equal @strings, @strings.dup.stringify_keys!
assert_equal @strings, @mixed.dup.stringify_keys!
end
+ def test_stringify_keys_with_bang_mutates
+ transformed_hash = @mixed.dup
+ transformed_hash.stringify_keys!
+ assert_equal @strings, transformed_hash
+ assert_equal @mixed, { :a => 1, "b" => 2 }
+ end
+
def test_deep_stringify_keys!
- assert_equal @nested_strings, @nested_symbols.dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_strings.dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_mixed.dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_symbols.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_strings.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_mixed.deep_dup.deep_stringify_keys!
+ end
+
+ def test_deep_stringify_keys_with_bang_mutates
+ transformed_hash = @nested_mixed.deep_dup
+ transformed_hash.deep_stringify_keys!
+ assert_equal @nested_strings, transformed_hash
+ assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } }
end
def test_symbolize_keys_for_hash_with_indifferent_access
@@ -169,9 +248,9 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access
- assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.dup.deep_symbolize_keys! }
- assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.dup.deep_symbolize_keys! }
- assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! }
end
def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
@@ -181,7 +260,7 @@ class HashExtTest < ActiveSupport::TestCase
def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
end
def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
@@ -191,7 +270,7 @@ class HashExtTest < ActiveSupport::TestCase
def test_deep_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
assert_equal @nested_fixnums, @nested_fixnums.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_fixnums.with_indifferent_access.dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_fixnums.with_indifferent_access.deep_dup.deep_symbolize_keys! }
end
def test_stringify_keys_for_hash_with_indifferent_access
@@ -217,9 +296,9 @@ class HashExtTest < ActiveSupport::TestCase
def test_deep_stringify_keys_bang_for_hash_with_indifferent_access
assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_strings.with_indifferent_access.dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_mixed.with_indifferent_access.dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys!
end
def test_nested_under_indifferent_access
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 6e1b3ca010..bd41311739 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -234,7 +234,7 @@ class ModuleTest < ActiveSupport::TestCase
def test_local_constant_names
ActiveSupport::Deprecation.silence do
- assert_equal %w(Constant1 Constant3), Ab.local_constant_names
+ assert_equal %w(Constant1 Constant3), Ab.local_constant_names.sort.map(&:to_s)
end
end
end
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 1cb1e25d4c..435f4aa5a1 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -186,3 +186,264 @@ class NumericExtSizeTest < ActiveSupport::TestCase
assert_equal 3458764513820540928, 3.exabyte
end
end
+
+class NumericExtFormattingTest < ActiveSupport::TestCase
+ def kilobytes(number)
+ number * 1024
+ end
+
+ def megabytes(number)
+ kilobytes(number) * 1024
+ end
+
+ def gigabytes(number)
+ megabytes(number) * 1024
+ end
+
+ def terabytes(number)
+ gigabytes(number) * 1024
+ end
+
+ def test_to_s__phone
+ assert_equal("555-1234", 5551234.to_s(:phone))
+ assert_equal("800-555-1212", 8005551212.to_s(:phone))
+ assert_equal("(800) 555-1212", 8005551212.to_s(:phone, :area_code => true))
+ assert_equal("800 555 1212", 8005551212.to_s(:phone, :delimiter => " "))
+ assert_equal("(800) 555-1212 x 123", 8005551212.to_s(:phone, :area_code => true, :extension => 123))
+ assert_equal("800-555-1212", 8005551212.to_s(:phone, :extension => " "))
+ assert_equal("555.1212", 5551212.to_s(:phone, :delimiter => '.'))
+ assert_equal("+1-800-555-1212", 8005551212.to_s(:phone, :country_code => 1))
+ assert_equal("+18005551212", 8005551212.to_s(:phone, :country_code => 1, :delimiter => ''))
+ assert_equal("22-555-1212", 225551212.to_s(:phone))
+ assert_equal("+45-22-555-1212", 225551212.to_s(:phone, :country_code => 45))
+ end
+
+ def test_to_s__currency
+ assert_equal("$1,234,567,890.50", 1234567890.50.to_s(:currency))
+ assert_equal("$1,234,567,890.51", 1234567890.506.to_s(:currency))
+ assert_equal("-$1,234,567,890.50", -1234567890.50.to_s(:currency))
+ assert_equal("-$ 1,234,567,890.50", -1234567890.50.to_s(:currency, :format => "%u %n"))
+ assert_equal("($1,234,567,890.50)", -1234567890.50.to_s(:currency, :negative_format => "(%u%n)"))
+ assert_equal("$1,234,567,892", 1234567891.50.to_s(:currency, :precision => 0))
+ assert_equal("$1,234,567,890.5", 1234567890.50.to_s(:currency, :precision => 1))
+ assert_equal("&pound;1234567890,50", 1234567890.50.to_s(:currency, :unit => "&pound;", :separator => ",", :delimiter => ""))
+ end
+
+
+ def test_to_s__rounded
+ assert_equal("-111.235", -111.2346.to_s(:rounded))
+ assert_equal("111.235", 111.2346.to_s(:rounded))
+ assert_equal("31.83", 31.825.to_s(:rounded, :precision => 2))
+ assert_equal("111.23", 111.2346.to_s(:rounded, :precision => 2))
+ assert_equal("111.00", 111.to_s(:rounded, :precision => 2))
+ assert_equal("3268", (32.6751 * 100.00).to_s(:rounded, :precision => 0))
+ assert_equal("112", 111.50.to_s(:rounded, :precision => 0))
+ assert_equal("1234567892", 1234567891.50.to_s(:rounded, :precision => 0))
+ assert_equal("0", 0.to_s(:rounded, :precision => 0))
+ assert_equal("0.00100", 0.001.to_s(:rounded, :precision => 5))
+ assert_equal("0.001", 0.00111.to_s(:rounded, :precision => 3))
+ assert_equal("10.00", 9.995.to_s(:rounded, :precision => 2))
+ assert_equal("11.00", 10.995.to_s(:rounded, :precision => 2))
+ assert_equal("0.00", -0.001.to_s(:rounded, :precision => 2))
+ end
+
+ def test_to_s__percentage
+ assert_equal("100.000%", 100.to_s(:percentage))
+ assert_equal("100%", 100.to_s(:percentage, :precision => 0))
+ assert_equal("302.06%", 302.0574.to_s(:percentage, :precision => 2))
+ assert_equal("123.4%", 123.400.to_s(:percentage, :precision => 3, :strip_insignificant_zeros => true))
+ assert_equal("1.000,000%", 1000.to_s(:percentage, :delimiter => '.', :separator => ','))
+ assert_equal("1000.000 %", 1000.to_s(:percentage, :format => "%n %"))
+ end
+
+ def test_to_s__delimited
+ assert_equal("12,345,678", 12345678.to_s(:delimited))
+ assert_equal("0", 0.to_s(:delimited))
+ assert_equal("123", 123.to_s(:delimited))
+ assert_equal("123,456", 123456.to_s(:delimited))
+ assert_equal("123,456.78", 123456.78.to_s(:delimited))
+ assert_equal("123,456.789", 123456.789.to_s(:delimited))
+ assert_equal("123,456.78901", 123456.78901.to_s(:delimited))
+ assert_equal("123,456,789.78901", 123456789.78901.to_s(:delimited))
+ assert_equal("0.78901", 0.78901.to_s(:delimited))
+ end
+
+ def test_to_s__delimited__with_options_hash
+ assert_equal '12 345 678', 12345678.to_s(:delimited, :delimiter => ' ')
+ assert_equal '12,345,678-05', 12345678.05.to_s(:delimited, :separator => '-')
+ assert_equal '12.345.678,05', 12345678.05.to_s(:delimited, :separator => ',', :delimiter => '.')
+ assert_equal '12.345.678,05', 12345678.05.to_s(:delimited, :delimiter => '.', :separator => ',')
+ end
+
+
+ def test_to_s__rounded_with_custom_delimiter_and_separator
+ assert_equal '31,83', 31.825.to_s(:rounded, :precision => 2, :separator => ',')
+ assert_equal '1.231,83', 1231.825.to_s(:rounded, :precision => 2, :separator => ',', :delimiter => '.')
+ end
+
+ def test_to_s__rounded__with_significant_digits
+ assert_equal "124000", 123987.to_s(:rounded, :precision => 3, :significant => true)
+ assert_equal "120000000", 123987876.to_s(:rounded, :precision => 2, :significant => true )
+ assert_equal "9775", 9775.to_s(:rounded, :precision => 4, :significant => true )
+ assert_equal "5.4", 5.3923.to_s(:rounded, :precision => 2, :significant => true )
+ assert_equal "5", 5.3923.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "1", 1.232.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "7", 7.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "1", 1.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "53", 52.7923.to_s(:rounded, :precision => 2, :significant => true )
+ assert_equal "9775.00", 9775.to_s(:rounded, :precision => 6, :significant => true )
+ assert_equal "5.392900", 5.3929.to_s(:rounded, :precision => 7, :significant => true )
+ assert_equal "0.0", 0.to_s(:rounded, :precision => 2, :significant => true )
+ assert_equal "0", 0.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "0.0001", 0.0001.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "0.000100", 0.0001.to_s(:rounded, :precision => 3, :significant => true )
+ assert_equal "0.0001", 0.0001111.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "10.0", 9.995.to_s(:rounded, :precision => 3, :significant => true)
+ assert_equal "9.99", 9.994.to_s(:rounded, :precision => 3, :significant => true)
+ assert_equal "11.0", 10.995.to_s(:rounded, :precision => 3, :significant => true)
+ end
+
+ def test_to_s__rounded__with_strip_insignificant_zeros
+ assert_equal "9775.43", 9775.43.to_s(:rounded, :precision => 4, :strip_insignificant_zeros => true )
+ assert_equal "9775.2", 9775.2.to_s(:rounded, :precision => 6, :significant => true, :strip_insignificant_zeros => true )
+ assert_equal "0", 0.to_s(:rounded, :precision => 6, :significant => true, :strip_insignificant_zeros => true )
+ end
+
+ def test_to_s__rounded__with_significant_true_and_zero_precision
+ # Zero precision with significant is a mistake (would always return zero),
+ # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size)
+ assert_equal "124", 123.987.to_s(:rounded, :precision => 0, :significant => true)
+ assert_equal "12", 12.to_s(:rounded, :precision => 0, :significant => true )
+ end
+
+ def test_to_s__human_size
+ assert_equal '0 Bytes', 0.to_s(:human_size)
+ assert_equal '1 Byte', 1.to_s(:human_size)
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size)
+ assert_equal '123 Bytes', 123.0.to_s(:human_size)
+ assert_equal '123 Bytes', 123.to_s(:human_size)
+ assert_equal '1.21 KB', 1234.to_s(:human_size)
+ assert_equal '12.1 KB', 12345.to_s(:human_size)
+ assert_equal '1.18 MB', 1234567.to_s(:human_size)
+ assert_equal '1.15 GB', 1234567890.to_s(:human_size)
+ assert_equal '1.12 TB', 1234567890123.to_s(:human_size)
+ assert_equal '1030 TB', terabytes(1026).to_s(:human_size)
+ assert_equal '444 KB', kilobytes(444).to_s(:human_size)
+ assert_equal '1020 MB', megabytes(1023).to_s(:human_size)
+ assert_equal '3 TB', terabytes(3).to_s(:human_size)
+ assert_equal '1.2 MB', 1234567.to_s(:human_size, :precision => 2)
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :precision => 4)
+ assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2)
+ assert_equal '1.01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4)
+ assert_equal '10 KB', kilobytes(10.000).to_s(:human_size, :precision => 4)
+ assert_equal '1 Byte', 1.1.to_s(:human_size)
+ assert_equal '10 Bytes', 10.to_s(:human_size)
+ end
+
+ def test_to_s__human_size_with_si_prefix
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :prefix => :si)
+ assert_equal '123 Bytes', 123.0.to_s(:human_size, :prefix => :si)
+ assert_equal '123 Bytes', 123.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 KB', 1234.to_s(:human_size, :prefix => :si)
+ assert_equal '12.3 KB', 12345.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si)
+ end
+
+ def test_to_s__human_size_with_options_hash
+ assert_equal '1.2 MB', 1234567.to_s(:human_size, :precision => 2)
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :precision => 4)
+ assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2)
+ assert_equal '1.01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4)
+ assert_equal '10 KB', kilobytes(10.000).to_s(:human_size, :precision => 4)
+ assert_equal '1 TB', 1234567890123.to_s(:human_size, :precision => 1)
+ assert_equal '500 MB', 524288000.to_s(:human_size, :precision=>3)
+ assert_equal '10 MB', 9961472.to_s(:human_size, :precision=>0)
+ assert_equal '40 KB', 41010.to_s(:human_size, :precision => 1)
+ assert_equal '40 KB', 41100.to_s(:human_size, :precision => 2)
+ assert_equal '1.0 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2, :strip_insignificant_zeros => false)
+ assert_equal '1.012 KB', kilobytes(1.0123).to_s(:human_size, :precision => 3, :significant => false)
+ assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 0, :significant => true) #ignores significant it precision is 0
+ end
+
+ def test_to_s__human_size_with_custom_delimiter_and_separator
+ assert_equal '1,01 KB', kilobytes(1.0123).to_s(:human_size, :precision => 3, :separator => ',')
+ assert_equal '1,01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4, :separator => ',')
+ assert_equal '1.000,1 TB', terabytes(1000.1).to_s(:human_size, :precision => 5, :delimiter => '.', :separator => ',')
+ end
+
+ def test_number_to_human
+ assert_equal '-123', -123.to_s(:human)
+ assert_equal '-0.5', -0.5.to_s(:human)
+ assert_equal '0', 0.to_s(:human)
+ assert_equal '0.5', 0.5.to_s(:human)
+ assert_equal '123', 123.to_s(:human)
+ assert_equal '1.23 Thousand', 1234.to_s(:human)
+ assert_equal '12.3 Thousand', 12345.to_s(:human)
+ assert_equal '1.23 Million', 1234567.to_s(:human)
+ assert_equal '1.23 Billion', 1234567890.to_s(:human)
+ assert_equal '1.23 Trillion', 1234567890123.to_s(:human)
+ assert_equal '1.23 Quadrillion', 1234567890123456.to_s(:human)
+ assert_equal '1230 Quadrillion', 1234567890123456789.to_s(:human)
+ assert_equal '490 Thousand', 489939.to_s(:human, :precision => 2)
+ assert_equal '489.9 Thousand', 489939.to_s(:human, :precision => 4)
+ assert_equal '489 Thousand', 489000.to_s(:human, :precision => 4)
+ assert_equal '489.0 Thousand', 489000.to_s(:human, :precision => 4, :strip_insignificant_zeros => false)
+ assert_equal '1.2346 Million', 1234567.to_s(:human, :precision => 4, :significant => false)
+ assert_equal '1,2 Million', 1234567.to_s(:human, :precision => 1, :significant => false, :separator => ',')
+ assert_equal '1 Million', 1234567.to_s(:human, :precision => 0, :significant => true, :separator => ',') #significant forced to false
+ end
+
+ def test_number_to_human_with_custom_units
+ #Only integers
+ volume = {:unit => "ml", :thousand => "lt", :million => "m3"}
+ assert_equal '123 lt', 123456.to_s(:human, :units => volume)
+ assert_equal '12 ml', 12.to_s(:human, :units => volume)
+ assert_equal '1.23 m3', 1234567.to_s(:human, :units => volume)
+
+ #Including fractionals
+ distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
+ assert_equal '1.23 mm', 0.00123.to_s(:human, :units => distance)
+ assert_equal '1.23 cm', 0.0123.to_s(:human, :units => distance)
+ assert_equal '1.23 dm', 0.123.to_s(:human, :units => distance)
+ assert_equal '1.23 m', 1.23.to_s(:human, :units => distance)
+ assert_equal '1.23 dam', 12.3.to_s(:human, :units => distance)
+ assert_equal '1.23 hm', 123.to_s(:human, :units => distance)
+ assert_equal '1.23 km', 1230.to_s(:human, :units => distance)
+ assert_equal '1.23 km', 1230.to_s(:human, :units => distance)
+ assert_equal '1.23 km', 1230.to_s(:human, :units => distance)
+ assert_equal '12.3 km', 12300.to_s(:human, :units => distance)
+
+ #The quantifiers don't need to be a continuous sequence
+ gangster = {:hundred => "hundred bucks", :million => "thousand quids"}
+ assert_equal '1 hundred bucks', 100.to_s(:human, :units => gangster)
+ assert_equal '25 hundred bucks', 2500.to_s(:human, :units => gangster)
+ assert_equal '25 thousand quids', 25000000.to_s(:human, :units => gangster)
+ assert_equal '12300 thousand quids', 12345000000.to_s(:human, :units => gangster)
+
+ #Spaces are stripped from the resulting string
+ assert_equal '4', 4.to_s(:human, :units => {:unit => "", :ten => 'tens '})
+ assert_equal '4.5 tens', 45.to_s(:human, :units => {:unit => "", :ten => ' tens '})
+ end
+
+ def test_number_to_human_with_custom_format
+ assert_equal '123 times Thousand', 123456.to_s(:human, :format => "%n times %u")
+ volume = {:unit => "ml", :thousand => "lt", :million => "m3"}
+ assert_equal '123.lt', 123456.to_s(:human, :units => volume, :format => "%n.%u")
+ end
+
+ def test_to_s__injected_on_proper_types
+ assert_equal Fixnum, 1230.class
+ assert_equal '1.23 Thousand', 1230.to_s(:human)
+
+ assert_equal Float, Float(1230).class
+ assert_equal '1.23 Thousand', Float(1230).to_s(:human)
+
+ assert_equal Bignum, (100**10).class
+ assert_equal '100000 Quadrillion', (100**10).to_s(:human)
+
+ assert_equal BigDecimal, BigDecimal("1000010").class
+ assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
+ end
+end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 8437ef1347..e5b774425e 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -290,6 +290,10 @@ class StringInflectionsTest < ActiveSupport::TestCase
"\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').truncate(10)
end
+ def test_truncate_should_not_be_html_safe
+ assert !"Hello World!".truncate(12).html_safe?
+ end
+
def test_constantize
run_constantize_tests_on do |string|
string.constantize
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 0566ebf291..a947635f4a 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -54,8 +54,6 @@ class TestJSONEncoding < ActiveSupport::TestCase
HashlikeTests = [[ Hashlike.new, %({\"a\":1}) ]]
CustomTests = [[ Custom.new, '"custom"' ]]
- VariableTests = [[ ActiveSupport::JSON::Variable.new('foo'), 'foo'],
- [ ActiveSupport::JSON::Variable.new('alert("foo")'), 'alert("foo")']]
RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
@@ -285,6 +283,12 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
end
+ def test_nil_true_and_false_represented_as_themselves
+ assert_equal nil, nil.as_json
+ assert_equal true, true.as_json
+ assert_equal false, false.as_json
+ end
+
protected
def object_keys(json_object)
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
new file mode 100644
index 0000000000..9b7d7f020c
--- /dev/null
+++ b/activesupport/test/number_helper_test.rb
@@ -0,0 +1,375 @@
+require 'abstract_unit'
+require 'active_support/number_helper'
+
+module ActiveSupport
+ module NumberHelper
+ class NumberHelperTest < ActiveSupport::TestCase
+
+ class TestClassWithInstanceNumberHelpers
+ include ActiveSupport::NumberHelper
+ end
+
+ class TestClassWithClassNumberHelpers
+ extend ActiveSupport::NumberHelper
+ end
+
+ def setup
+ @instance_with_helpers = TestClassWithInstanceNumberHelpers.new
+ end
+
+ def kilobytes(number)
+ number * 1024
+ end
+
+ def megabytes(number)
+ kilobytes(number) * 1024
+ end
+
+ def gigabytes(number)
+ megabytes(number) * 1024
+ end
+
+ def terabytes(number)
+ gigabytes(number) * 1024
+ end
+
+ def test_number_to_phone
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("555-1234", number_helper.number_to_phone(5551234))
+ assert_equal("800-555-1212", number_helper.number_to_phone(8005551212))
+ assert_equal("(800) 555-1212", number_helper.number_to_phone(8005551212, {:area_code => true}))
+ assert_equal("", number_helper.number_to_phone("", {:area_code => true}))
+ assert_equal("800 555 1212", number_helper.number_to_phone(8005551212, {:delimiter => " "}))
+ assert_equal("(800) 555-1212 x 123", number_helper.number_to_phone(8005551212, {:area_code => true, :extension => 123}))
+ assert_equal("800-555-1212", number_helper.number_to_phone(8005551212, :extension => " "))
+ assert_equal("555.1212", number_helper.number_to_phone(5551212, :delimiter => '.'))
+ assert_equal("800-555-1212", number_helper.number_to_phone("8005551212"))
+ assert_equal("+1-800-555-1212", number_helper.number_to_phone(8005551212, :country_code => 1))
+ assert_equal("+18005551212", number_helper.number_to_phone(8005551212, :country_code => 1, :delimiter => ''))
+ assert_equal("22-555-1212", number_helper.number_to_phone(225551212))
+ assert_equal("+45-22-555-1212", number_helper.number_to_phone(225551212, :country_code => 45))
+ end
+ end
+
+ def test_number_to_currency
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("$1,234,567,890.50", number_helper.number_to_currency(1234567890.50))
+ assert_equal("$1,234,567,890.51", number_helper.number_to_currency(1234567890.506))
+ assert_equal("-$1,234,567,890.50", number_helper.number_to_currency(-1234567890.50))
+ assert_equal("-$ 1,234,567,890.50", number_helper.number_to_currency(-1234567890.50, {:format => "%u %n"}))
+ assert_equal("($1,234,567,890.50)", number_helper.number_to_currency(-1234567890.50, {:negative_format => "(%u%n)"}))
+ assert_equal("$1,234,567,892", number_helper.number_to_currency(1234567891.50, {:precision => 0}))
+ assert_equal("$1,234,567,890.5", number_helper.number_to_currency(1234567890.50, {:precision => 1}))
+ assert_equal("&pound;1234567890,50", number_helper.number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}))
+ assert_equal("$1,234,567,890.50", number_helper.number_to_currency("1234567890.50"))
+ assert_equal("1,234,567,890.50 K&#269;", number_helper.number_to_currency("1234567890.50", {:unit => "K&#269;", :format => "%n %u"}))
+ assert_equal("1,234,567,890.50 - K&#269;", number_helper.number_to_currency("-1234567890.50", {:unit => "K&#269;", :format => "%n %u", :negative_format => "%n - %u"}))
+ assert_equal("0.00", number_helper.number_to_currency(+0.0, {:unit => "", :negative_format => "(%n)"}))
+ assert_equal("(0.00)", number_helper.number_to_currency(-0.0, {:unit => "", :negative_format => "(%n)"}))
+ end
+ end
+
+ def test_number_to_percentage
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("100.000%", number_helper.number_to_percentage(100))
+ assert_equal("100%", number_helper.number_to_percentage(100, {:precision => 0}))
+ assert_equal("302.06%", number_helper.number_to_percentage(302.0574, {:precision => 2}))
+ assert_equal("100.000%", number_helper.number_to_percentage("100"))
+ assert_equal("1000.000%", number_helper.number_to_percentage("1000"))
+ assert_equal("123.4%", number_helper.number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true))
+ assert_equal("1.000,000%", number_helper.number_to_percentage(1000, :delimiter => '.', :separator => ','))
+ assert_equal("1000.000 %", number_helper.number_to_percentage(1000, :format => "%n %"))
+ end
+ end
+
+ def test_to_delimited
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("12,345,678", number_helper.number_to_delimited(12345678))
+ assert_equal("0", number_helper.number_to_delimited(0))
+ assert_equal("123", number_helper.number_to_delimited(123))
+ assert_equal("123,456", number_helper.number_to_delimited(123456))
+ assert_equal("123,456.78", number_helper.number_to_delimited(123456.78))
+ assert_equal("123,456.789", number_helper.number_to_delimited(123456.789))
+ assert_equal("123,456.78901", number_helper.number_to_delimited(123456.78901))
+ assert_equal("123,456,789.78901", number_helper.number_to_delimited(123456789.78901))
+ assert_equal("0.78901", number_helper.number_to_delimited(0.78901))
+ assert_equal("123,456.78", number_helper.number_to_delimited("123456.78"))
+ end
+ end
+
+ def test_to_delimited_with_options_hash
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '12 345 678', number_helper.number_to_delimited(12345678, :delimiter => ' ')
+ assert_equal '12,345,678-05', number_helper.number_to_delimited(12345678.05, :separator => '-')
+ assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :separator => ',', :delimiter => '.')
+ assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :delimiter => '.', :separator => ',')
+ end
+ end
+
+ def test_to_rounded
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("-111.235", number_helper.number_to_rounded(-111.2346))
+ assert_equal("111.235", number_helper.number_to_rounded(111.2346))
+ assert_equal("31.83", number_helper.number_to_rounded(31.825, :precision => 2))
+ assert_equal("111.23", number_helper.number_to_rounded(111.2346, :precision => 2))
+ assert_equal("111.00", number_helper.number_to_rounded(111, :precision => 2))
+ assert_equal("111.235", number_helper.number_to_rounded("111.2346"))
+ assert_equal("31.83", number_helper.number_to_rounded("31.825", :precision => 2))
+ assert_equal("3268", number_helper.number_to_rounded((32.6751 * 100.00), :precision => 0))
+ assert_equal("112", number_helper.number_to_rounded(111.50, :precision => 0))
+ assert_equal("1234567892", number_helper.number_to_rounded(1234567891.50, :precision => 0))
+ assert_equal("0", number_helper.number_to_rounded(0, :precision => 0))
+ assert_equal("0.00100", number_helper.number_to_rounded(0.001, :precision => 5))
+ assert_equal("0.001", number_helper.number_to_rounded(0.00111, :precision => 3))
+ assert_equal("10.00", number_helper.number_to_rounded(9.995, :precision => 2))
+ assert_equal("11.00", number_helper.number_to_rounded(10.995, :precision => 2))
+ assert_equal("0.00", number_helper.number_to_rounded(-0.001, :precision => 2))
+ end
+ end
+
+ def test_to_rounded_with_custom_delimiter_and_separator
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '31,83', number_helper.number_to_rounded(31.825, :precision => 2, :separator => ',')
+ assert_equal '1.231,83', number_helper.number_to_rounded(1231.825, :precision => 2, :separator => ',', :delimiter => '.')
+ end
+ end
+
+ def test_to_rounded_with_significant_digits
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal "124000", number_helper.number_to_rounded(123987, :precision => 3, :significant => true)
+ assert_equal "120000000", number_helper.number_to_rounded(123987876, :precision => 2, :significant => true )
+ assert_equal "40000", number_helper.number_to_rounded("43523", :precision => 1, :significant => true )
+ assert_equal "9775", number_helper.number_to_rounded(9775, :precision => 4, :significant => true )
+ assert_equal "5.4", number_helper.number_to_rounded(5.3923, :precision => 2, :significant => true )
+ assert_equal "5", number_helper.number_to_rounded(5.3923, :precision => 1, :significant => true )
+ assert_equal "1", number_helper.number_to_rounded(1.232, :precision => 1, :significant => true )
+ assert_equal "7", number_helper.number_to_rounded(7, :precision => 1, :significant => true )
+ assert_equal "1", number_helper.number_to_rounded(1, :precision => 1, :significant => true )
+ assert_equal "53", number_helper.number_to_rounded(52.7923, :precision => 2, :significant => true )
+ assert_equal "9775.00", number_helper.number_to_rounded(9775, :precision => 6, :significant => true )
+ assert_equal "5.392900", number_helper.number_to_rounded(5.3929, :precision => 7, :significant => true )
+ assert_equal "0.0", number_helper.number_to_rounded(0, :precision => 2, :significant => true )
+ assert_equal "0", number_helper.number_to_rounded(0, :precision => 1, :significant => true )
+ assert_equal "0.0001", number_helper.number_to_rounded(0.0001, :precision => 1, :significant => true )
+ assert_equal "0.000100", number_helper.number_to_rounded(0.0001, :precision => 3, :significant => true )
+ assert_equal "0.0001", number_helper.number_to_rounded(0.0001111, :precision => 1, :significant => true )
+ assert_equal "10.0", number_helper.number_to_rounded(9.995, :precision => 3, :significant => true)
+ assert_equal "9.99", number_helper.number_to_rounded(9.994, :precision => 3, :significant => true)
+ assert_equal "11.0", number_helper.number_to_rounded(10.995, :precision => 3, :significant => true)
+ end
+ end
+
+ def test_to_rounded_with_strip_insignificant_zeros
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal "9775.43", number_helper.number_to_rounded(9775.43, :precision => 4, :strip_insignificant_zeros => true )
+ assert_equal "9775.2", number_helper.number_to_rounded(9775.2, :precision => 6, :significant => true, :strip_insignificant_zeros => true )
+ assert_equal "0", number_helper.number_to_rounded(0, :precision => 6, :significant => true, :strip_insignificant_zeros => true )
+ end
+ end
+
+ def test_to_rounded_with_significant_true_and_zero_precision
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ # Zero precision with significant is a mistake (would always return zero),
+ # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size)
+ assert_equal "124", number_helper.number_to_rounded(123.987, :precision => 0, :significant => true)
+ assert_equal "12", number_helper.number_to_rounded(12, :precision => 0, :significant => true )
+ assert_equal "12", number_helper.number_to_rounded("12.3", :precision => 0, :significant => true )
+ end
+ end
+
+ def test_number_number_to_human_size
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '0 Bytes', number_helper.number_to_human_size(0)
+ assert_equal '1 Byte', number_helper.number_to_human_size(1)
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123.0)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123)
+ assert_equal '1.21 KB', number_helper.number_to_human_size(1234)
+ assert_equal '12.1 KB', number_helper.number_to_human_size(12345)
+ assert_equal '1.18 MB', number_helper.number_to_human_size(1234567)
+ assert_equal '1.15 GB', number_helper.number_to_human_size(1234567890)
+ assert_equal '1.12 TB', number_helper.number_to_human_size(1234567890123)
+ assert_equal '1030 TB', number_helper.number_to_human_size(terabytes(1026))
+ assert_equal '444 KB', number_helper.number_to_human_size(kilobytes(444))
+ assert_equal '1020 MB', number_helper.number_to_human_size(megabytes(1023))
+ assert_equal '3 TB', number_helper.number_to_human_size(terabytes(3))
+ assert_equal '1.2 MB', number_helper.number_to_human_size(1234567, :precision => 2)
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :precision => 4)
+ assert_equal '123 Bytes', number_helper.number_to_human_size('123')
+ assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2)
+ assert_equal '1.01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4)
+ assert_equal '10 KB', number_helper.number_to_human_size(kilobytes(10.000), :precision => 4)
+ assert_equal '1 Byte', number_helper.number_to_human_size(1.1)
+ assert_equal '10 Bytes', number_helper.number_to_human_size(10)
+ end
+ end
+
+ def test_number_to_human_size_with_si_prefix
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :prefix => :si)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123.0, :prefix => :si)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123, :prefix => :si)
+ assert_equal '1.23 KB', number_helper.number_to_human_size(1234, :prefix => :si)
+ assert_equal '12.3 KB', number_helper.number_to_human_size(12345, :prefix => :si)
+ assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si)
+ assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si)
+ assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si)
+ end
+ end
+
+ def test_number_to_human_size_with_options_hash
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '1.2 MB', number_helper.number_to_human_size(1234567, :precision => 2)
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :precision => 4)
+ assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2)
+ assert_equal '1.01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4)
+ assert_equal '10 KB', number_helper.number_to_human_size(kilobytes(10.000), :precision => 4)
+ assert_equal '1 TB', number_helper.number_to_human_size(1234567890123, :precision => 1)
+ assert_equal '500 MB', number_helper.number_to_human_size(524288000, :precision=>3)
+ assert_equal '10 MB', number_helper.number_to_human_size(9961472, :precision=>0)
+ assert_equal '40 KB', number_helper.number_to_human_size(41010, :precision => 1)
+ assert_equal '40 KB', number_helper.number_to_human_size(41100, :precision => 2)
+ assert_equal '1.0 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2, :strip_insignificant_zeros => false)
+ assert_equal '1.012 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 3, :significant => false)
+ assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 0, :significant => true) #ignores significant it precision is 0
+ end
+ end
+
+ def test_number_to_human_size_with_custom_delimiter_and_separator
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '1,01 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 3, :separator => ',')
+ assert_equal '1,01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4, :separator => ',')
+ assert_equal '1.000,1 TB', number_helper.number_to_human_size(terabytes(1000.1), :precision => 5, :delimiter => '.', :separator => ',')
+ end
+ end
+
+ def test_number_to_human
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '-123', number_helper.number_to_human(-123)
+ assert_equal '-0.5', number_helper.number_to_human(-0.5)
+ assert_equal '0', number_helper.number_to_human(0)
+ assert_equal '0.5', number_helper.number_to_human(0.5)
+ assert_equal '123', number_helper.number_to_human(123)
+ assert_equal '1.23 Thousand', number_helper.number_to_human(1234)
+ assert_equal '12.3 Thousand', number_helper.number_to_human(12345)
+ assert_equal '1.23 Million', number_helper.number_to_human(1234567)
+ assert_equal '1.23 Billion', number_helper.number_to_human(1234567890)
+ assert_equal '1.23 Trillion', number_helper.number_to_human(1234567890123)
+ assert_equal '1.23 Quadrillion', number_helper.number_to_human(1234567890123456)
+ assert_equal '1230 Quadrillion', number_helper.number_to_human(1234567890123456789)
+ assert_equal '490 Thousand', number_helper.number_to_human(489939, :precision => 2)
+ assert_equal '489.9 Thousand', number_helper.number_to_human(489939, :precision => 4)
+ assert_equal '489 Thousand', number_helper.number_to_human(489000, :precision => 4)
+ assert_equal '489.0 Thousand', number_helper.number_to_human(489000, :precision => 4, :strip_insignificant_zeros => false)
+ assert_equal '1.2346 Million', number_helper.number_to_human(1234567, :precision => 4, :significant => false)
+ assert_equal '1,2 Million', number_helper.number_to_human(1234567, :precision => 1, :significant => false, :separator => ',')
+ assert_equal '1 Million', number_helper.number_to_human(1234567, :precision => 0, :significant => true, :separator => ',') #significant forced to false
+ end
+ end
+
+ def test_number_to_human_with_custom_units
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ #Only integers
+ volume = {:unit => "ml", :thousand => "lt", :million => "m3"}
+ assert_equal '123 lt', number_helper.number_to_human(123456, :units => volume)
+ assert_equal '12 ml', number_helper.number_to_human(12, :units => volume)
+ assert_equal '1.23 m3', number_helper.number_to_human(1234567, :units => volume)
+
+ #Including fractionals
+ distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
+ assert_equal '1.23 mm', number_helper.number_to_human(0.00123, :units => distance)
+ assert_equal '1.23 cm', number_helper.number_to_human(0.0123, :units => distance)
+ assert_equal '1.23 dm', number_helper.number_to_human(0.123, :units => distance)
+ assert_equal '1.23 m', number_helper.number_to_human(1.23, :units => distance)
+ assert_equal '1.23 dam', number_helper.number_to_human(12.3, :units => distance)
+ assert_equal '1.23 hm', number_helper.number_to_human(123, :units => distance)
+ assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance)
+ assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance)
+ assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance)
+ assert_equal '12.3 km', number_helper.number_to_human(12300, :units => distance)
+
+ #The quantifiers don't need to be a continuous sequence
+ gangster = {:hundred => "hundred bucks", :million => "thousand quids"}
+ assert_equal '1 hundred bucks', number_helper.number_to_human(100, :units => gangster)
+ assert_equal '25 hundred bucks', number_helper.number_to_human(2500, :units => gangster)
+ assert_equal '25 thousand quids', number_helper.number_to_human(25000000, :units => gangster)
+ assert_equal '12300 thousand quids', number_helper.number_to_human(12345000000, :units => gangster)
+
+ #Spaces are stripped from the resulting string
+ assert_equal '4', number_helper.number_to_human(4, :units => {:unit => "", :ten => 'tens '})
+ assert_equal '4.5 tens', number_helper.number_to_human(45, :units => {:unit => "", :ten => ' tens '})
+ end
+ end
+
+ def test_number_to_human_with_custom_format
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '123 times Thousand', number_helper.number_to_human(123456, :format => "%n times %u")
+ volume = {:unit => "ml", :thousand => "lt", :million => "m3"}
+ assert_equal '123.lt', number_helper.number_to_human(123456, :units => volume, :format => "%n.%u")
+ end
+ end
+
+ def test_number_helpers_should_return_nil_when_given_nil
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_nil number_helper.number_to_phone(nil)
+ assert_nil number_helper.number_to_currency(nil)
+ assert_nil number_helper.number_to_percentage(nil)
+ assert_nil number_helper.number_to_delimited(nil)
+ assert_nil number_helper.number_to_rounded(nil)
+ assert_nil number_helper.number_to_human_size(nil)
+ assert_nil number_helper.number_to_human(nil)
+ end
+ end
+
+ def test_number_helpers_do_not_mutate_options_hash
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ options = { 'raise' => true }
+
+ number_helper.number_to_phone(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_currency(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_percentage(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_delimited(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_rounded(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_human_size(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_human(1, options)
+ assert_equal({ 'raise' => true }, options)
+ end
+ end
+
+ def test_number_helpers_should_return_non_numeric_param_unchanged
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("+1-x x 123", number_helper.number_to_phone("x", :country_code => 1, :extension => 123))
+ assert_equal("x", number_helper.number_to_phone("x"))
+ assert_equal("$x.", number_helper.number_to_currency("x."))
+ assert_equal("$x", number_helper.number_to_currency("x"))
+ assert_equal("x%", number_helper.number_to_percentage("x"))
+ assert_equal("x", number_helper.number_to_delimited("x"))
+ assert_equal("x.", number_helper.number_to_rounded("x."))
+ assert_equal("x", number_helper.number_to_rounded("x"))
+ assert_equal "x", number_helper.number_to_human_size('x')
+ assert_equal "x", number_helper.number_to_human('x')
+ end
+ end
+
+ def test_extending_or_including_number_helper_correctly_hides_private_methods
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert !number_helper.respond_to?(:valid_float?)
+ assert number_helper.respond_to?(:valid_float?, true)
+ end
+ end
+
+ end
+ end
+end
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index e5b5547478..c02bfa8497 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -18,11 +18,9 @@ module ActiveSupport
end
end
- def test_callback_with_exception
+ def test_standard_error_raised_within_setup_callback_is_puked
tc = Class.new(TestCase) do
- def self.name
- nil
- end
+ def self.name; nil; end
setup :bad_callback
def bad_callback; raise 'oh noes' end
@@ -41,11 +39,9 @@ module ActiveSupport
assert_equal 'oh noes', exception.message
end
- def test_teardown_callback_with_exception
+ def test_standard_error_raised_within_teardown_callback_is_puked
tc = Class.new(TestCase) do
- def self.name
- nil
- end
+ def self.name; nil; end
teardown :bad_callback
def bad_callback; raise 'oh noes' end
@@ -63,5 +59,51 @@ module ActiveSupport
assert_equal test_name, name
assert_equal 'oh noes', exception.message
end
+
+ def test_passthrough_exception_raised_within_test_method_is_not_rescued
+ tc = Class.new(TestCase) do
+ def self.name; nil; end
+
+ def test_which_raises_interrupt; raise Interrupt; end
+ end
+
+ test_name = 'test_which_raises_interrupt'
+ fr = FakeRunner.new
+
+ test = tc.new test_name
+ assert_raises(Interrupt) { test.run fr }
+ end
+
+ def test_passthrough_exception_raised_within_setup_callback_is_not_rescued
+ tc = Class.new(TestCase) do
+ def self.name; nil; end
+
+ setup :callback_which_raises_interrupt
+ def callback_which_raises_interrupt; raise Interrupt; end
+ def test_true; assert true end
+ end
+
+ test_name = 'test_true'
+ fr = FakeRunner.new
+
+ test = tc.new test_name
+ assert_raises(Interrupt) { test.run fr }
+ end
+
+ def test_passthrough_exception_raised_within_teardown_callback_is_not_rescued
+ tc = Class.new(TestCase) do
+ def self.name; nil; end
+
+ teardown :callback_which_raises_interrupt
+ def callback_which_raises_interrupt; raise Interrupt; end
+ def test_true; assert true end
+ end
+
+ test_name = 'test_true'
+ fr = FakeRunner.new
+
+ test = tc.new test_name
+ assert_raises(Interrupt) { test.run fr }
+ end
end
end
diff --git a/activesupport/test/testing/performance_test.rb b/activesupport/test/testing/performance_test.rb
new file mode 100644
index 0000000000..74d7dae9e7
--- /dev/null
+++ b/activesupport/test/testing/performance_test.rb
@@ -0,0 +1,40 @@
+require 'abstract_unit'
+require 'active_support/testing/performance'
+
+
+module ActiveSupport
+ module Testing
+ class PerformanceTest < ActiveSupport::TestCase
+ def test_amount_format
+ amount_metric = ActiveSupport::Testing::Performance::Metrics[:amount].new
+ assert_equal "0", amount_metric.format(0)
+ assert_equal "1", amount_metric.format(1.23)
+ assert_equal "40,000,000", amount_metric.format(40000000)
+ end
+
+ def test_time_format
+ time_metric = ActiveSupport::Testing::Performance::Metrics[:time].new
+ assert_equal "0 ms", time_metric.format(0)
+ assert_equal "40 ms", time_metric.format(0.04)
+ assert_equal "41 ms", time_metric.format(0.0415)
+ assert_equal "1.23 sec", time_metric.format(1.23)
+ assert_equal "40000.00 sec", time_metric.format(40000)
+ assert_equal "-5000 ms", time_metric.format(-5)
+ end
+
+ def test_space_format
+ space_metric = ActiveSupport::Testing::Performance::Metrics[:digital_information_unit].new
+ assert_equal "0 Bytes", space_metric.format(0)
+ assert_equal "0 Bytes", space_metric.format(0.4)
+ assert_equal "1 Byte", space_metric.format(1.23)
+ assert_equal "123 Bytes", space_metric.format(123)
+ assert_equal "123 Bytes", space_metric.format(123.45)
+ assert_equal "12 KB", space_metric.format(12345)
+ assert_equal "1.2 MB", space_metric.format(1234567)
+ assert_equal "9.3 GB", space_metric.format(10**10)
+ assert_equal "91 TB", space_metric.format(10**14)
+ assert_equal "910000 TB", space_metric.format(10**18)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/guides/Rakefile b/guides/Rakefile
index ad4ff91fe6..d005a12936 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -1,11 +1,71 @@
-desc 'Generate guides (for authors), use ONLY=foo to process just "foo.textile"'
-task :generate_guides do
- ENV["WARN_BROKEN_LINKS"] = "1" # authors can't disable this
- ruby "rails_guides.rb"
-end
+namespace :guides do
+
+ desc 'Generate guides (for authors), use ONLY=foo to process just "foo.textile"'
+ task :generate => 'generate:html'
+
+ namespace :generate do
+
+ desc "Generate HTML guides"
+ task :html do
+ ENV["WARN_BROKEN_LINKS"] = "1" # authors can't disable this
+ ruby "rails_guides.rb"
+ end
+
+ desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/kindlepublishing"
+ task :kindle do
+ ENV['KINDLE'] = '1'
+ Rake::Task['guides:generate:html'].invoke
+ end
+ end
+
+ # Validate guides -------------------------------------------------------------------------
+ desc 'Validate guides, use ONLY=foo to process just "foo.html"'
+ task :validate do
+ ruby "w3c_validator.rb"
+ end
+
+ desc "Show help"
+ task :help do
+ puts <<-help
+
+Guides are taken from the source directory, and the resulting HTML goes into the
+output directory. Assets are stored under files, and copied to output/files as
+part of the generation process.
+
+All this process is handled via rake tasks, here's a full list of them:
-# Validate guides -------------------------------------------------------------------------
-desc 'Validate guides, use ONLY=foo to process just "foo.html"'
-task :validate_guides do
- ruby "w3c_validator.rb"
+#{%x[rake -T]}
+Some arguments may be passed via environment variables:
+
+ WARNINGS=1
+ Internal links (anchors) are checked, also detects duplicated IDs.
+
+ ALL=1
+ Force generation of all guides.
+
+ ONLY=name
+ Useful if you want to generate only one or a set of guides.
+
+ Generate only association_basics.html:
+ ONLY=assoc
+
+ Separate many using commas:
+ ONLY=assoc,migrations
+
+ GUIDES_LANGUAGE
+ Use it when you want to generate translated guides in
+ source/<GUIDES_LANGUAGE> folder (such as source/es)
+
+ EDGE=1
+ Indicate generated guides should be marked as edge.
+
+Examples:
+ $ rake guides:generate ALL=1
+ $ rake guides:generate EDGE=1
+ $ rake guides:generate:kindle EDGE=1
+ $ rake guides:generate GUIDES_LANGUAGE=es
+ help
+ end
end
+
+task :default => 'guides:help'
diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb
index d6a98f9ac4..230bebf3bb 100644
--- a/guides/rails_guides/generator.rb
+++ b/guides/rails_guides/generator.rb
@@ -1,7 +1,7 @@
# ---------------------------------------------------------------------------
#
-# This script generates the guides. It can be invoked either directly or via the
-# generate_guides rake task within the railties directory.
+# This script generates the guides. It can be invoked via the
+# guides:generate rake task within the guides directory.
#
# Guides are taken from the source directory, and the resulting HTML goes into the
# output directory. Assets are stored under files, and copied to output/files as
@@ -47,11 +47,6 @@
# Set to "1" to indicate generated guides should be marked as edge. This
# inserts a badge and changes the preamble of the home page.
#
-# KINDLE
-# Set to "1" to generate the .mobi with all the guides. The kindlegen
-# executable must be in your PATH. You can get it for free from
-# http://www.amazon.com/kindlepublishing
-#
# ---------------------------------------------------------------------------
require 'set'
diff --git a/guides/source/action_view_overview.textile b/guides/source/action_view_overview.textile
index bde30ba21c..fdfa97effa 100644
--- a/guides/source/action_view_overview.textile
+++ b/guides/source/action_view_overview.textile
@@ -454,7 +454,7 @@ input("post", "title") # =>
h4. RecordTagHelper
-This module provides methods for generating a container tag, such as a +<div>+, for your record. This is the recommended way of creating a container for render your Active Record object, as it adds an appropriate class and id attributes to that container. You can then refer to those containers easily by following the convention, instead of having to think about which class or id attribute you should use.
+This module provides methods for generating container tags, such as +div+, for your record. This is the recommended way of creating a container for render your Active Record object, as it adds an appropriate class and id attributes to that container. You can then refer to those containers easily by following the convention, instead of having to think about which class or id attribute you should use.
h5. content_tag_for
@@ -542,28 +542,28 @@ image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png
h5. register_javascript_expansion
-Register one or more JavaScript files to be included when symbol is passed to javascript_include_tag. This method is typically intended to be called from plugin initialization to register JavaScript files that the plugin installed in +public/javascripts+.
+Register one or more JavaScript files to be included when symbol is passed to javascript_include_tag. This method is typically intended to be called from plugin initialization to register JavaScript files that the plugin installed in +vendor/assets/javascripts+.
<ruby>
ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"]
javascript_include_tag :monkey # =>
- <script src="/javascripts/head.js"></script>
- <script src="/javascripts/body.js"></script>
- <script src="/javascripts/tail.js"></script>
+ <script src="/assets/head.js"></script>
+ <script src="/assets/body.js"></script>
+ <script src="/assets/tail.js"></script>
</ruby>
h5. register_stylesheet_expansion
-Register one or more stylesheet files to be included when symbol is passed to +stylesheet_link_tag+. This method is typically intended to be called from plugin initialization to register stylesheet files that the plugin installed in +public/stylesheets+.
+Register one or more stylesheet files to be included when symbol is passed to +stylesheet_link_tag+. This method is typically intended to be called from plugin initialization to register stylesheet files that the plugin installed in +vendor/assets/stylesheets+.
<ruby>
ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"]
stylesheet_link_tag :monkey # =>
- <link href="/stylesheets/head.css" media="screen" rel="stylesheet" />
- <link href="/stylesheets/body.css" media="screen" rel="stylesheet" />
- <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" />
+ <link href="/assets/head.css" media="screen" rel="stylesheet" />
+ <link href="/assets/body.css" media="screen" rel="stylesheet" />
+ <link href="/assets/tail.css" media="screen" rel="stylesheet" />
</ruby>
h5. auto_discovery_link_tag
@@ -577,44 +577,49 @@ auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "RSS
h5. image_path
-Computes the path to an image asset in the +public/images+ directory. Full paths from the document root will be passed through. Used internally by +image_tag+ to build the image path.
+Computes the path to an image asset in the +app/assets/images+ directory. Full paths from the document root will be passed through. Used internally by +image_tag+ to build the image path.
<ruby>
-image_path("edit.png") # => /images/edit.png
+image_path("edit.png") # => /assets/edit.png
+</ruby>
+
+Fingerprint will be added to the filename if config.assets.digest is set to true.
+
+<ruby>
+image_path("edit.png") # => /assets/edit-2d1a2db63fc738690021fedb5a65b68e.png
</ruby>
h5. image_url
-Computes the url to an image asset in the +public/images+ directory. This will call +image_path+ internally and merge with your current host or your asset host.
+Computes the url to an image asset in the +app/asset/images+ directory. This will call +image_path+ internally and merge with your current host or your asset host.
<ruby>
-image_url("edit.png") # => http://www.example.com/images/edit.png
+image_url("edit.png") # => http://www.example.com/assets/edit.png
</ruby>
h5. image_tag
-Returns an html image tag for the source. The source can be a full path or a file that exists in your +public/images+ directory.
+Returns an html image tag for the source. The source can be a full path or a file that exists in your +app/assets/images+ directory.
<ruby>
-image_tag("icon.png") # => <img src="/images/icon.png" alt="Icon" />
+image_tag("icon.png") # => <img src="/assets/icon.png" alt="Icon" />
</ruby>
h5. javascript_include_tag
-Returns an html script tag for each of the sources provided. You can pass in the filename (+.js+ extension is optional) of JavaScript files that exist in your +public/javascripts+ directory for inclusion into the current page or you can pass the full path relative to your document root.
+Returns an html script tag for each of the sources provided. You can pass in the filename (+.js+ extension is optional) of JavaScript files that exist in your +app/assets/javascripts+ directory for inclusion into the current page or you can pass the full path relative to your document root.
<ruby>
-javascript_include_tag "common" # =>
- <script src="/javascripts/common.js"></script>
+javascript_include_tag "common" # => <script src="/assets/common.js"></script>
</ruby>
-If the application does not use the asset pipeline, to include the jQuery JavaScript library in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +public/javascripts+ directory, it will be included as well.
+If the application does not use the asset pipeline, to include the jQuery JavaScript library in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +app/assets/javascripts+ directory, it will be included as well.
<ruby>
javascript_include_tag :defaults
</ruby>
-You can also include all JavaScript files in the +public/javascripts+ directory using +:all+ as the source.
+You can also include all JavaScript files in the +app/assets/javascripts+ directory using +:all+ as the source.
<ruby>
javascript_include_tag :all
@@ -629,18 +634,18 @@ javascript_include_tag :all, :cache => true # =>
h5. javascript_path
-Computes the path to a JavaScript asset in the +public/javascripts+ directory. If the source filename has no extension, +.js+ will be appended. Full paths from the document root will be passed through. Used internally by +javascript_include_tag+ to build the script path.
+Computes the path to a JavaScript asset in the +app/assets/javascripts+ directory. If the source filename has no extension, +.js+ will be appended. Full paths from the document root will be passed through. Used internally by +javascript_include_tag+ to build the script path.
<ruby>
-javascript_path "common" # => /javascripts/common.js
+javascript_path "common" # => /assets/common.js
</ruby>
h5. javascript_url
-Computes the url to a JavaScript asset in the +public/javascripts+ directory. This will call +javascript_path+ internally and merge with your current host or your asset host.
+Computes the url to a JavaScript asset in the +app/assets/javascripts+ directory. This will call +javascript_path+ internally and merge with your current host or your asset host.
<ruby>
-javascript_url "common" # => http://www.example.com/javascripts/common.js
+javascript_url "common" # => http://www.example.com/assets/common.js
</ruby>
h5. stylesheet_link_tag
@@ -648,8 +653,7 @@ h5. stylesheet_link_tag
Returns a stylesheet link tag for the sources specified as arguments. If you don't specify an extension, +.css+ will be appended automatically.
<ruby>
-stylesheet_link_tag "application" # =>
- <link href="/stylesheets/application.css" media="screen" rel="stylesheet" />
+stylesheet_link_tag "application" # => <link href="/assets/application.css" media="screen" rel="stylesheet" />
</ruby>
You can also include all styles in the stylesheet directory using :all as the source:
@@ -662,23 +666,23 @@ You can also cache multiple stylesheets into one file, which requires less HTTP
<ruby>
stylesheet_link_tag :all, :cache => true
- <link href="/stylesheets/all.css" media="screen" rel="stylesheet" />
+# => <link href="/assets/all.css" media="screen" rel="stylesheet" />
</ruby>
h5. stylesheet_path
-Computes the path to a stylesheet asset in the +public/stylesheets+ directory. If the source filename has no extension, .css will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path.
+Computes the path to a stylesheet asset in the +app/assets/stylesheets+ directory. If the source filename has no extension, .css will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path.
<ruby>
-stylesheet_path "application" # => /stylesheets/application.css
+stylesheet_path "application" # => /assets/application.css
</ruby>
h5. stylesheet_url
-Computes the url to a stylesheet asset in the +public/stylesheets+ directory. This will call +stylesheet_path+ internally and merge with your current host or your asset host.
+Computes the url to a stylesheet asset in the +app/assets/stylesheets+ directory. This will call +stylesheet_path+ internally and merge with your current host or your asset host.
<ruby>
-stylesheet_url "application" # => http://www.example.com/stylesheets/application.css
+stylesheet_url "application" # => http://www.example.com/assets/application.css
</ruby>
h4. AtomFeedHelper
diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile
index 294ef25b33..4b14671efc 100644
--- a/guides/source/active_record_querying.textile
+++ b/guides/source/active_record_querying.textile
@@ -259,6 +259,54 @@ SELECT * FROM clients WHERE (clients.id IN (1,10))
WARNING: <tt>Model.find(array_of_primary_key)</tt> will raise an +ActiveRecord::RecordNotFound+ exception unless a matching record is found for <strong>all</strong> of the supplied primary keys.
+h5. take
+
+<tt>Model.take(limit)</tt> retrieves the first number of records specified by +limit+ without any explicit ordering:
+
+<ruby>
+Client.take(2)
+# => [#<Client id: 1, first_name: "Lifo">,
+ #<Client id: 2, first_name: "Raf">]
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 2
+</sql>
+
+h5. first
+
+<tt>Model.first(limit)</tt> finds the first number of records specified by +limit+ ordered by primary key:
+
+<ruby>
+Client.first(2)
+# => [#<Client id: 1, first_name: "Lifo">,
+ #<Client id: 2, first_name: "Raf">]
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 2
+</sql>
+
+h5. last
+
+<tt>Model.last(limit)</tt> finds the number of records specified by +limit+ ordered by primary key in descending order:
+
+<ruby>
+Client.last(2)
+# => [#<Client id: 10, first_name: "Ryan">,
+ #<Client id: 9, first_name: "John">]
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients ORDER By id DESC LIMIT 2
+</sql>
+
h4. Retrieving Multiple Objects in Batches
We often need to iterate over a large set of records, as when we send a newsletter to a large set of users, or when we export data.
diff --git a/guides/source/active_support_core_extensions.textile b/guides/source/active_support_core_extensions.textile
index 587f65529e..2addc50d68 100644
--- a/guides/source/active_support_core_extensions.textile
+++ b/guides/source/active_support_core_extensions.textile
@@ -1840,6 +1840,76 @@ date and time arithmetic.
NOTE: Defined in +active_support/core_ext/numeric/time.rb+.
+h4. Formatting
+
+Enables the formatting of numbers in a variety of ways.
+
+Produce a string representation of a number as a telephone number:
+<ruby>
+5551234.to_s(:phone) # => 555-1234
+1235551234.to_s(:phone) # => 123-555-1234
+1235551234.to_s(:phone, :area_code => true) # => (123) 555-1234
+1235551234.to_s(:phone, :delimiter => " ") # => 123 555 1234
+1235551234.to_s(:phone, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
+1235551234.to_s(:phone, :country_code => 1) # => +1-123-555-1234
+</ruby>
+
+Produce a string representation of a number as currency:
+<ruby>
+1234567890.50.to_s(:currency) # => $1,234,567,890.50
+1234567890.506.to_s(:currency) # => $1,234,567,890.51
+1234567890.506.to_s(:currency, :precision => 3) # => $1,234,567,890.506
+</ruby>
+
+Produce a string representation of a number as a percentage:
+<ruby>
+100.to_s(:percentage) # => 100.000%
+100.to_s(:percentage, :precision => 0) # => 100%
+1000.to_s(:percentage, :delimiter => '.', :separator => ',') # => 1.000,000%
+302.24398923423.to_s(:percentage, :precision => 5) # => 302.24399%
+</ruby>
+
+Produce a string representation of a number in delimited form:
+<ruby>
+12345678.to_s(:delimited) # => 12,345,678
+12345678.05.to_s(:delimited) # => 12,345,678.05
+12345678.to_s(:delimited, :delimiter => ".") # => 12.345.678
+12345678.to_s(:delimited, :delimiter => ",") # => 12,345,678
+12345678.05.to_s(:delimited, :separator => " ") # => 12,345,678 05
+</ruby>
+
+Produce a string representation of a number rounded to a precision:
+<ruby>
+111.2345.to_s(:rounded) # => 111.235
+111.2345.to_s(:rounded, :precision => 2) # => 111.23
+13.to_s(:rounded, :precision => 5) # => 13.00000
+389.32314.to_s(:rounded, :precision => 0) # => 389
+111.2345.to_s(:rounded, :significant => true) # => 111
+</ruby>
+
+Produce a string representation of a number as a human-readable number of bytes:
+<ruby>
+123.to_s(:human_size) # => 123 Bytes
+1234.to_s(:human_size) # => 1.21 KB
+12345.to_s(:human_size) # => 12.1 KB
+1234567.to_s(:human_size) # => 1.18 MB
+1234567890.to_s(:human_size) # => 1.15 GB
+1234567890123.to_s(:human_size) # => 1.12 TB
+</ruby>
+
+Produce a string representation of a number in human-readable words:
+<ruby>
+123.to_s(:human) # => "123"
+1234.to_s(:human) # => "1.23 Thousand"
+12345.to_s(:human) # => "12.3 Thousand"
+1234567.to_s(:human) # => "1.23 Million"
+1234567890.to_s(:human) # => "1.23 Billion"
+1234567890123.to_s(:human) # => "1.23 Trillion"
+1234567890123456.to_s(:human) # => "1.23 Quadrillion"
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/numeric/formatting.rb+.
+
h3. Extensions to +Integer+
h4. +multiple_of?+
diff --git a/guides/source/active_support_instrumentation.textile b/guides/source/active_support_instrumentation.textile
index 430549fba4..dcdd9d14f5 100644
--- a/guides/source/active_support_instrumentation.textile
+++ b/guides/source/active_support_instrumentation.textile
@@ -15,7 +15,7 @@ h3. Introduction to instrumentation
The instrumentation API provided by ActiveSupport allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in <TODO: link to section detailing each hook point>. With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code.
-For example, there is a hook provided within Active Record that is called every time Active Record uses a SQL query on a database. This hook could be *subscribed* to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken.
+For example, there is a hook provided within Active Record that is called every time Active Record uses an SQL query on a database. This hook could be *subscribed* to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken.
You are even able to create your own events inside your application which you can later subscribe to.
@@ -377,7 +377,7 @@ listen to any notification.
The block receives the following arguments:
# The name of the event
-# Time when is started
+# Time when it started
# Time when it finished
# An unique ID for this event
# The payload (described in previous sections)
diff --git a/guides/source/command_line.textile b/guides/source/command_line.textile
index b656a0857a..19e42cea93 100644
--- a/guides/source/command_line.textile
+++ b/guides/source/command_line.textile
@@ -31,20 +31,21 @@ h4. +rails new+
The first thing we'll want to do is create a new Rails application by running the +rails new+ command after installing Rails.
-TIP: You can install the rails gem by typing +gem install rails+, if you don't have it already.
+INFO: You can install the rails gem by typing +gem install rails+, if you don't have it already.
<shell>
$ rails new commandsapp
create
create README.rdoc
- create .gitignore
create Rakefile
create config.ru
+ create .gitignore
create Gemfile
create app
...
create tmp/cache
- create tmp/pids
+ ...
+ run bundle install
</shell>
Rails will set you up with what seems like a huge amount of stuff for such a tiny command! You've got the entire Rails directory structure now with all the code you need to run our simple application right out of the box.
@@ -61,17 +62,17 @@ With no further work, +rails server+ will run our new shiny Rails app:
$ cd commandsapp
$ rails server
=> Booting WEBrick
-=> Rails 3.1.0 application starting in development on http://0.0.0.0:3000
+=> Rails 3.2.3 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
-[2010-04-18 03:20:33] INFO WEBrick 1.3.1
-[2010-04-18 03:20:33] INFO ruby 1.8.7 (2010-01-10) [x86_64-linux]
-[2010-04-18 03:20:33] INFO WEBrick::HTTPServer#start: pid=26086 port=3000
+[2012-05-28 00:39:41] INFO WEBrick 1.3.1
+[2012-05-28 00:39:41] INFO ruby 1.9.2 (2011-02-18) [x86_64-darwin11.2.0]
+[2012-05-28 00:39:41] INFO WEBrick::HTTPServer#start: pid=69680 port=3000
</shell>
With just three commands we whipped up a Rails server listening on port 3000. Go to your browser and open "http://localhost:3000":http://localhost:3000, you will see a basic Rails app running.
-You can also use the alias "s" to start the server: <tt>rails s</tt>.
+INFO: You can also use the alias "s" to start the server: <tt>rails s</tt>.
The server can be run on a different port using the +-p+ option. The default development environment can be changed using +-e+.
@@ -85,7 +86,7 @@ h4. +rails generate+
The +rails generate+ command uses templates to create a whole lot of things. Running +rails generate+ by itself gives a list of available generators:
-You can also use the alias "g" to invoke the generator command: <tt>rails g</tt>.
+INFO: You can also use the alias "g" to invoke the generator command: <tt>rails g</tt>.
<shell>
$ rails generate
@@ -97,6 +98,7 @@ Usage: rails generate GENERATOR [args] [options]
Please choose a generator below.
Rails:
+ assets
controller
generator
...
@@ -118,23 +120,22 @@ Usage: rails generate controller NAME [action action] [options]
...
...
+Description:
+ ...
+
+ To create a controller within a module, specify the controller name as a
+ path like 'parent_module/controller_name'.
+
+ ...
+
Example:
- rails generate controller CreditCard open debit credit close
+ `rails generate controller CreditCard open debit credit close`
Credit card controller with URLs like /credit_card/debit.
- Controller: app/controllers/credit_card_controller.rb
- Views: app/views/credit_card/debit.html.erb [...]
- Helper: app/helpers/credit_card_helper.rb
- Test: test/functional/credit_card_controller_test.rb
-
-Modules Example:
- rails generate controller 'admin/credit_card' suspend late_fee
-
- Credit card admin controller with URLs like /admin/credit_card/suspend.
- Controller: app/controllers/admin/credit_card_controller.rb
- Views: app/views/admin/credit_card/debit.html.erb [...]
- Helper: app/helpers/admin/credit_card_helper.rb
- Test: test/functional/admin/credit_card_controller_test.rb
+ Controller: app/controllers/credit_card_controller.rb
+ Functional Test: test/functional/credit_card_controller_test.rb
+ Views: app/views/credit_card/debit.html.erb [...]
+ Helper: app/helpers/credit_card_helper.rb
</shell>
The controller generator is expecting parameters in the form of +generate controller ControllerName action1 action2+. Let's make a +Greetings+ controller with an action of *hello*, which will say something nice to us.
@@ -153,10 +154,10 @@ $ rails generate controller Greetings hello
invoke test_unit
create test/unit/helpers/greetings_helper_test.rb
invoke assets
- create app/assets/javascripts/greetings.js
- invoke css
- create app/assets/stylesheets/greetings.css
-
+ invoke coffee
+ create app/assets/javascripts/greetings.js.coffee
+ invoke scss
+ create app/assets/stylesheets/greetings.css.scss
</shell>
What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a javascript file and a stylesheet file.
@@ -193,21 +194,19 @@ Rails comes with a generator for data models too.
<shell>
$ rails generate model
-Usage: rails generate model NAME [field:type field:type] [options]
+Usage:
+ rails generate model NAME [field[:type][:index] field[:type][:index]] [options]
...
-Examples:
- rails generate model account
-
- Model: app/models/account.rb
- Test: test/unit/account_test.rb
- Fixtures: test/fixtures/accounts.yml
- Migration: db/migrate/XXX_add_accounts.rb
+ActiveRecord options:
+ [--migration] # Indicates when to generate migration
+ # Default: true
- rails generate model post title:string body:text published:boolean
+...
- Creates a Post model with a string title, text body, and published flag.
+Description:
+ Create rails files for model generator.
</shell>
NOTE: For a list of available field types, refer to the "API documentation":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html#method-i-column for the column method for the +TableDefinition+ class.
@@ -218,46 +217,47 @@ We will set up a simple resource called "HighScore" that will keep track of our
<shell>
$ rails generate scaffold HighScore game:string score:integer
- exists app/models/
- exists app/controllers/
- exists app/helpers/
- create app/views/high_scores
- create app/views/layouts/
- exists test/functional/
- create test/unit/
- create app/assets/stylesheets/
- create app/views/high_scores/index.html.erb
- create app/views/high_scores/show.html.erb
- create app/views/high_scores/new.html.erb
- create app/views/high_scores/edit.html.erb
- create app/views/layouts/high_scores.html.erb
- create app/assets/stylesheets/scaffold.css.scss
- create app/controllers/high_scores_controller.rb
- create test/functional/high_scores_controller_test.rb
- create app/helpers/high_scores_helper.rb
- route resources :high_scores
-dependency model
- exists app/models/
- exists test/unit/
- create test/fixtures/
+ invoke active_record
+ create db/migrate/20120528060026_create_high_scores.rb
create app/models/high_score.rb
- create test/unit/high_score_test.rb
- create test/fixtures/high_scores.yml
- exists db/migrate
- create db/migrate/20100209025147_create_high_scores.rb
+ invoke test_unit
+ create test/unit/high_score_test.rb
+ create test/fixtures/high_scores.yml
+ route resources :high_scores
+ invoke scaffold_controller
+ create app/controllers/high_scores_controller.rb
+ invoke erb
+ create app/views/high_scores
+ create app/views/high_scores/index.html.erb
+ create app/views/high_scores/edit.html.erb
+ create app/views/high_scores/show.html.erb
+ create app/views/high_scores/new.html.erb
+ create app/views/high_scores/_form.html.erb
+ invoke test_unit
+ create test/functional/high_scores_controller_test.rb
+ invoke helper
+ create app/helpers/high_scores_helper.rb
+ invoke test_unit
+ create test/unit/helpers/high_scores_helper_test.rb
+ invoke assets
+ invoke coffee
+ create app/assets/javascripts/high_scores.js.coffee
+ invoke scss
+ create app/assets/stylesheets/high_scores.css.scss
+ invoke scss
+ create app/assets/stylesheets/scaffolds.css.scss
</shell>
The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the +high_scores+ table and fields), takes care of the route for the *resource*, and new tests for everything.
-The migration requires that we *migrate*, that is, run some Ruby code (living in that +20100209025147_create_high_scores.rb+) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the +rake db:migrate+ command. We'll talk more about Rake in-depth in a little while.
+The migration requires that we *migrate*, that is, run some Ruby code (living in that +20120528060026_create_high_scores.rb+) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the +rake db:migrate+ command. We'll talk more about Rake in-depth in a little while.
<shell>
$ rake db:migrate
-(in /home/foobar/commandsapp)
== CreateHighScores: migrating ===============================================
-- create_table(:high_scores)
- -> 0.0026s
-== CreateHighScores: migrated (0.0028s) ======================================
+ -> 0.0017s
+== CreateHighScores: migrated (0.0019s) ======================================
</shell>
INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions about code. In unit testing, we take a little part of code, say a method of a model, and test its inputs and outputs. Unit tests are your friend. The sooner you make peace with the fact that your quality of life will drastically increase when you unit test your code, the better. Seriously. We'll make one in a moment.
@@ -274,19 +274,19 @@ h4. +rails console+
The +console+ command lets you interact with your Rails application from the command line. On the underside, +rails console+ uses IRB, so if you've ever used it, you'll be right at home. This is useful for testing out quick ideas with code and changing data server-side without touching the website.
-You can also use the alias "c" to invoke the console: <tt>rails c</tt>.
+INFO: You can also use the alias "c" to invoke the console: <tt>rails c</tt>.
-You can specify the environment in which the +console+ command should operate using the +-e+ switch.
+You can specify the environment in which the +console+ command should operate.
<shell>
-$ rails console -e staging
+$ rails console staging
</shell>
If you wish to test out some code without changing any data, you can do that by invoking +rails console --sandbox+.
<shell>
$ rails console --sandbox
-Loading development environment in sandbox (Rails 3.1.0)
+Loading development environment in sandbox (Rails 3.2.3)
Any modifications you make will be rolled back on exit
irb(main):001:0>
</shell>
@@ -295,7 +295,7 @@ h4. +rails dbconsole+
+rails dbconsole+ figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3.
-You can also use the alias "db" to invoke the dbconsole: <tt>rails db</tt>.
+INFO: You can also use the alias "db" to invoke the dbconsole: <tt>rails db</tt>.
h4. +rails runner+
@@ -305,7 +305,7 @@ h4. +rails runner+
$ rails runner "Model.long_running_method"
</shell>
-You can also use the alias "r" to invoke the runner: <tt>rails r</tt>.
+INFO: You can also use the alias "r" to invoke the runner: <tt>rails r</tt>.
You can specify the environment in which the +runner+ command should operate using the +-e+ switch.
@@ -317,31 +317,25 @@ h4. +rails destroy+
Think of +destroy+ as the opposite of +generate+. It'll figure out what generate did, and undo it.
-You can also use the alias "d" to invoke the destroy command: <tt>rails d</tt>.
+INFO: You can also use the alias "d" to invoke the destroy command: <tt>rails d</tt>.
<shell>
$ rails generate model Oops
- exists app/models/
- exists test/unit/
- exists test/fixtures/
- create app/models/oops.rb
- create test/unit/oops_test.rb
- create test/fixtures/oops.yml
- exists db/migrate
- create db/migrate/20081221040817_create_oops.rb
+ invoke active_record
+ create db/migrate/20120528062523_create_oops.rb
+ create app/models/oops.rb
+ invoke test_unit
+ create test/unit/oops_test.rb
+ create test/fixtures/oops.yml
+</shell>
+<shell>
$ rails destroy model Oops
- notempty db/migrate
- notempty db
- rm db/migrate/20081221040817_create_oops.rb
- rm test/fixtures/oops.yml
- rm test/unit/oops_test.rb
- rm app/models/oops.rb
- notempty test/fixtures
- notempty test
- notempty test/unit
- notempty test
- notempty app/models
- notempty app
+ invoke active_record
+ remove db/migrate/20120528062523_create_oops.rb
+ remove app/models/oops.rb
+ invoke test_unit
+ remove test/unit/oops_test.rb
+ remove test/fixtures/oops.yml
</shell>
h3. Rake
@@ -352,16 +346,16 @@ You can get a list of Rake tasks available to you, which will often depend on yo
<shell>
$ rake --tasks
-(in /home/foobar/commandsapp)
-rake db:abort_if_pending_migrations # Raises an error if there are pending migrations
-rake db:charset # Retrieves the charset for the current environment's database
-rake db:collation # Retrieves the collation for the current environment's database
-rake db:create # Create the database defined in config/database.yml for the current Rails.env
+rake about # List versions of all Rails frameworks and the environment
+rake assets:clean # Remove compiled assets
+rake assets:precompile # Compile all the assets named in config.assets.precompile
+rake db:create # Create the database from config/database.yml for the current Rails.env
...
+rake log:clear # Truncates all *.log files in log/ to zero bytes
+rake middleware # Prints out your Rack middleware stack
...
-rake tmp:pids:clear # Clears all files in tmp/pids
-rake tmp:sessions:clear # Clears all files in tmp/sessions
-rake tmp:sockets:clear # Clears all files in tmp/sockets
+rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear)
+rake tmp:create # Creates tmp directories for sessions, cache, sockets, and pids
</shell>
h4. +about+
diff --git a/guides/source/contributing_to_ruby_on_rails.textile b/guides/source/contributing_to_ruby_on_rails.textile
index 72cdea885f..acf75d41cd 100644
--- a/guides/source/contributing_to_ruby_on_rails.textile
+++ b/guides/source/contributing_to_ruby_on_rails.textile
@@ -412,6 +412,42 @@ Push to your remote:
$ git push mine my_new_branch
</shell>
+You might have cloned your forked repository into your machine and might want to add the original Rails repository as a remote instead, if that's the case here's what you have to do.
+
+In the directory you cloned your fork:
+
+<shell>
+$ git remote add rails git://github.com/rails/rails.git
+</shell>
+
+Download new commits and branches from the official repository:
+
+<shell>
+$ git fetch rails
+</shell>
+
+Merge the new content:
+
+<shell>
+$ git checkout master
+$ git rebase rails/master
+</shell>
+
+Update your fork:
+
+<shell>
+$ git push origin master
+</shell>
+
+If you want to update another branches:
+
+<shell>
+$ git checkout branch_name
+$ git rebase rails/branch_name
+$ git push origin branch_name
+</shell>
+
+
h4. Issue a Pull Request
Navigate to the Rails repository you just pushed to (e.g. https://github.com/your-user-name/rails) and press "Pull Request" in the upper right hand corner.
diff --git a/guides/source/engines.textile b/guides/source/engines.textile
index c35305a822..86e7254201 100644
--- a/guides/source/engines.textile
+++ b/guides/source/engines.textile
@@ -36,6 +36,12 @@ To generate an engine with Rails 3.1, you will need to run the plugin generator
$ rails plugin new blorgh --full --mountable
</shell>
+The full list of options for the plugin generator may be seen by typing:
+
+<shell>
+$ rails plugin --help
+</shell>
+
The +--full+ option tells the plugin generator that you want to create an engine (which is a mountable plugin, hence the option name), creating the basic directory structure of an engine by providing things such as the foundations of an +app+ folder, as well a +config/routes.rb+ file. This generator also provides a file at +lib/blorgh/engine.rb+ which is identical in function to an application's +config/application.rb+ file.
The +--mountable+ option tells the generator to mount the engine inside the dummy testing application located at +test/dummy+ inside the engine. It does this by placing this line in to the dummy application's +config/routes.rb+ file, located at +test/dummy/config/routes.rb+ inside the engine:
diff --git a/guides/source/getting_started.textile b/guides/source/getting_started.textile
index c129aeb2e1..f25e0c0200 100644
--- a/guides/source/getting_started.textile
+++ b/guides/source/getting_started.textile
@@ -75,7 +75,7 @@ By following along with this guide, you'll create a Rails project called
(very) simple weblog. Before you can start building the application, you need to
make sure that you have Rails itself installed.
-TIP: The examples below use # and $ to denote terminal prompts. If you are using Windows, your prompt will look something like c:\source_code>
+TIP: The examples below use # and $ to denote superuser and regular user terminal prompts respectively in a UNIX-like OS. If you are using Windows, your prompt will look something like c:\source_code>
h4. Installing Rails
@@ -108,7 +108,7 @@ To use this generator, open a terminal, navigate to a directory where you have r
$ rails new blog
</shell>
-This will create a Rails application called Blog in a directory called blog.
+This will create a Rails application called Blog in a directory called blog and install the gem dependencies that are already mentioned in +Gemfile+ using +bundle install+.
TIP: You can see all of the command line options that the Rails
application builder accepts by running +rails new -h+.
@@ -138,7 +138,7 @@ application. Most of the work in this tutorial will happen in the +app/+ folder,
|README.rdoc|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.|
|script/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.|
|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html|
-|tmp/|Temporary files|
+|tmp/|Temporary files (like cache, pid and session files)|
|vendor/|A place for all third-party code. In a typical Rails application, this includes Ruby Gems and the Rails source code (if you optionally install it into your project).|
h3. Hello, Rails!
@@ -177,7 +177,28 @@ To create a new controller, you will need to run the "controller" generator and
$ rails generate controller welcome index
</shell>
-Rails will create several files for you. Most important of these are of course the controller, located at +app/controllers/welcome_controller.rb+ and the view, located at +app/views/welcome/index.html.erb+.
+Rails will create several files and a route for you.
+
+<shell>
+create app/controllers/welcome_controller.rb
+ route get "welcome/index"
+invoke erb
+create app/views/welcome
+create app/views/welcome/index.html.erb
+invoke test_unit
+create test/functional/welcome_controller_test.rb
+invoke helper
+create app/helpers/welcome_helper.rb
+invoke test_unit
+create test/unit/helpers/welcome_helper_test.rb
+invoke assets
+invoke coffee
+create app/assets/javascripts/welcome.js.coffee
+invoke scss
+create app/assets/stylesheets/welcome.css.scss
+</shell>
+
+Most important of these are of course the controller, located at +app/controllers/welcome_controller.rb+ and the view, located at +app/views/welcome/index.html.erb+.
Open the +app/views/welcome/index.html.erb+ file in your text editor and edit it to contain a single line of code:
@@ -195,18 +216,27 @@ You need to do this because Rails will serve any static file in the +public+ dir
Next, you have to tell Rails where your actual home page is located.
-Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with +root :to+ and uncomment it. It should look something like the following:
+Open the file +config/routes.rb+ in your editor.
<ruby>
Blog::Application.routes.draw do
-
- #...
+ get "welcome/index"
+
+ # The priority is based upon order of creation:
+ # first created -> highest priority.
+ # ...
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
- root :to => "welcome#index"
+ # root :to => "welcome#index"
+</ruby>
+
+This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with +root :to+ and uncomment it. It should look something like the following:
+
+<ruby>
+root :to => "welcome#index"
</ruby>
-The +root :to => "welcome#index"+ tells Rails to map requests to the root of the application to the welcome controller's index action. This was created earlier when you ran the controller generator (+rails generate controller welcome index+).
+The +root :to => "welcome#index"+ tells Rails to map requests to the root of the application to the welcome controller's index action and +get "welcome/index"+ tells Rails to map requests to "http://localhost:3000/welcome/index":http://localhost:3000/welcome/index to the welcome controller's index action. This was created earlier when you ran the controller generator (+rails generate controller welcome index+).
If you navigate to "http://localhost:3000":http://localhost:3000 in your browser, you'll see the +Hello, Rails!+ message you put into +app/views/welcome/index.html.erb+, indicating that this new route is indeed going to +WelcomeController+'s +index+ action and is rendering the view correctly.
@@ -500,7 +530,7 @@ database columns. In the first line we do just that (remember that
+params[:post]+ contains the attributes we're interested in). Then,
+@post.save+ is responsible for saving the model in the database.
Finally, we redirect the user to the +show+ action,
-wich we'll define later.
+which we'll define later.
TIP: As we'll see later, +@post.save+ returns a boolean indicating
wherever the model was saved or not.
@@ -610,7 +640,7 @@ The +link_to+ method is one of Rails' built-in view helpers. It creates a
hyperlink based on text to display and where to go - in this case, to the path
for posts.
-Let's add links to the other views as well, starting with adding this "New Post" link to +app/views/posts/index.html.erb+, placing it above the +<table>+ tag:
+Let's add links to the other views as well, starting with adding this "New Post" link to +app/views/posts/index.html.erb+, placing it above the +&lt;table&gt;+ tag:
<erb>
<%= link_to 'New post', :action => :new %>
@@ -1129,7 +1159,7 @@ together.
Here we're using +link_to+ in a different way. We wrap the
+:action+ and +:id+ attributes in a hash so that we can pass those two keys in
first as one argument, and then the final two keys as another argument. The +:method+ and +:confirm+
-options are used as html5 attributes so that when the click is linked,
+options are used as HTML5 attributes so that when the link is clicked,
Rails will first show a confirm dialog to the user, and then submit the
link with method +delete+. This is done via the JavaScript file +jquery_ujs+
which is automatically included into your application's layout
diff --git a/guides/source/initialization.textile b/guides/source/initialization.textile
index 12b2eb7458..48d4373afe 100644
--- a/guides/source/initialization.textile
+++ b/guides/source/initialization.textile
@@ -57,7 +57,7 @@ else
end
</ruby>
-The +rbconfig+ file from the Ruby standard library provides us with the +RbConfig+ class which contains detailed information about the Ruby environment, including how Ruby was compiled. We can see thisin use in +railties/lib/rails/script_rails_loader+.
+The +rbconfig+ file from the Ruby standard library provides us with the +RbConfig+ class which contains detailed information about the Ruby environment, including how Ruby was compiled. We can see this in use in +railties/lib/rails/script_rails_loader+.
<ruby>
require 'pathname'
@@ -157,11 +157,11 @@ The gems that a Rails 4 application depends on are as follows:
TODO: change these when the Rails 4 release is near.
* abstract (1.0.0)
-* actionmailer (3.1.0.beta)
-* actionpack (3.1.0.beta)
-* activemodel (3.1.0.beta)
-* activerecord (3.1.0.beta)
-* activesupport (3.1.0.beta)
+* actionmailer (4.0.0.beta)
+* actionpack (4.0.0.beta)
+* activemodel (4.0.0.beta)
+* activerecord (4.0.0.beta)
+* activesupport (4.0.0.beta)
* arel (2.0.7)
* builder (3.0.0)
* bundler (1.0.6)
@@ -174,8 +174,8 @@ TODO: change these when the Rails 4 release is near.
* rack-cache (0.5.3)
* rack-mount (0.6.13)
* rack-test (0.5.6)
-* rails (3.1.0.beta)
-* railties (3.1.0.beta)
+* rails (4.0.0.beta)
+* railties (4.0.0.beta)
* rake (0.8.7)
* sqlite3-ruby (1.3.2)
* thor (0.14.6)
@@ -191,6 +191,7 @@ ARGV << '--help' if ARGV.empty?
aliases = {
"g" => "generate",
+ "d" => "destroy",
"c" => "console",
"s" => "server",
"db" => "dbconsole",
@@ -531,6 +532,7 @@ require "rails"
action_controller
action_mailer
rails/test_unit
+ sprockets/rails
).each do |framework|
begin
require "#{framework}/railtie"
@@ -543,13 +545,19 @@ First off the line is the +rails+ require itself.
h4. +railties/lib/rails.rb+
-This file is responsible for the initial definition of the +Rails+ module and, rather than defining the autoloads like +ActiveSupport+, +ActionDispatch+ and so on, it actually defines other functionality. Such as the +root+, +env+ and +application+ methods which are extremely useful in Rails 3 applications.
+This file is responsible for the initial definition of the +Rails+
+module and, rather than defining the autoloads like +ActiveSupport+,
++ActionDispatch+ and so on, it actually defines other functionality.
+Such as the +root+, +env+ and +application+ methods which are extremely
+useful in Rails 4 applications.
However, before all that takes place the +rails/ruby_version_check+ file is required first.
h4. +railties/lib/rails/ruby_version_check.rb+
-This file simply checks if the Ruby version is less than 1.8.7 or is 1.9.1 and raises an error if that is the case. Rails 3 simply will not run on earlier versions of Ruby than 1.8.7 or 1.9.1.
+This file simply checks if the Ruby version is less than 1.9.3 and
+raises an error if that is the case. Rails 4 simply will not run on
+earlier versions of Ruby.
NOTE: You should always endeavor to run the latest version of Ruby with your Rails applications. The benefits are many, including security fixes and the like, and very often there is a speed increase associated with it. The caveat is that you could have code that potentially breaks on the latest version, which should be fixed to work on the latest version rather than kept around as an excuse not to upgrade.
@@ -565,35 +573,28 @@ end
These methods can be used to silence STDERR responses and the +silence_stream+ allows you to also silence other streams. Additionally, this mixin allows you to suppress exceptions and capture streams. For more information see the "Silencing Warnings, Streams, and Exceptions":active_support_core_extensions.html#silencing-warnings-streams-and-exceptions section from the Active Support Core Extensions Guide.
-h4. +active_support/core_ext/logger.rb+
+h4. +active_support/core_ext/array/extract_options.rb+
-The next file that is required is another Active Support core extension, this time to the +Logger+ class. This begins by defining the +around_[level]+ helpers for the +Logger+ class as well as other methods such as a +datetime_format+ getter and setter for the +formatter+ object tied to a +Logger+ object.
-
-For more information see the "Extensions to Logger":active_support_core_extensions.html#extensions-to-logger section from the Active Support Core Extensions Guide.
+The next file that is required is another Active Support core extension,
+this time to the +Array+ and +Hash+ classes. This file defines an
++extract_options!+ method which Rails uses to extract options from
+parameters.
h4. +railties/lib/rails/application.rb+
-The next file required by +railties/lib/rails.rb+ is +application.rb+. This file defines the +Rails::Application+ constant which the application's class defined in +config/application.rb+ in a standard Rails application depends on. Before the +Rails::Application+ class is defined however, there's some other files that get required first.
-
-The first of these is +active_support/core_ext/hash/reverse_merge+ which can be "read about in the Active Support Core Extensions guide":active_support_core_extensions.html#merging under the "Merging" section.
-
-h4. +active_support/file_update_checker.rb+
-
-The +ActiveSupport::FileUpdateChecker+ class defined within this file is responsible for checking if a file has been updated since it was last checked. This is used for monitoring the routes file for changes during development environment runs.
+The next file required by +railties/lib/rails.rb+ is +application.rb+.
+This file defines the +Rails::Application+ constant which the
+application's class defined in +config/application.rb+ in a standard
+Rails application depends on.
-h4. +railties/lib/rails/plugin.rb+
-
-This file defines +Rails::Plugin+ which inherits from +Rails::Engine+. Unlike +Rails::Engine+ and +Rails::Railtie+ however, this class is not designed to be inherited from. Instead, this is used simply for loading plugins from within an application and an engine.
-
-This file begins by requiring +rails/engine.rb+
-
-h4. +railties/lib/rails/engine.rb+
+Before the +Rails::Application+ class is
+defined however, +rails/engine+ is also loaded, which is responsible for
+handling the behavior and definitions of Rails engines.
-The +rails/engine.rb+ file defines the +Rails::Engine+ class which inherits from +Rails::Railtie+. The +Rails::Engine+ class defines much of the functionality found within a standard application class such as the +routes+ and +config+ methods.
+TIP: You can read more about engines in the "Getting Started with Engines":engines.html guide.
-The "API documentation":http://api.rubyonrails.org/classes/Rails/Engine.html for +Rails::Engine+ explains the function of this class pretty well.
-
-This file's first line requires +rails/railtie.rb+.
+Among other things, Rails Engine is also responsible for loading the
+Railtie class.
h4. +railties/lib/rails/railtie.rb+
@@ -655,7 +656,7 @@ h4. +activesupport/lib/active_support/deprecation/proxy_wrappers.rb+
+proxy_wrappers.rb+ defines deprecation wrappers for methods, instance variables and constants. Previously, this was used for the +RAILS_ENV+ and +RAILS_ROOT+ constants for 3.0 but since then these constants have been removed. The deprecation message that would be raised from these would be something like:
<plain>
- BadConstant is deprecated! Use GoodConstant instead.
+BadConstant is deprecated! Use GoodConstant instead.
</plain>
h4. +active_support/ordered_options+
@@ -664,7 +665,30 @@ This file is the next file required from +rails/configuration.rb+ is the file th
The next file required is +active_support/core_ext/hash/deep_dup+ which is covered in "Active Support Core Extensions guide":active_support_core_extensions.html#deep_dup
-The file that is required next from is +rails/paths+
+h4. +active_support/core_ext/object+
+
+This file is responsible for requiring many more Active Support core extensions:
+
+<ruby>
+require 'active_support/core_ext/object/acts_like'
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/object/duplicable'
+require 'active_support/core_ext/object/deep_dup'
+require 'active_support/core_ext/object/try'
+require 'active_support/core_ext/object/inclusion'
+
+require 'active_support/core_ext/object/conversions'
+require 'active_support/core_ext/object/instance_variables'
+
+require 'active_support/core_ext/object/to_json'
+require 'active_support/core_ext/object/to_param'
+require 'active_support/core_ext/object/to_query'
+require 'active_support/core_ext/object/with_options'
+</ruby>
+
+The Rails API documentation covers them in great detail, so we're not going to explain each of them.
+
+The file that is required next from +rails/configuration+ is +rails/paths+.
h4. +railties/lib/rails/paths.rb+
@@ -680,7 +704,6 @@ module Rails
autoload :Debugger, "rails/rack/debugger"
autoload :Logger, "rails/rack/logger"
autoload :LogTailer, "rails/rack/log_tailer"
- autoload :Static, "rails/rack/static"
end
end
</ruby>
@@ -706,9 +729,23 @@ h4. +active_support/inflections+
This file references the +ActiveSupport::Inflector+ constant which isn't loaded by this point. But there were autoloads set up in +activesupport/lib/active_support.rb+ which will load the file which loads this constant and so then it will be defined. Then this file defines pluralization and singularization rules for words in Rails. This is how Rails knows how to pluralize "tomato" to "tomatoes".
+<ruby>
+inflect.irregular('zombie', 'zombies')
+</ruby>
+
h4. +activesupport/lib/active_support/inflector/transliterate.rb+
-In this file is where the "+transliterate+":http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate and +parameterize+:http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize methods are defined. The documentation for both of these methods is very much worth reading.
+This is the file that defines the "+transliterate+":http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate and "+parameterize+":http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize methods.
+
+h4. +active_support/core_ext/module/introspection+
+
+The next file loaded by +rails/railtie+ is the introspection core
+extension, which extends +Module+ with methods like +parent_name+, +parent+ and
++parents+.
+
+h4. +active_support/core_ext/module/delegation+
+
+The final file loaded by +rails/railtie+ is the delegation core extension, which defines the "+delegate+":http://api.rubyonrails.org/classes/Module.html#method-i-delegate method.
h4. Back to +railties/lib/rails/railtie.rb+
@@ -888,7 +925,7 @@ The +initializers_chain+ method referenced in the +initializers_for+ method is d
<ruby>
def initializers_chain
initializers = Collection.new
- ancestors.reverse_each do | klass |
+ ancestors.reverse_each do |klass|
next unless klass.respond_to?(:initializers)
initializers = initializers + klass.initializers
end
@@ -951,46 +988,35 @@ This file defines the +ActiveSupport::Railtie+ constant which like the +I18n::Ra
Then this Railtie sets up three more initializers:
-* +active_support.initialize_whiny_nils+
* +active_support.deprecation_behavior+
* +active_support.initialize_time_zone+
+* +active_support.set_configs+
We will cover what each of these initializers do when they run.
Once the +active_support/railtie+ file has finished loading the next file required from +railties/lib/rails.rb+ is the +action_dispatch/railtie+.
-h4. +activesupport/lib/action_dispatch/railtie.rb+
+h4. +actionpack/lib/action_dispatch/railtie.rb+
This file defines the +ActionDispatch::Railtie+ class, but not before requiring +action_dispatch+.
-h4. +activesupport/lib/action_dispatch.rb+
-
-This file attempts to locate the +active_support+ and +active_model+ libraries by looking a couple of directories back from the current file and then adds the +active_support+ and +active_model+ +lib+ directories to the load path, but only if they aren't already, which they are.
-
-<ruby>
-activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
-activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
-$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
-</ruby>
-
-In effect, these lines only define the +activesupport_path+ and +activemodel_path+ variables and nothing more.
+h4. +actionpack/lib/action_dispatch.rb+
-The next two requires in this file are already done, so they are not run:
+This file starts off with the following requires:
<ruby>
require 'active_support'
require 'active_support/dependencies/autoload'
+require 'active_support/core_ext/module/attribute_accessors'
</ruby>
-The following require is to +action_pack+ (+activesupport/lib/action_pack.rb+) which has a 22-line copyright notice at the top of it and ends in a simple require to +action_pack/version+. This file, like other +version.rb+ files before it, defines the +ActionPack::VERSION+ constant:
+The following require is to +action_pack+ (+actionpack/lib/action_pack.rb+) which contains a simple require to +action_pack/version+. This file, like other +version.rb+ files before it, defines the +ActionPack::VERSION+ constant:
<ruby>
module ActionPack
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 1
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
@@ -1008,8 +1034,8 @@ This file makes a require to +active_model/version+ which defines the version fo
<ruby>
module ActiveModel
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 1
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
@@ -1046,7 +1072,7 @@ Once it has finished loading, the +I18n.load_path+ method is used to add the +ac
The loading of this file finishes the loading of +active_model+ and so we go back to +action_dispatch+.
-h4. Back to +activesupport/lib/action_dispatch.rb+
+h4. Back to +actionpack/lib/action_dispatch.rb+
The remainder of this file requires the +rack+ file from the Rack gem which defines the +Rack+ module. After +rack+, there's autoloads defined for the +Rack+, +ActionDispatch+, +ActionDispatch::Http+, +ActionDispatch::Session+. A new method called +autoload_under+ is used here, and this simply prefixes the files where the modules are autoloaded from with the path specified. For example here:
@@ -1060,7 +1086,7 @@ The +Assertions+ module is in the +action_dispatch/testing+ folder rather than s
Finally, this file defines a top-level autoload, the +Mime+ constant.
-h4. Back to +activesupport/lib/action_dispatch/railtie.rb+
+h4. Back to +actionpack/lib/action_dispatch/railtie.rb+
After +action_dispatch+ is required in this file, the +ActionDispatch::Railtie+ class is defined and is yet another class that inherits from +Rails::Railtie+. This class defines some initial configuration option defaults for +config.action_dispatch+ before setting up a single initializer called +action_dispatch.configure+.
@@ -1082,22 +1108,21 @@ h4. +activerecord/lib/active_record.rb+
This file begins by detecting if the +lib+ directories of +active_support+ and +active_model+ are not in the load path and if they aren't then adds them. As we saw back in +action_dispatch.rb+, these directories are already there.
-The first three requires have already been done by other files and so aren't loaded here, but the 4th require, the one to +arel+ will require the file provided by the Arel gem, which defines the +Arel+ module.
+The first couple of requires have already been done by other files and so aren't loaded here, but the next one to +arel+ will require the file provided by the Arel gem, which defines the +Arel+ module.
<ruby>
require 'active_support'
-require 'active_support/i18n'
require 'active_model'
require 'arel'
</ruby>
-The 5th require in this file is one to +active_record/version+ which defines the +ActiveRecord::VERSION+ constant:
+The file required next is +active_record/version+ which defines the +ActiveRecord::VERSION+ constant:
<ruby>
module ActiveRecord
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 1
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
@@ -1121,7 +1146,9 @@ This will set the engine for +Arel::Table+ to be +ActiveRecord::Base+.
The file then finishes with this line:
<ruby>
-I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
+ActiveSupport.on_load(:i18n) do
+ I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
+end
</ruby>
This will add the translations from +activerecord/lib/active_record/locale/en.yml+ to the load path for +I18n+, with this file being parsed when all the translations are loaded.
diff --git a/guides/source/migrations.textile b/guides/source/migrations.textile
index 52dba76e68..342b5a4d57 100644
--- a/guides/source/migrations.textile
+++ b/guides/source/migrations.textile
@@ -8,8 +8,7 @@ production machines next time you deploy.
Active Record tracks which migrations have already been run so all you have to
do is update your source and run +rake db:migrate+. Active Record will work out
-which migrations should be run. It will also update your +db/schema.rb+ file to
-match the structure of your database.
+which migrations should be run. Active Record will also update your +db/schema.rb+ file to match the up-to-date structure of your database.
Migrations also allow you to describe these transformations using Ruby. The
great thing about this is that (like most of Active Record's functionality) it
diff --git a/guides/source/rails_on_rack.textile b/guides/source/rails_on_rack.textile
index d8910cf1d0..3a7c392508 100644
--- a/guides/source/rails_on_rack.textile
+++ b/guides/source/rails_on_rack.textile
@@ -23,29 +23,49 @@ h3. Rails on Rack
h4. Rails Application's Rack Object
-<tt>ActionController::Dispatcher.new</tt> is the primary Rack application object of a Rails application. Any Rack compliant web server should be using +ActionController::Dispatcher.new+ object to serve a Rails application.
+<tt>ApplicationName::Application</tt> is the primary Rack application object of a Rails application. Any Rack compliant web server should be using +ApplicationName::Application+ object to serve a Rails application.
h4. +rails server+
-<tt>rails server</tt> does the basic job of creating a +Rack::Builder+ object and starting the webserver. This is Rails' equivalent of Rack's +rackup+ script.
+<tt>rails server</tt> does the basic job of creating a +Rack::Server+ object and starting the webserver.
-Here's how +rails server+ creates an instance of +Rack::Builder+
+Here's how +rails server+ creates an instance of +Rack::Server+
<ruby>
-app = Rack::Builder.new {
- use Rails::Rack::LogTailer unless options[:detach]
- use Rails::Rack::Debugger if options[:debugger]
- use ActionDispatch::Static
- run ActionController::Dispatcher.new
-}.to_app
+Rails::Server.new.tap { |server|
+ require APP_PATH
+ Dir.chdir(Rails.application.root)
+ server.start
+}
</ruby>
-Middlewares used in the code above are primarily useful only in the development environment. The following table explains their usage:
+The +Rails::Server+ inherits from +Rack::Server+ and calls the +Rack::Server#start+ method this way:
+
+<ruby>
+class Server < ::Rack::Server
+ def start
+ ...
+ super
+ end
+end
+</ruby>
+
+Here's how it loads the middlewares:
+
+<ruby>
+def middleware
+ middlewares = []
+ middlewares << [Rails::Rack::Debugger] if options[:debugger]
+ middlewares << [::Rack::ContentLength]
+ Hash.new(middlewares)
+end
+</ruby>
+
++Rails::Rack::Debugger+ is primarily useful only in the development environment. The following table explains the usage of the loaded middlewares:
|_.Middleware|_.Purpose|
-|+Rails::Rack::LogTailer+|Appends log file output to console|
-|+ActionDispatch::Static+|Serves static files inside +Rails.root/public+ directory|
|+Rails::Rack::Debugger+|Starts Debugger|
+|+Rack::ContentLength+|Counts the number of bytes in the response and set the HTTP Content-Length header|
h4. +rackup+
@@ -55,9 +75,9 @@ To use +rackup+ instead of Rails' +rails server+, you can put the following insi
# Rails.root/config.ru
require "config/environment"
-use Rails::Rack::LogTailer
-use ActionDispatch::Static
-run ActionController::Dispatcher.new
+use Rack::Debugger
+use Rack::ContentLength
+run ApplicationName::Application
</ruby>
And start the server:
@@ -72,11 +92,11 @@ To find out more about different +rackup+ options:
$ rackup --help
</shell>
-h3. Action Controller Middleware Stack
+h3. Action Dispatcher Middleware Stack
-Many of Action Controller's internal components are implemented as Rack middlewares. +ActionController::Dispatcher+ uses +ActionController::MiddlewareStack+ to combine various internal and external middlewares to form a complete Rails Rack application.
+Many of Action Dispatchers's internal components are implemented as Rack middlewares. +Rails::Application+ uses +ActionDispatch::MiddlewareStack+ to combine various internal and external middlewares to form a complete Rails Rack application.
-NOTE: +ActionController::MiddlewareStack+ is Rails' equivalent of +Rack::Builder+, but built for better flexibility and more features to meet Rails' requirements.
+NOTE: +ActionDispatch::MiddlewareStack+ is Rails' equivalent of +Rack::Builder+, but built for better flexibility and more features to meet Rails' requirements.
h4. Inspecting Middleware Stack
@@ -111,7 +131,7 @@ use ActionDispatch::Head
use Rack::ConditionalGet
use Rack::ETag
use ActionDispatch::BestStandardsSupport
-run Blog::Application.routes
+run ApplicationName::Application.routes
</ruby>
Purpose of each of this middlewares is explained in the "Internal Middlewares":#internal-middleware-stack section.
@@ -172,7 +192,7 @@ use ActionDispatch::Static
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8>
use Rack::Runtime
...
-run Myapp::Application.routes
+run Blog::Application.routes
</shell>
h4. Internal Middleware Stack
@@ -264,7 +284,7 @@ config.middleware.clear
<ruby>
# config.ru
use MyOwnStackFromScratch
-run ActionController::Dispatcher.new
+run ApplicationName::Application
</ruby>
h3. Resources
diff --git a/guides/source/routing.textile b/guides/source/routing.textile
index 4a50edbb15..0773a96c67 100644
--- a/guides/source/routing.textile
+++ b/guides/source/routing.textile
@@ -395,7 +395,7 @@ NOTE: You can't use +:namespace+ or +:module+ with a +:controller+ path segment.
get ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/
</ruby>
-TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment add a constraint which overrides this - for example +:id+ => /[^\/]+/ allows anything except a slash.
+TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment, add a constraint that overrides this – for example, +:id+ => /[^\/]+/ allows anything except a slash.
h4. Static Segments
@@ -445,6 +445,14 @@ get 'exit' => 'sessions#destroy', :as => :logout
This will create +logout_path+ and +logout_url+ as named helpers in your application. Calling +logout_path+ will return +/exit+
+You can also use this to override routing methods defined by resources, like this:
+
+<ruby>
+get ':username', :to => "users#show", :as => :user
+</ruby>
+
+This will define a +user_path+ method that will be available in controllers, helpers and views that will go to a route such as +/bob+. Inside the +show+ action of +UsersController+, +params[:username]+ will contain the username for the user. Change +:username+ in the route definition if you do not want your parameter name to be +:username+.
+
h4. HTTP Verb Constraints
In general, you should use the +get+, +post+, +put+ and +delete+ methods to constrain a route to a particular verb. You can use the +match+ method with the +:via+ option to match multiple verbs at once:
diff --git a/guides/source/ruby_on_rails_guides_guidelines.textile b/guides/source/ruby_on_rails_guides_guidelines.textile
index f3e934d38c..dd209b61d6 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.textile
+++ b/guides/source/ruby_on_rails_guides_guidelines.textile
@@ -47,7 +47,13 @@ h4. Generation
To generate all the guides, just +cd+ into the *+guides+* directory and execute:
<plain>
-bundle exec rake generate_guides
+bundle exec rake guides:generate
+</plain>
+
+or
+
+<plain>
+bundle exec rake guides:generate:html
</plain>
(You may need to run +bundle install+ first to install the required gems.)
@@ -56,7 +62,7 @@ To process +my_guide.textile+ and nothing else use the +ONLY+ environment variab
<plain>
touch my_guide.textile
-bundle exec rake generate_guides ONLY=my_guide
+bundle exec rake guides:generate ONLY=my_guide
</plain>
By default, guides that have not been modified are not processed, so +ONLY+ is rarely needed in practice.
@@ -68,7 +74,13 @@ It is also recommended that you work with +WARNINGS=1+. This detects duplicate I
If you want to generate guides in a language other than English, you can keep them in a separate directory under +source+ (eg. <tt>source/es</tt>) and use the +GUIDES_LANGUAGE+ environment variable:
<plain>
-bundle exec rake generate_guides GUIDES_LANGUAGE=es
+bundle exec rake guides:generate GUIDES_LANGUAGE=es
+</plain>
+
+If you want to see all the environment variables you can use to configure the generation script just run:
+
+<plain>
+rake
</plain>
h4. Validation
@@ -76,7 +88,7 @@ h4. Validation
Please validate the generated HTML with:
<plain>
-bundle exec rake validate_guides
+bundle exec rake guides:validate
</plain>
Particularly, titles get an ID generated from their content and this often leads to duplicates. Please set +WARNINGS=1+ when generating guides to detect them. The warning messages suggest a solution.
@@ -85,8 +97,8 @@ h3. Kindle Guides
h4(#generation-kindle). Generation
-To generate guides for the Kindle, you need to provide +KINDLE=1+ as an environment variable:
+To generate guides for the Kindle, use the following rake task:
<plain>
-KINDLE=1 bundle exec rake generate_guides
+bundle exec rake guides:generate:kindle
</plain>
diff --git a/guides/source/security.textile b/guides/source/security.textile
index ac55d60368..0931dd6393 100644
--- a/guides/source/security.textile
+++ b/guides/source/security.textile
@@ -30,7 +30,7 @@ A good place to start looking at security is with sessions, which can be vulnera
h4. What are Sessions?
--- _HTTP is a stateless protocol. Sessions make it stateful._
+NOTE: _HTTP is a stateless protocol. Sessions make it stateful._
Most applications need to keep track of certain state of a particular user. This could be the contents of a shopping basket or the user id of the currently logged in user. Without the idea of sessions, the user would have to identify, and probably authenticate, on every request.
Rails will create a new session automatically if a new user accesses the application. It will load an existing session if the user has already used the application.
@@ -44,13 +44,13 @@ User.find(session[:user_id])
h4. Session id
--- _The session id is a 32 byte long MD5 hash value._
+NOTE: _The session id is a 32 byte long MD5 hash value._
A session id consists of the hash value of a random string. The random string is the current time, a random number between 0 and 1, the process id number of the Ruby interpreter (also basically a random number) and a constant string. Currently it is not feasible to brute-force Rails' session ids. To date MD5 is uncompromised, but there have been collisions, so it is theoretically possible to create another input text with the same hash value. But this has had no security impact to date.
h4. Session Hijacking
--- _Stealing a user's session id lets an attacker use the web application in the victim's name._
+WARNING: _Stealing a user's session id lets an attacker use the web application in the victim's name._
Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session id in the cookie identifies the session.
@@ -72,7 +72,7 @@ The main objective of most attackers is to make money. The underground prices fo
h4. Session Guidelines
--- _Here are some general guidelines on sessions._
+Here are some general guidelines on sessions.
* _(highlight)Do not store large objects in a session_. Instead you should store them in the database and save their id in the session. This will eliminate synchronization headaches and it won't fill up your session storage space (depending on what session storage you chose, see below).
This will also be a good idea, if you modify the structure of an object and old versions of it are still in some user's cookies. With server-side session storages you can clear out the sessions, but with client-side storages, this is hard to mitigate.
@@ -81,7 +81,7 @@ This will also be a good idea, if you modify the structure of an object and old
h4. Session Storage
--- _Rails provides several storage mechanisms for the session hashes. The most important are ActiveRecord::SessionStore and ActionDispatch::Session::CookieStore._
+NOTE: _Rails provides several storage mechanisms for the session hashes. The most important are +ActiveRecord::SessionStore+ and +ActionDispatch::Session::CookieStore+._
There are a number of session storages, i.e. where Rails saves the session hash and session id. Most real-live applications choose ActiveRecord::SessionStore (or one of its derivatives) over file storage due to performance and maintenance reasons. ActiveRecord::SessionStore keeps the session id and hash in a database table and saves and retrieves the hash on every request.
@@ -104,7 +104,7 @@ There are, however, derivatives of CookieStore which encrypt the session hash, s
h4. Replay Attacks for CookieStore Sessions
--- _Another sort of attack you have to be aware of when using CookieStore is the replay attack._
+TIP: _Another sort of attack you have to be aware of when using +CookieStore+ is the replay attack._
It works like this:
@@ -120,7 +120,7 @@ The best _(highlight)solution against it is not to store this kind of data in a
h4. Session Fixation
--- _Apart from stealing a user's session id, the attacker may fix a session id known to him. This is called session fixation._
+NOTE: _Apart from stealing a user's session id, the attacker may fix a session id known to him. This is called session fixation._
!images/session_fixation.png(Session fixation)!
@@ -135,7 +135,7 @@ This attack focuses on fixing a user's session id known to the attacker, and for
h4. Session Fixation – Countermeasures
--- _One line of code will protect you from session fixation._
+TIP: _One line of code will protect you from session fixation._
The most effective countermeasure is to _(highlight)issue a new session identifier_ and declare the old one invalid after a successful login. That way, an attacker cannot use the fixed session identifier. This is a good countermeasure against session hijacking, as well. Here is how to create a new session in Rails:
@@ -149,7 +149,7 @@ Another countermeasure is to _(highlight)save user-specific properties in the se
h4. Session Expiry
--- _Sessions that never expire extend the time-frame for attacks such as cross-site reference forgery (CSRF), session hijacking and session fixation._
+NOTE: _Sessions that never expire extend the time-frame for attacks such as cross-site reference forgery (CSRF), session hijacking and session fixation._
One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _(highlight)expire sessions in a database table_. Call +Session.sweep("20 minutes")+ to expire sessions that were used longer than 20 minutes ago.
@@ -174,7 +174,7 @@ delete_all "updated_at < '#{time.ago.to_s(:db)}' OR
h3. Cross-Site Request Forgery (CSRF)
--- _This attack method works by including malicious code or a link in a page that accesses a web application that the user is believed to have authenticated. If the session for that web application has not timed out, an attacker may execute unauthorized commands._
+This attack method works by including malicious code or a link in a page that accesses a web application that the user is believed to have authenticated. If the session for that web application has not timed out, an attacker may execute unauthorized commands.
!images/csrf.png!
@@ -193,7 +193,7 @@ CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) -- less t
h4. CSRF Countermeasures
--- _First, as is required by the W3C, use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF._
+NOTE: _First, as is required by the W3C, use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF._
The HTTP protocol basically provides two main types of requests - GET and POST (and more, but they are not supported by most browsers). The World Wide Web Consortium (W3C) provides a checklist for choosing HTTP GET or POST:
@@ -236,6 +236,17 @@ protect_from_forgery :secret => "123456789012345678901234567890..."
This will automatically include a security token, calculated from the current session and the server-side secret, in all forms and Ajax requests generated by Rails. You won't need the secret, if you use CookieStorage as session storage. If the security token doesn't match what was expected, the session will be reset. *Note:* In Rails versions prior to 3.0.4, this raised an <tt>ActionController::InvalidAuthenticityToken</tt> error.
+It is common to use persistent cookies to store user information, with +cookies.permanent+ for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself:
+
+<ruby>
+def handle_unverified_request
+ super
+ sign_out_user # Example method that will destroy the user cookies.
+end
+</ruby>
+
+The above method can be placed in the +ApplicationController+ and will be called when a CSRF token is not present on a non-GET request.
+
Note that _(highlight)cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so he can read the CSRF security token from a form or directly submit the form. Read <a href="#cross-site-scripting-xss">more about XSS</a> later.
h3. Redirection and Files
@@ -244,7 +255,7 @@ Another class of security vulnerabilities surrounds the use of redirection and f
h4. Redirection
--- _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, he may also create a self-contained attack._
+WARNING: _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, he may also create a self-contained attack._
Whenever the user is allowed to pass (parts of) the URL for redirection, it is possibly vulnerable. The most obvious attack would be to redirect users to a fake web application which looks and feels exactly as the original one. This so-called phishing attack works by sending an unsuspicious link in an email to the users, injecting the link by XSS in the web application or putting the link into an external site. It is unsuspicious, because the link starts with the URL to the web application and the URL to the malicious site is hidden in the redirection parameter: http://www.example.com/site/redirect?to= www.attacker.com. Here is an example of a legacy action:
@@ -272,7 +283,7 @@ This example is a Base64 encoded JavaScript which displays a simple message box.
h4. File Uploads
--- _Make sure file uploads don't overwrite important files, and process media files asynchronously._
+NOTE: _Make sure file uploads don't overwrite important files, and process media files asynchronously._
Many web applications allow users to upload files. _(highlight)File names, which the user may choose (partly), should always be filtered_ as an attacker could use a malicious file name to overwrite any file on the server. If you store file uploads at /var/www/uploads, and the user enters a file name like “../../../etc/passwd”, it may overwrite an important file. Of course, the Ruby interpreter would need the appropriate permissions to do so – one more reason to run web servers, database servers and other programs as a less privileged Unix user.
@@ -297,7 +308,7 @@ The solution to this is best to _(highlight)process media files asynchronously_:
h4. Executable Code in File Uploads
--- _Source code in uploaded files may be executed when placed in specific directories. Do not place file uploads in Rails' /public directory if it is Apache's home directory._
+WARNING: _Source code in uploaded files may be executed when placed in specific directories. Do not place file uploads in Rails' /public directory if it is Apache's home directory._
The popular Apache web server has an option called DocumentRoot. This is the home directory of the web site, everything in this directory tree will be served by the web server. If there are files with a certain file name extension, the code in it will be executed when requested (might require some options to be set). Examples for this are PHP and CGI files. Now think of a situation where an attacker uploads a file “file.cgi” with code in it, which will be executed when someone downloads the file.
@@ -305,7 +316,7 @@ _(highlight)If your Apache DocumentRoot points to Rails' /public directory, do n
h4. File Downloads
--- _Make sure users cannot download arbitrary files._
+NOTE: _Make sure users cannot download arbitrary files._
Just as you have to filter file names for uploads, you have to do so for downloads. The send_file() method sends files from the server to the client. If you use a file name, that the user entered, without filtering, any file can be downloaded:
@@ -327,7 +338,7 @@ Another (additional) approach is to store the file names in the database and nam
h3. Intranet and Admin Security
--- _Intranet and administration interfaces are popular attack targets, because they allow privileged access. Although this would require several extra-security measures, the opposite is the case in the real world._
+Intranet and administration interfaces are popular attack targets, because they allow privileged access. Although this would require several extra-security measures, the opposite is the case in the real world.
In 2007 there was the first tailor-made trojan which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.

@@ -359,7 +370,7 @@ The common admin interface works like this: it's located at www.example.com/admi
h3. Mass Assignment
--- _Without any precautions Model.new(params[:model]) allows attackers to set any database column's value._
+WARNING: _Without any precautions +Model.new(params[:model]+) allows attackers to set any database column's value._
The mass-assignment feature may become a problem, as it allows an attacker to set any model's attributes by manipulating the hash passed to a model's +new()+ method:
@@ -471,7 +482,7 @@ This will create an empty whitelist of attributes available for mass-assignment
h3. User Management
--- _Almost every web application has to deal with authorization and authentication. Instead of rolling your own, it is advisable to use common plug-ins. But keep them up-to-date, too. A few additional precautions can make your application even more secure._
+NOTE: _Almost every web application has to deal with authorization and authentication. Instead of rolling your own, it is advisable to use common plug-ins. But keep them up-to-date, too. A few additional precautions can make your application even more secure._
There are a number of authentication plug-ins for Rails available. Good ones, such as the popular "devise":https://github.com/plataformatec/devise and "authlogic":https://github.com/binarylogic/authlogic, store only encrypted passwords, not plain-text passwords. In Rails 3.1 you can use the built-in +has_secure_password+ method which has similar features.
@@ -498,7 +509,7 @@ And thus it found the first user in the database, returned it and logged him in.
h4. Brute-Forcing Accounts
--- _Brute-force attacks on accounts are trial and error attacks on the login credentials. Fend them off with more generic error messages and possibly require to enter a CAPTCHA._
+NOTE: _Brute-force attacks on accounts are trial and error attacks on the login credentials. Fend them off with more generic error messages and possibly require to enter a CAPTCHA._
A list of user names for your web application may be misused to brute-force the corresponding passwords, because most people don't use sophisticated passwords. Most passwords are a combination of dictionary words and possibly numbers. So armed with a list of user names and a dictionary, an automatic program may find the correct password in a matter of minutes.
@@ -510,7 +521,7 @@ In order to mitigate such attacks, _(highlight)display a generic error message o
h4. Account Hijacking
--- _Many web applications make it easy to hijack user accounts. Why not be different and make it more difficult?_
+Many web applications make it easy to hijack user accounts. Why not be different and make it more difficult?.
h5. Passwords
@@ -526,7 +537,7 @@ Depending on your web application, there may be more ways to hijack the user's a
h4. CAPTCHAs
--- _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that he is human, but reveal that a robot is a robot._
+INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that he is human, but reveal that a robot is a robot._
But not only spam robots (bots) are a problem, but also automatic login bots. A popular CAPTCHA API is "reCAPTCHA":http://recaptcha.net/ which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. "ReCAPTCHA":http://ambethia.com/recaptcha/ is also a Rails plug-in with the same name as the API.
@@ -553,7 +564,7 @@ Note that this protects you only from automatic bots, targeted tailor-made bots
h4. Logging
--- _Tell Rails not to put passwords in the log files._
+WARNING: _Tell Rails not to put passwords in the log files._
By default, Rails logs all requests being made to the web application. But log files can be a huge security issue, as they may contain login credentials, credit card numbers et cetera. When designing a web application security concept, you should also think about what will happen if an attacker got (full) access to the web server. Encrypting secrets and passwords in the database will be quite useless, if the log files list them in clear text. You can _(highlight)filter certain request parameters from your log files_ by appending them to <tt>config.filter_parameters</tt> in the application configuration. These parameters will be marked [FILTERED] in the log.
@@ -563,7 +574,7 @@ config.filter_parameters << :password
h4. Good Passwords
--- _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._
+INFO: _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._
Bruce Schneier, a security technologist, "has analyzed":http://www.schneier.com/blog/archives/2006/12/realworld_passw.html 34,000 real-world user names and passwords from the MySpace phishing attack mentioned <a href="#examples-from-the-underground">below</a>. It turns out that most of the passwords are quite easy to crack. The 20 most common passwords are:
@@ -575,7 +586,7 @@ A good password is a long alphanumeric combination of mixed cases. As this is qu
h4. Regular Expressions
--- _A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z._
+INFO: _A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z._
Ruby uses a slightly different approach than many other languages to match the end and the beginning of a string. That is why even many Ruby and Rails books make this wrong. So how is this a security threat? Imagine you have a File model and you validate the file name by a regular expression like this:
@@ -599,7 +610,7 @@ Whereas %0A is a line feed in URL encoding, so Rails automatically converts it t
h4. Privilege Escalation
--- _Changing a single parameter may give the user unauthorized access. Remember that every parameter may be changed, no matter how much you hide or obfuscate it._
+WARNING: _Changing a single parameter may give the user unauthorized access. Remember that every parameter may be changed, no matter how much you hide or obfuscate it._
The most common parameter that a user might tamper with, is the id parameter, as in +http://www.domain.com/project/1+, whereas 1 is the id. It will be available in params in the controller. There, you will most likely do something like this:
@@ -619,13 +630,13 @@ Don't be fooled by security by obfuscation and JavaScript security. The Web Deve
h3. Injection
--- _Injection is a class of attacks that introduce malicious code or parameters into a web application in order to run it within its security context. Prominent examples of injection are cross-site scripting (XSS) and SQL injection._
+INFO: _Injection is a class of attacks that introduce malicious code or parameters into a web application in order to run it within its security context. Prominent examples of injection are cross-site scripting (XSS) and SQL injection._
Injection is very tricky, because the same code or parameter can be malicious in one context, but totally harmless in another. A context can be a scripting, query or programming language, the shell or a Ruby/Rails method. The following sections will cover all important contexts where injection attacks may happen. The first section, however, covers an architectural decision in connection with Injection.
h4. Whitelists versus Blacklists
--- _When sanitizing, protecting or verifying something, whitelists over blacklists._
+NOTE: _When sanitizing, protecting or verifying something, whitelists over blacklists._
A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _(highlight)prefer to use whitelist approaches_:
@@ -640,7 +651,7 @@ Whitelists are also a good approach against the human factor of forgetting somet
h4. SQL Injection
--- _Thanks to clever methods, this is hardly a problem in most Rails applications. However, this is a very devastating and common attack in web applications, so it is important to understand the problem._
+INFO: _Thanks to clever methods, this is hardly a problem in most Rails applications. However, this is a very devastating and common attack in web applications, so it is important to understand the problem._
h5(#sql-injection-introduction). Introduction
@@ -719,7 +730,7 @@ The array or hash form is only available in model instances. You can try +saniti
h4. Cross-Site Scripting (XSS)
--- _The most widespread, and one of the most devastating security vulnerabilities in web applications is XSS. This malicious attack injects client-side executable code. Rails provides helper methods to fend these attacks off._
+INFO: _The most widespread, and one of the most devastating security vulnerabilities in web applications is XSS. This malicious attack injects client-side executable code. Rails provides helper methods to fend these attacks off._
h5. Entry Points
@@ -847,7 +858,7 @@ The MySpace Samy worm will be discussed in the CSS Injection section.
h4. CSS Injection
--- _CSS Injection is actually JavaScript injection, because some browsers (IE, some versions of Safari and others) allow JavaScript in CSS. Think twice about allowing custom CSS in your web application._
+INFO: _CSS Injection is actually JavaScript injection, because some browsers (IE, some versions of Safari and others) allow JavaScript in CSS. Think twice about allowing custom CSS in your web application._
CSS Injection is explained best by a well-known worm, the "MySpace Samy worm":http://namb.la/popular/tech.html. This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, but it creates too much traffic on MySpace, so that the site goes offline. The following is a technical explanation of the worm.
@@ -887,7 +898,7 @@ This example, again, showed that a blacklist filter is never complete. However,
h4. Textile Injection
--- _If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://redcloth.org/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS._
+If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://redcloth.org/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS.
For example, RedCloth translates +_test_+ to &lt;em&gt;test&lt;em&gt;, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS. Get the "all-new version 4":http://www.redcloth.org that removed serious bugs. However, even that version has "some security bugs":http://www.rorsecurity.info/journal/2008/10/13/new-redcloth-security.html, so the countermeasures still apply. Here is an example for version 3.0.4:
@@ -916,13 +927,13 @@ It is recommended to _(highlight)use RedCloth in combination with a whitelist in
h4. Ajax Injection
--- _The same security precautions have to be taken for Ajax actions as for “normal” ones. There is at least one exception, however: The output has to be escaped in the controller already, if the action doesn't render a view._
+NOTE: _The same security precautions have to be taken for Ajax actions as for “normal” ones. There is at least one exception, however: The output has to be escaped in the controller already, if the action doesn't render a view._
If you use the "in_place_editor plugin":http://dev.rubyonrails.org/browser/plugins/in_place_editing, or actions that return a string, rather than rendering a view, _(highlight)you have to escape the return value in the action_. Otherwise, if the return value contains a XSS string, the malicious code will be executed upon return to the browser. Escape any input value using the h() method.
h4. Command Line Injection
--- _Use user-supplied command line parameters with caution._
+NOTE: _Use user-supplied command line parameters with caution._
If your application has to execute commands in the underlying operating system, there are several methods in Ruby: exec(command), syscall(command), system(command) and `command`. You will have to be especially careful with these functions if the user may enter the whole command, or a part of it. This is because in most shells, you can execute another command at the end of the first one, concatenating them with a semicolon (;) or a vertical bar (|).
@@ -936,7 +947,7 @@ system("/bin/echo","hello; rm *")
h4. Header Injection
--- _HTTP headers are dynamically generated and under certain circumstances user input may be injected. This can lead to false redirection, XSS or HTTP response splitting._
+WARNING: _HTTP headers are dynamically generated and under certain circumstances user input may be injected. This can lead to false redirection, XSS or HTTP response splitting._
HTTP request headers have a Referer, User-Agent (client software), and Cookie field, among others. Response headers for example have a status code, Cookie and Location (redirection target URL) field. All of them are user-supplied and may be manipulated with more or less effort. _(highlight)Remember to escape these header fields, too._ For example when you display the user agent in an administration area.
diff --git a/guides/source/upgrading_ruby_on_rails.textile b/guides/source/upgrading_ruby_on_rails.textile
index 02407a5fe8..6cdc6ab289 100644
--- a/guides/source/upgrading_ruby_on_rails.textile
+++ b/guides/source/upgrading_ruby_on_rails.textile
@@ -34,17 +34,25 @@ h4(#plugins4_0). vendor/plugins
Rails 4.0 no longer supports loading plugins from <tt>vendor/plugins</tt>. You must replace any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, <tt>lib/my_plugin/*</tt> and add an appropriate initializer in <tt>config/initializers/my_plugin.rb</tt>.
-h4(#identity_map4_0). IdentityMap
+h4(#identity_map4_0). Identity Map
-Rails 4.0 has removed <tt>IdentityMap</tt> from <tt>ActiveRecord</tt>, due to "some inconsistencies with associations":https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: <tt>config.active_record.identity_map</tt>.
+Rails 4.0 has removed the identity map from Active Record, due to "some inconsistencies with associations":https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: <tt>config.active_record.identity_map</tt>.
-h4(#active_model4_0). ActiveModel
+h4(#active_record4_0). Active Record
-Rails 4.0 has changed how errors attach with the ConfirmationValidator. Now when confirmation validations fail the error will be attached to <tt>:#{attribute}_confirmation</tt> instead of <tt>attribute</tt>.
+The <tt>delete</tt> method in collection associations can now receive <tt>Fixnum</tt> or <tt>String</tt> arguments as record ids, besides records, pretty much like the <tt>destroy</tt> method does. Previously it raised <tt>ActiveRecord::AssociationTypeMismatch</tt> for such arguments. From Rails 4.0 on <tt>delete</tt> automatically tries to find the records matching the given ids before deleting them.
-h4(#action_pack4_0). ActionPack
+h4(#active_model4_0). Active Model
-Rails 4.0 changed how <tt>assert_generates</tt>, <tt>assert_recognizes</tt>, and <tt>assert_routing</tt> work. Now all these assertions raise <tt>Assertion</tt> instead of <tt>RoutingError</tt>.
+Rails 4.0 has changed how errors attach with the <tt>ActiveModel::Validations::ConfirmationValidator</tt>. Now when confirmation validations fail the error will be attached to <tt>:#{attribute}_confirmation</tt> instead of <tt>attribute</tt>.
+
+h4(#action_pack4_0). Action Pack
+
+Rails 4.0 changed how <tt>assert_generates</tt>, <tt>assert_recognizes</tt>, and <tt>assert_routing</tt> work. Now all these assertions raise <tt>Assertion</tt> instead of <tt>ActionController::RoutingError</tt>.
+
+h4(#helpers_order). Helpers Loading Order
+
+The loading order of helpers from more than one directory has changed in Rails 4.0. Previously, helpers from all directories were gathered and then sorted alphabetically. After upgrade to Rails 4.0 helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use <tt>helpers_path</tt> parameter, this change will only impact the way of loading helpers from engines. If you rely on the fact that particular helper from engine loads before or after another helper from application or another engine, you should check if correct methods are available after upgrade. If you would like to change order in which engines are loaded, you can use <tt>config.railties_order=</tt> method.
h3. Upgrading from Rails 3.1 to Rails 3.2
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index ccccc178c5..a6abe5ee97 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,9 @@
## Rails 4.0.0 (unreleased) ##
+* Add runner to Rails::Railtie as a hook called just after runner starts. *José Valim & kennyj*
+
+* Add `/rails/info/routes` path, displays same information as `rake routes` *Richard Schneeman & Andrew White*
+
* Improved `rake routes` output for redirects *Łukasz Strzałkowski & Andrew White*
* Load all environments available in `config.paths["config/environments"]`. *Piotr Sarnacki*
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index d44465e547..32797ee657 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -158,6 +158,14 @@ module Rails
self
end
+ # Load the application runner and invoke the registered hooks.
+ # Check <tt>Rails::Railtie.runner</tt> for more info.
+ def load_runner(app=self)
+ initialize_runner
+ super
+ self
+ end
+
# Stores some of the Rails initial environment parameters which
# will be used by middlewares and engines to configure themselves.
def env_config
@@ -185,7 +193,7 @@ module Rails
end
all = (railties.all - order)
- all.push(self) unless all.include?(self)
+ all.push(self) unless (all + order).include?(self)
order.push(:all) unless order.include?(:all)
index = order.index(:all)
@@ -304,6 +312,9 @@ module Rails
require "rails/console/helpers"
end
+ def initialize_runner #:nodoc:
+ end
+
def build_original_fullpath(env)
path_info = env["PATH_INFO"]
query_string = env["QUERY_STRING"]
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 84f2601f28..60aa40b92f 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -23,6 +23,8 @@ module Rails
if Rails.env.development?
app.routes.append do
get '/rails/info/properties' => "rails/info#properties"
+ get '/rails/info/routes' => "rails/info#routes"
+ get '/rails/info' => "rails/info#index"
end
end
end
diff --git a/railties/lib/rails/application/route_inspector.rb b/railties/lib/rails/application/route_inspector.rb
index b23fb3e920..942c4f4789 100644
--- a/railties/lib/rails/application/route_inspector.rb
+++ b/railties/lib/rails/application/route_inspector.rb
@@ -51,7 +51,7 @@ module Rails
end
def internal?
- path =~ %r{/rails/info/properties|^#{Rails.application.config.assets.prefix}}
+ path =~ %r{/rails/info.*|^#{Rails.application.config.assets.prefix}}
end
def engine?
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 77f1b15fb4..a672258aa6 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -41,6 +41,7 @@ ENV["RAILS_ENV"] = options[:environment]
require APP_PATH
Rails.application.require_environment!
+ Rails.application.load_runner
if code_or_file.nil?
$stderr.puts "Run '#{$0} -h' for help."
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 4c7199a2e2..806b553b81 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -437,6 +437,11 @@ module Rails
super
end
+ def load_runner(app=self)
+ railties.all { |r| r.load_runner(app) }
+ super
+ end
+
def eager_load!
railties.all(&:eager_load!)
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index bf47e66cc4..55a6b3f4f2 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -19,7 +19,7 @@ source 'https://rubygems.org'
# gem 'unicorn'
# Deploy with Capistrano
-# gem 'capistrano', :group => :development
+# gem 'capistrano', group: :development
# To use debugger
# gem 'debugger'
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 7088367462..ab0e440bc4 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -232,6 +232,18 @@ task :default => :test
public_task :apply_rails_template, :run_bundle
+ def name
+ @name ||= begin
+ # same as ActiveSupport::Inflector#underscore except not replacing '-'
+ underscored = original_name.dup
+ underscored.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
+ underscored.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
+ underscored.downcase!
+
+ underscored
+ end
+ end
+
protected
def app_templates_dir
@@ -268,18 +280,6 @@ task :default => :test
@original_name ||= File.basename(destination_root)
end
- def name
- @name ||= begin
- # same as ActiveSupport::Inflector#underscore except not replacing '-'
- underscored = original_name.dup
- underscored.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
- underscored.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
- underscored.downcase!
-
- underscored
- end
- end
-
def camelized
@camelized ||= name.gsub(/\W/, '_').squeeze('_').camelize
end
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index 6b4bdb2921..5081074395 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -1,15 +1,33 @@
+require 'rails/application/route_inspector'
+
class Rails::InfoController < ActionController::Base
+ self.view_paths = File.join(File.dirname(__FILE__), 'templates')
+ layout 'application'
+
+ before_filter :require_local!
+
+ def index
+ redirect_to '/rails/info/routes'
+ end
+
def properties
- if consider_all_requests_local? || request.local?
- render :inline => Rails::Info.to_html
- else
- render :text => '<p>For security purposes, this information is only available to local requests.</p>', :status => :forbidden
- end
+ @info = Rails::Info.to_html
+ end
+
+ def routes
+ inspector = Rails::Application::RouteInspector.new
+ @info = inspector.format(_routes.routes).join("\n")
end
protected
- def consider_all_requests_local?
- Rails.application.config.consider_all_requests_local
+ def require_local!
+ unless local_request?
+ render :text => '<p>For security purposes, this information is only available to local requests.</p>', :status => :forbidden
+ end
+ end
+
+ def local_request?
+ Rails.application.config.consider_all_requests_local || request.local?
end
end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 2102f8a03c..c3cc65ab31 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -145,6 +145,12 @@ module Rails
@load_console
end
+ def runner(&blk)
+ @load_runner ||= []
+ @load_runner << blk if blk
+ @load_runner
+ end
+
def generators(&blk)
@generators ||= []
@generators << blk if blk
@@ -179,6 +185,10 @@ module Rails
self.class.console.each { |block| block.call(app) }
end
+ def load_runner(app=self)
+ self.class.runner.each { |block| block.call(app) }
+ end
+
def load_tasks(app=self)
extend Rake::DSL if defined? Rake::DSL
self.class.rake_tasks.each { |block| self.instance_exec(app, &block) }
diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake
index eea8abe7d2..70370be3f5 100644
--- a/railties/lib/rails/tasks/engine.rake
+++ b/railties/lib/rails/tasks/engine.rake
@@ -60,7 +60,7 @@ namespace :db do
end
def find_engine_path(path)
- return if path == "/"
+ return File.expand_path(Dir.pwd) if path == "/"
if Rails::Engine.find(path)
path
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index 7dc54144da..5778b22f18 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -1,8 +1,6 @@
desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
task :routes => :environment do
- Rails.application.reload_routes!
all_routes = Rails.application.routes.routes
-
require 'rails/application/route_inspector'
inspector = Rails::Application::RouteInspector.new
puts inspector.format(all_routes, ENV['CONTROLLER']).join "\n"
diff --git a/railties/lib/rails/templates/layouts/application.html.erb b/railties/lib/rails/templates/layouts/application.html.erb
new file mode 100644
index 0000000000..53276d3e7c
--- /dev/null
+++ b/railties/lib/rails/templates/layouts/application.html.erb
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8" />
+ <title>Routes</title>
+ <style>
+ body { background-color: #fff; color: #333; }
+
+ body, p, ol, ul, td {
+ font-family: helvetica, verdana, arial, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+ }
+
+ pre {
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+ white-space: pre-wrap;
+ }
+
+ a { color: #000; }
+ a:visited { color: #666; }
+ a:hover { color: #fff; background-color:#000; }
+ </style>
+</head>
+<body>
+<h2>Your App: <%= link_to 'properties', '/rails/info/properties' %> | <%= link_to 'routes', '/rails/info/routes' %></h2>
+<%= yield %>
+
+</body>
+</html>
diff --git a/railties/lib/rails/templates/rails/info/properties.html.erb b/railties/lib/rails/templates/rails/info/properties.html.erb
new file mode 100644
index 0000000000..d47cbab202
--- /dev/null
+++ b/railties/lib/rails/templates/rails/info/properties.html.erb
@@ -0,0 +1 @@
+<%= @info.html_safe %> \ No newline at end of file
diff --git a/railties/lib/rails/templates/rails/info/routes.html.erb b/railties/lib/rails/templates/rails/info/routes.html.erb
new file mode 100644
index 0000000000..890f6f5b03
--- /dev/null
+++ b/railties/lib/rails/templates/rails/info/routes.html.erb
@@ -0,0 +1,9 @@
+<h2>
+ Routes
+</h2>
+
+<p>
+ Routes match in priority from top to bottom
+</p>
+
+<p><pre><%= @info %></pre></p> \ No newline at end of file
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 252dd0e31a..d7689863e6 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -288,16 +288,16 @@ module ApplicationTests
params = {:authenticity_token => token}
get "/posts/1"
- assert_match /patch/, last_response.body
+ assert_match(/patch/, last_response.body)
patch "/posts/1", params
- assert_match /update/, last_response.body
+ assert_match(/update/, last_response.body)
patch "/posts/1", params
assert_equal 200, last_response.status
put "/posts/1", params
- assert_match /update/, last_response.body
+ assert_match(/update/, last_response.body)
put "/posts/1", params
assert_equal 200, last_response.status
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index 05d73dfc5c..3f4db77897 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -33,14 +33,14 @@ module ApplicationTests
output = `bundle exec rake notes`
lines = output.scan(/\[([0-9\s]+)\](\s)/)
- assert_match /note in erb/, output
- assert_match /note in haml/, output
- assert_match /note in slim/, output
- assert_match /note in ruby/, output
- assert_match /note in coffee/, output
- assert_match /note in js/, output
- assert_match /note in css/, output
- assert_match /note in scss/, output
+ assert_match(/note in erb/, output)
+ assert_match(/note in haml/, output)
+ assert_match(/note in slim/, output)
+ assert_match(/note in ruby/, output)
+ assert_match(/note in coffee/, output)
+ assert_match(/note in js/, output)
+ assert_match(/note in css/, output)
+ assert_match(/note in scss/, output)
assert_equal 8, lines.size
@@ -72,12 +72,12 @@ module ApplicationTests
output = `bundle exec rake notes`
lines = output.scan(/\[([0-9\s]+)\]/).flatten
- assert_match /note in app directory/, output
- assert_match /note in config directory/, output
- assert_match /note in lib directory/, output
- assert_match /note in script directory/, output
- assert_match /note in test directory/, output
- assert_no_match /note in some_other directory/, output
+ assert_match(/note in app directory/, output)
+ assert_match(/note in config directory/, output)
+ assert_match(/note in lib directory/, output)
+ assert_match(/note in script directory/, output)
+ assert_match(/note in test directory/, output)
+ assert_no_match(/note in some_other directory/, output)
assert_equal 5, lines.size
@@ -108,13 +108,13 @@ module ApplicationTests
output = `SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bundle exec rake notes`
lines = output.scan(/\[([0-9\s]+)\]/).flatten
- assert_match /note in app directory/, output
- assert_match /note in config directory/, output
- assert_match /note in lib directory/, output
- assert_match /note in script directory/, output
- assert_match /note in test directory/, output
+ assert_match(/note in app directory/, output)
+ assert_match(/note in config directory/, output)
+ assert_match(/note in lib directory/, output)
+ assert_match(/note in script directory/, output)
+ assert_match(/note in test directory/, output)
- assert_match /note in some_other directory/, output
+ assert_match(/note in some_other directory/, output)
assert_equal 6, lines.size
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 27d521485c..8cf867da3c 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -167,5 +167,28 @@ module ApplicationTests
end
assert !File.exists?(File.join(app_path, 'db', 'schema_cache.dump'))
end
+
+ def test_load_activerecord_base_when_we_use_observers
+ Dir.chdir(app_path) do
+ `bundle exec rails g model user;
+ bundle exec rake db:migrate;
+ bundle exec rails g observer user;`
+
+ add_to_config "config.active_record.observers = :user_observer"
+
+ assert_equal "0", `bundle exec rails r "puts User.count"`.strip
+
+ app_file "lib/tasks/count_user.rake", <<-RUBY
+ namespace :user do
+ task :count => :environment do
+ puts User.count
+ end
+ end
+ RUBY
+
+ assert_equal "0", `bundle exec rake user:count`.strip
+ end
+ end
+
end
end
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index 977a5fc7e8..d1373ba202 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -15,12 +15,24 @@ module ApplicationTests
teardown_app
end
+ test "rails/info/routes in development" do
+ app("development")
+ get "/rails/info/routes"
+ assert_equal 200, last_response.status
+ end
+
test "rails/info/properties in development" do
app("development")
get "/rails/info/properties"
assert_equal 200, last_response.status
end
+ test "rails/info/routes in production" do
+ app("production")
+ get "/rails/info/routes"
+ assert_equal 404, last_response.status
+ end
+
test "rails/info/properties in production" do
app("production")
get "/rails/info/properties"
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index e1d283a7fd..81ed5873a5 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -57,5 +57,15 @@ module ApplicationTests
assert_match "script/program_name.rb", Dir.chdir(app_path) { `bundle exec rails runner "script/program_name.rb"` }
end
+
+ def test_with_hook
+ add_to_config <<-RUBY
+ runner do |app|
+ app.config.ran = true
+ end
+ RUBY
+
+ assert_match "true", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.application.config.ran"` }
+ end
end
end
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index 9aa1d68675..78648a16b3 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -35,7 +35,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
start
- assert_match /Loading \w+ environment \(Rails/, output
+ assert_match(/Loading \w+ environment \(Rails/, output)
end
def test_start_with_debugger
@@ -52,7 +52,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
start ["--sandbox"]
- assert_match /Loading \w+ environment in sandbox \(Rails/, output
+ assert_match(/Loading \w+ environment in sandbox \(Rails/, output)
end
def test_console_with_environment
@@ -61,7 +61,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
start ["-e production"]
- assert_match /production/, output
+ assert_match(/production/, output)
end
def test_console_with_rails_environment
@@ -70,7 +70,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
start ["RAILS_ENV=production"]
- assert_match /production/, output
+ assert_match(/production/, output)
end
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
index 6d0f5ca073..562b83713b 100644
--- a/railties/test/commands/dbconsole_test.rb
+++ b/railties/test/commands/dbconsole_test.rb
@@ -12,7 +12,7 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
app_config({})
capture_abort { Rails::DBConsole.config }
assert aborted
- assert_match /No database is configured for the environment '\w+'/, output
+ assert_match(/No database is configured for the environment '\w+'/, output)
app_config(test: "with_init")
assert_equal Rails::DBConsole.config, "with_init"
@@ -129,7 +129,7 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
def test_unknown_command_line_client
start(adapter: 'unknown', database: 'db')
assert aborted
- assert_match /Unknown command-line client for db/, output
+ assert_match(/Unknown command-line client for db/, output)
end
def test_print_help_short
@@ -138,7 +138,7 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
assert aborted
assert_equal '', output
- assert_match /Usage:.*dbconsole/, stdout
+ assert_match(/Usage:.*dbconsole/, stdout)
end
def test_print_help_long
@@ -147,7 +147,7 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
assert aborted
assert_equal '', output
- assert_match /Usage:.*dbconsole/, stdout
+ assert_match(/Usage:.*dbconsole/, stdout)
end
private
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 60e7e57a91..417d019178 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -186,7 +186,7 @@ class GeneratorsTest < Rails::Generators::TestCase
mkdir_p(File.dirname(template))
File.open(template, 'w'){ |f| f.write "empty" }
- output = capture(:stdout) do
+ capture(:stdout) do
Rails::Generators.invoke :model, ["user"], :destination_root => destination_root
end
@@ -205,7 +205,7 @@ class GeneratorsTest < Rails::Generators::TestCase
def test_usage_with_embedded_ruby
require File.expand_path("fixtures/lib/generators/usage_template/usage_template_generator", File.dirname(__FILE__))
output = capture(:stdout) { Rails::Generators.invoke :usage_template, ['--help'] }
- assert_match /:: 2 ::/, output
+ assert_match(/:: 2 ::/, output)
end
def test_hide_namespace
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index aa04cad033..5d6b6f9f72 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -29,7 +29,7 @@ class PathsTest < ActiveSupport::TestCase
test "creating a root level path" do
@root.add "app"
assert_equal ["/foo/bar/app"], @root["app"].to_a
- assert_equal [Pathname.new("/foo/bar/app")], @root["app"].paths
+ assert_equal ["/foo/bar/app"], @root["app"].paths
end
test "creating a root level path with options" do
@@ -192,7 +192,7 @@ class PathsTest < ActiveSupport::TestCase
@root["app"] = "/app"
@root["app"].glob = "*.rb"
assert_equal "*.rb", @root["app"].glob
- assert_equal [Pathname.new("/app")], @root["app"].paths
+ assert_equal ["/foo/bar/app"], @root["app"].paths
end
test "it should be possible to override a path's default glob without assignment" do
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index f7a30a16d2..cfb32b7d35 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -12,29 +12,28 @@ class InfoControllerTest < ActionController::TestCase
def setup
Rails.application.routes.draw do
get '/rails/info/properties' => "rails/info#properties"
+ get '/rails/info/routes' => "rails/info#routes"
end
- @request.stubs(:local? => true)
- @controller.stubs(:consider_all_requests_local? => false)
+ @controller.stubs(:local_request? => true)
@routes = Rails.application.routes
Rails::InfoController.send(:include, @routes.url_helpers)
end
test "info controller does not allow remote requests" do
- @request.stubs(:local? => false)
+ @controller.stubs(:local_request? => false)
get :properties
assert_response :forbidden
end
test "info controller renders an error message when request was forbidden" do
- @request.stubs(:local? => false)
+ @controller.stubs(:local_request? => false)
get :properties
assert_select 'p'
end
test "info controller allows requests when all requests are considered local" do
- @request.stubs(:local? => false)
- @controller.stubs(:consider_all_requests_local? => true)
+ @controller.stubs(:local_request? => true)
get :properties
assert_response :success
end
@@ -48,4 +47,10 @@ class InfoControllerTest < ActionController::TestCase
get :properties
assert_select 'table'
end
+
+ test "info controller renders with routes" do
+ get :routes
+ assert_select 'pre'
+ end
+
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 7a047ef93a..4437e2c8af 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -1098,6 +1098,10 @@ YAML
get("/assets/bar.js")
assert_equal "// App's bar js\n;", last_response.body.strip
+
+ # ensure that railties are not added twice
+ railties = Rails.application.ordered_railties.map(&:class)
+ assert_equal railties, railties.uniq
end
test "railties_order adds :all with lowest priority if not given" do
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index cd495320b5..c80b0f63af 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -163,6 +163,22 @@ module RailtiesTest
assert $ran_block
end
+ test "runner block is executed when MyApp.load_runner is called" do
+ $ran_block = false
+
+ class MyTie < Rails::Railtie
+ runner do
+ $ran_block = true
+ end
+ end
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ AppTemplate::Application.load_runner
+ assert $ran_block
+ end
+
test "railtie can add initializers" do
$ran_block = false