Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge remote-tracking branch 'upstream/master'

  • Loading branch information...
commit e1e5f06d76cc8c9d71341633015e50abdff6d48a 2 parents d49f4a7 + a513cc1
@jeroeningen authored
Showing with 715 additions and 239 deletions.
  1. +16 −0 actionmailer/CHANGELOG.md
  2. +96 −30 actionpack/CHANGELOG.md
  3. +8 −2 actionpack/lib/action_controller/metal/url_for.rb
  4. +5 −0 actionpack/lib/action_dispatch/railtie.rb
  5. +15 −10 actionpack/lib/action_dispatch/routing/route_set.rb
  6. +1 −0  actionpack/lib/action_view/helpers/form_helper.rb
  7. +5 −3 actionpack/lib/action_view/helpers/form_options_helper.rb
  8. +0 −18 actionpack/test/dispatch/prefix_generation_test.rb
  9. +14 −0 actionpack/test/template/form_helper_test.rb
  10. +15 −5 actionpack/test/template/form_options_helper_test.rb
  11. +14 −0 activemodel/CHANGELOG.md
  12. +8 −5 activemodel/lib/active_model/naming.rb
  13. +86 −3 activerecord/CHANGELOG.md
  14. +7 −7 activerecord/lib/active_record/associations.rb
  15. +15 −12 activerecord/lib/active_record/associations/builder/association.rb
  16. +25 −32 activerecord/lib/active_record/associations/builder/belongs_to.rb
  17. +10 −8 activerecord/lib/active_record/associations/builder/collection_association.rb
  18. +1 −1  activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
  19. +11 −11 activerecord/lib/active_record/associations/builder/singular_association.rb
  20. +2 −2 activerecord/test/cases/associations/belongs_to_associations_test.rb
  21. +1 −1  activerecord/test/cases/associations/join_model_test.rb
  22. +1 −3 activerecord/test/cases/multiple_db_test.rb
  23. +15 −13 activerecord/test/cases/timestamp_test.rb
  24. +1 −1  activerecord/test/models/author.rb
  25. +2 −3 activerecord/test/models/member.rb
  26. +2 −2 activerecord/test/support/connection.rb
  27. +50 −0 activesupport/CHANGELOG.md
  28. +2 −2 activesupport/lib/active_support/core_ext/object/try.rb
  29. +4 −6 activesupport/lib/active_support/locale/en.yml
  30. +135 −26 activesupport/lib/active_support/number_helper.rb
  31. +42 −0 activesupport/test/number_helper_i18n_test.rb
  32. +3 −4 activesupport/test/number_helper_test.rb
  33. +2 −0  guides/source/4_0_release_notes.textile
  34. +12 −6 guides/source/association_basics.textile
  35. +6 −2 guides/source/configuring.textile
  36. +25 −0 guides/source/contributing_to_ruby_on_rails.textile
  37. +5 −0 railties/CHANGELOG.md
  38. +5 −1 railties/lib/rails/engine.rb
  39. +0 −5 railties/lib/rails/generators/rails/app/templates/config/application.rb
  40. +48 −0 railties/test/railties/engine_test.rb
  41. +0 −15 railties/test/railties/mounted_engine_test.rb
View
16 actionmailer/CHANGELOG.md
@@ -6,6 +6,22 @@
* Asynchronously send messages via the Rails Queue *Brian Cardarella*
+
+## Rails 3.2.8 (Aug 9, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.7 (Jul 26, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.6 (Jun 12, 2012) ##
+
+* No changes.
+
+
## Rails 3.2.5 (Jun 1, 2012) ##
* No changes.
View
126 actionpack/CHANGELOG.md
@@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##
+* Raises an ArgumentError when the first argument in `form_for` contain `nil`
+ or is empty.
+
+ *Richard Schneeman*
+
* Add 'X-Frame-Options' => 'SAMEORIGIN' and
'X-XSS-Protection' => '1; mode=block'
as default headers.
@@ -12,19 +17,19 @@
We recommend the use of Unobtrusive JavaScript instead. For example:
- link_to "Greeting", "#", :class => "nav_link"
+ link_to "Greeting", "#", :class => "nav_link"
- $(function() {
- $('.nav_link').click(function() {
- // Some complex code
+ $(function() {
+ $('.nav_link').click(function() {
+ // Some complex code
- return false;
+ return false;
+ });
});
- });
or
- link_to "Greeting", '#', onclick: "alert('Hello world!'); return false", class: "nav_link"
+ link_to "Greeting", '#', onclick: "alert('Hello world!'); return false", class: "nav_link"
for simple cases.
@@ -41,18 +46,18 @@
* Added ActionController::Live. Mix it in to your controller and you can
stream data to the client live. For example:
- class FooController < ActionController::Base
- include ActionController::Live
+ class FooController < ActionController::Base
+ include ActionController::Live
- def index
- 100.times {
- # Client will see this as it's written
- response.stream.write "hello world\n"
- sleep 1
- }
- response.stream.close
+ def index
+ 100.times {
+ # Client will see this as it's written
+ response.stream.write "hello world\n"
+ sleep 1
+ }
+ response.stream.close
+ end
end
- end
* Remove ActionDispatch::Head middleware in favor of Rack::Head. *Santiago Pastorino*
@@ -261,13 +266,13 @@
* Add `collection_check_boxes` form helper, similar to `collection_select`:
Example:
- collection_check_boxes :post, :author_ids, Author.all, :id, :name
- # Outputs something like:
- <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" />
- <label for="post_author_ids_1">D. Heinemeier Hansson</label>
- <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
- <label for="post_author_ids_2">D. Thomas</label>
- <input name="post[author_ids][]" type="hidden" value="" />
+ collection_check_boxes :post, :author_ids, Author.all, :id, :name
+ # Outputs something like:
+ <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" />
+ <label for="post_author_ids_1">D. Heinemeier Hansson</label>
+ <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
+ <label for="post_author_ids_2">D. Thomas</label>
+ <input name="post[author_ids][]" type="hidden" value="" />
The label/check_box pairs can be customized with a block.
@@ -276,12 +281,12 @@
* Add `collection_radio_buttons` form helper, similar to `collection_select`:
Example:
- collection_radio_buttons :post, :author_id, Author.all, :id, :name
- # Outputs something like:
- <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" />
- <label for="post_author_id_1">D. Heinemeier Hansson</label>
- <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
- <label for="post_author_id_2">D. Thomas</label>
+ collection_radio_buttons :post, :author_id, Author.all, :id, :name
+ # Outputs something like:
+ <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" />
+ <label for="post_author_id_1">D. Heinemeier Hansson</label>
+ <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
+ <label for="post_author_id_2">D. Thomas</label>
The label/radio_button pairs can be customized with a block.
@@ -325,6 +330,67 @@
HTML5 `mark` element. *Brian Cardarella*
+## Rails 3.2.8 (Aug 9, 2012) ##
+
+* There is an XSS vulnerability in the strip_tags helper in Ruby on Rails, the
+ helper doesn't correctly handle malformed html. As a result an attacker can
+ execute arbitrary javascript through the use of specially crafted malformed
+ html.
+
+ *Marek from Nethemba (www.nethemba.com) & Santiago Pastorino*
+
+* When a "prompt" value is supplied to the `select_tag` helper, the "prompt" value is not escaped.
+ If untrusted data is not escaped, and is supplied as the prompt value, there is a potential for XSS attacks.
+ Vulnerable code will look something like this:
+ select_tag("name", options, :prompt => UNTRUSTED_INPUT)
+
+ *Santiago Pastorino*
+
+* Reverted the deprecation of `:confirm`. *Rafael Mendonça França*
+
+* Reverted the deprecation of `:disable_with`. *Rafael Mendonça França*
+
+* Reverted the deprecation of `:mouseover` option to `image_tag`. *Rafael Mendonça França*
+
+* Reverted the deprecation of `button_to_function` and `link_to_function` helpers.
+
+ *Rafael Mendonça França*
+
+
+## Rails 3.2.7 (Jul 26, 2012) ##
+
+* Do not convert digest auth strings to symbols. CVE-2012-3424
+
+* Bump Journey requirements to 1.0.4
+
+* Add support for optional root segments containing slashes
+
+* Fixed bug creating invalid HTML in select options
+
+* Show in log correct wrapped keys
+
+* Fix NumberHelper options wrapping to prevent verbatim blocks being rendered instead of line continuations.
+
+* ActionController::Metal doesn't have logger method, check it and then delegate
+
+* ActionController::Caching depends on RackDelegation and AbstractController::Callbacks
+
+
+## Rails 3.2.6 (Jun 12, 2012) ##
+
+* nil is removed from array parameter values
+
+ CVE-2012-2694
+
+* Deprecate `:confirm` in favor of `':data => { :confirm => "Text" }'` option for `button_to`, `button_tag`, `image_submit_tag`, `link_to` and `submit_tag` helpers.
+
+ *Carlos Galdino*
+
+* Allow to use mounted_helpers (helpers for accessing mounted engines) in ActionView::TestCase. *Piotr Sarnacki*
+
+* Include mounted_helpers (helpers for accessing mounted engines) in ActionDispatch::IntegrationTest by default. *Piotr Sarnacki*
+
+
## Rails 3.2.5 (Jun 1, 2012) ##
* No changes.
View
10 actionpack/lib/action_controller/metal/url_for.rb
@@ -30,9 +30,15 @@ def url_options
:_recall => request.symbolized_path_parameters
).freeze
- if _routes.equal?(env["action_dispatch.routes"])
+ if (same_origin = _routes.equal?(env["action_dispatch.routes"])) ||
+ (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
+ (original_script_name = env['SCRIPT_NAME'])
@_url_options.dup.tap do |options|
- options[:script_name] = request.script_name.dup
+ if original_script_name
+ options[:original_script_name] = original_script_name
+ else
+ options[:script_name] = same_origin ? request.script_name.dup : script_name
+ end
options.freeze
end
else
View
5 actionpack/lib/action_dispatch/railtie.rb
@@ -19,6 +19,11 @@ class Railtie < Rails::Railtie
:verbose => false
}
+ config.action_dispatch.default_headers = {
+ 'X-Frame-Options' => 'SAMEORIGIN',
+ 'X-XSS-Protection' => '1; mode=block'
+ }
+
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
View
25 actionpack/lib/action_dispatch/routing/route_set.rb
@@ -163,9 +163,9 @@ def length
private
def define_named_route_methods(name, route)
- define_url_helper route, :"#{name}_path",
+ define_url_helper route, :"#{name}_path",
route.defaults.merge(:use_route => name, :only_path => true)
- define_url_helper route, :"#{name}_url",
+ define_url_helper route, :"#{name}_url",
route.defaults.merge(:use_route => name, :only_path => false)
end
@@ -409,21 +409,19 @@ def build_path(path, requirements, separators, anchor)
def build_conditions(current_conditions, path_values)
conditions = current_conditions.dup
- verbs = conditions[:request_method] || []
-
# Rack-Mount requires that :request_method be a regular expression.
# :request_method represents the HTTP verb that matches this route.
#
# Here we munge values before they get sent on to rack-mount.
+ verbs = conditions[:request_method] || []
unless verbs.empty?
conditions[:request_method] = %r[^#{verbs.join('|')}$]
end
- conditions.keep_if do |k,v|
+
+ conditions.keep_if do |k, _|
k == :action || k == :controller ||
@request_class.public_method_defined?(k) || path_values.include?(k)
end
-
- conditions
end
private :build_conditions
@@ -465,7 +463,7 @@ def current_controller
def use_recall_for(key)
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
if !named_route_exists? || segment_keys.include?(key)
- @options[key] = @recall.delete(key)
+ @options[key] = @recall.delete(key)
end
end
end
@@ -574,7 +572,8 @@ def generate(options, recall = {}, extras = false)
end
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
- :trailing_slash, :anchor, :params, :only_path, :script_name]
+ :trailing_slash, :anchor, :params, :only_path, :script_name,
+ :original_script_name]
def mounted?
false
@@ -594,7 +593,13 @@ def url_for(options)
user, password = extract_authentication(options)
recall = options.delete(:_recall)
- script_name = options.delete(:script_name).presence || _generate_prefix(options)
+
+ original_script_name = options.delete(:original_script_name).presence
+ script_name = options.delete(:script_name).presence || _generate_prefix(options)
+
+ if script_name && original_script_name
+ script_name = original_script_name + script_name
+ end
path_options = options.except(*RESERVED_OPTIONS)
path_options = yield(path_options) if block_given?
View
1  actionpack/lib/action_view/helpers/form_helper.rb
@@ -423,6 +423,7 @@ def form_for(record, options = {}, &proc)
object = nil
else
object = record.is_a?(Array) ? record.last : record
+ raise ArgumentError, "First argument in form cannot contain nil or be empty" if object.blank?
object_name = options[:as] || model_name_from_record_or_class(object).param_key
apply_form_for_options!(record, object, options)
end
View
8 actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -708,9 +708,11 @@ def collection_check_boxes(object, method, collection, value_method, text_method
private
def option_html_attributes(element)
- return {} unless Array === element
-
- Hash[element.select { |e| Hash === e }.reduce({}, :merge).map { |k, v| [k, v] }]
+ if Array === element
+ element.select { |e| Hash === e }.reduce({}, :merge!)
+ else
+ {}
+ end
end
def option_text_and_value(option)
View
18 actionpack/test/dispatch/prefix_generation_test.rb
@@ -166,18 +166,6 @@ def setup
assert_equal "/generate", last_response.body
end
- test "[ENGINE] generating application's url includes default_url_options[:script_name]" do
- RailsApplication.routes.default_url_options = {:script_name => "/something"}
- get "/pure-awesomeness/blog/url_to_application"
- assert_equal "/something/generate", last_response.body
- end
-
- test "[ENGINE] generating application's url should give higher priority to default_url_options[:script_name]" do
- RailsApplication.routes.default_url_options = {:script_name => "/something"}
- get "/pure-awesomeness/blog/url_to_application", {}, 'SCRIPT_NAME' => '/foo'
- assert_equal "/something/generate", last_response.body
- end
-
test "[ENGINE] generating engine's url with polymorphic path" do
get "/pure-awesomeness/blog/polymorphic_path_for_engine"
assert_equal "/pure-awesomeness/blog/posts/1", last_response.body
@@ -200,12 +188,6 @@ def setup
assert_equal "/something/awesome/blog/posts/1", last_response.body
end
- test "[APP] generating engine's route should give higher priority to default_url_options[:script_name]" do
- RailsApplication.routes.default_url_options = {:script_name => "/something"}
- get "/generate", {}, 'SCRIPT_NAME' => "/foo"
- assert_equal "/something/awesome/blog/posts/1", last_response.body
- end
-
test "[APP] generating engine's url with polymorphic path" do
get "/polymorphic_path_for_engine"
assert_equal "/awesome/blog/posts/1", last_response.body
View
14 actionpack/test/template/form_helper_test.rb
@@ -1045,6 +1045,20 @@ def test_form_for_requires_block
end
end
+ def test_form_for_requires_arguments
+ error = assert_raises(ArgumentError) do
+ form_for(nil, :html => { :id => 'create-post' }) do
+ end
+ end
+ assert_equal "First argument in form cannot contain nil or be empty", error.message
+
+ error = assert_raises(ArgumentError) do
+ form_for([nil, nil], :html => { :id => 'create-post' }) do
+ end
+ end
+ assert_equal "First argument in form cannot contain nil or be empty", error.message
+ end
+
def test_form_for
form_for(@post, :html => { :id => 'create-post' }) do |f|
concat f.label(:title) { "The Title" }
View
20 actionpack/test/template/form_options_helper_test.rb
@@ -1164,11 +1164,12 @@ def test_options_for_select_with_special_characters
)
end
- def test_option_html_attributes_from_without_hash
- assert_equal(
- {},
- option_html_attributes([ 'foo', 'bar' ])
- )
+ def test_option_html_attributes_with_no_array_element
+ assert_equal({}, option_html_attributes('foo'))
+ end
+
+ def test_option_html_attributes_without_hash
+ assert_equal({}, option_html_attributes([ 'foo', 'bar' ]))
end
def test_option_html_attributes_with_single_element_hash
@@ -1192,6 +1193,15 @@ def test_option_html_attributes_with_multiple_hashes
)
end
+ def test_option_html_attributes_with_multiple_hashes_does_not_modify_them
+ options1 = { class: 'fancy' }
+ options2 = { onclick: "alert('Hello World');" }
+ option_html_attributes([ 'foo', 'bar', options1, options2 ])
+
+ assert_equal({ class: 'fancy' }, options1)
+ assert_equal({ onclick: "alert('Hello World');" }, options2)
+ end
+
def test_grouped_collection_select
@post = Post.new
@post.origin = 'dk'
View
14 activemodel/CHANGELOG.md
@@ -39,10 +39,24 @@
* When `^` or `$` are used in the regular expression provided to `validates_format_of` and the :multiline option is not set to true, an exception will be raised. This is to prevent security vulnerabilities when using `validates_format_of`. The problem is described in detail in the Rails security guide.
+
+## Rails 3.2.8 (Aug 9, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.7 (Jul 26, 2012) ##
+
+* `validates_inclusion_of` and `validates_exclusion_of` now accept `:within` option as alias of `:in` as documented.
+
+* Fix the the backport of the object dup with the ruby 1.9.3p194.
+
+
## Rails 3.2.6 (Jun 12, 2012) ##
* No changes.
+
## Rails 3.2.5 (Jun 1, 2012) ##
* No changes.
View
13 activemodel/lib/active_model/naming.rb
@@ -298,12 +298,15 @@ def self.param_key(record_or_class)
model_name_from_record_or_class(record_or_class).param_key
end
- private
- def self.model_name_from_record_or_class(record_or_class)
- return record_or_class.model_name if record_or_class.respond_to?(:model_name)
- return record_or_class.to_model.class.model_name if record_or_class.respond_to?(:to_model)
+ def self.model_name_from_record_or_class(record_or_class) #:nodoc:
+ if record_or_class.respond_to?(:model_name)
+ record_or_class.model_name
+ elsif record_or_class.respond_to?(:to_model)
+ record_or_class.to_model.class.model_name
+ else
record_or_class.class.model_name
end
+ end
+ private_class_method :model_name_from_record_or_class
end
-
end
View
89 activerecord/CHANGELOG.md
@@ -1,5 +1,8 @@
## Rails 4.0.0 (unreleased) ##
+* Fixed table name prefix that is generated in engines for namespaced models
+ *Wojciech Wnętrzak*
+
* Make sure `:environment` task is executed before `db:schema:load` or `db:structure:load`
Fixes #4772.
@@ -465,11 +468,11 @@
The `add_index` method now supports a `where` option that receives a
string with the partial index criteria.
- add_index(:accounts, :code, :where => "active")
+ add_index(:accounts, :code, :where => "active")
- Generates
+ Generates
- CREATE INDEX index_accounts_on_code ON accounts(code) WHERE active
+ CREATE INDEX index_accounts_on_code ON accounts(code) WHERE active
*Marcelo Silveira*
@@ -579,6 +582,86 @@
* PostgreSQL hstore types are automatically deserialized from the database.
+## Rails 3.2.8 (Aug 9, 2012) ##
+
+* Do not consider the numeric attribute as changed if the old value is zero and the new value
+ is not a string.
+ Fixes #7237.
+
+ *Rafael Mendonça França*
+
+* Do not consider the numeric attribute as changed if the old value is zero and the new value
+ is not a string.
+ Fixes #7237.
+
+ *Rafael Mendonça França*
+
+* Removes the deprecation of `update_attribute`. *fxn*
+
+* Reverted the deprecation of `composed_of`. *Rafael Mendonça França*
+
+* Reverted the deprecation of `*_sql` association options. They will
+ be deprecated in 4.0 instead. *Jon Leighton*
+
+* Do not eager load AR session store. ActiveRecord::SessionStore depends on the abstract store
+ in Action Pack. Eager loading this class would break client code that eager loads Active Record
+ standalone.
+ Fixes #7160
+
+ *Xavier Noria*
+
+* Do not set RAILS_ENV to "development" when using `db:test:prepare` and related rake tasks.
+ This was causing the truncation of the development database data when using RSpec.
+ Fixes #7175.
+
+ *Rafael Mendonça França*
+
+
+## Rails 3.2.7 (Jul 26, 2012) ##
+
+* `:finder_sql` and `:counter_sql` options on collection associations
+ are deprecated. Please transition to using scopes.
+
+ *Jon Leighton*
+
+* `:insert_sql` and `:delete_sql` options on `has_and_belongs_to_many`
+ associations are deprecated. Please transition to using `has_many
+ :through`
+
+ *Jon Leighton*
+
+* `composed_of` has been deprecated. You'll have to write your own accessor
+ and mutator methods if you'd like to use value objects to represent some
+ portion of your models.
+
+ *Steve Klabnik*
+
+* `update_attribute` has been deprecated. Use `update_column` if
+ you want to bypass mass-assignment protection, validations, callbacks,
+ and touching of updated_at. Otherwise please use `update_attributes`.
+
+ *Steve Klabnik*
+
+
+## Rails 3.2.6 (Jun 12, 2012) ##
+
+* protect against the nesting of hashes changing the
+ table context in the next call to build_from_hash. This fix
+ covers this case as well.
+
+ CVE-2012-2695
+
+* Revert earlier 'perf fix' (see 3.2.4 changelog / GH #6289). This
+ change introduced a regression (GH #6609). assoc.clear and
+ assoc.delete_all have loaded the association before doing the delete
+ since at least Rails 2.3. Doing the delete without loading the
+ records means that the `before_remove` and `after_remove` callbacks do
+ not get invoked. Therefore, this change was less a fix a more an
+ optimisation, which should only have gone into master.
+
+ *Jon Leighton*
+
+
## Rails 3.2.5 (Jun 1, 2012) ##
* Restore behavior of Active Record 3.2.3 scopes.
View
14 activerecord/lib/active_record/associations.rb
@@ -1205,14 +1205,14 @@ def has_many(name, scope = nil, options = {}, &extension)
# from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
# if the real class name is Person, you'll have to specify it with this option.
# [:dependent]
- # Controls what happens to the associated objects when
- # their owner is destroyed:
+ # Controls what happens to the associated object when
+ # its owner is destroyed:
#
- # * <tt>:destroy</tt> causes all the associated objects to also be destroyed
- # * <tt>:delete</tt> causes all the asssociated objects to be deleted directly from the database (so callbacks will not execute)
- # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
- # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records
- # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects
+ # * <tt>:destroy</tt> causes the associated object to also be destroyed
+ # * <tt>:delete</tt> causes the asssociated object to be deleted directly from the database (so callbacks will not execute)
+ # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
+ # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record
+ # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
View
27 activerecord/lib/active_record/associations/builder/association.rb
@@ -63,17 +63,19 @@ def define_accessors
end
def define_readers
- name = self.name
- mixin.redefine_method(name) do |*params|
- association(name).reader(*params)
- end
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}(*args)
+ association(:#{name}).reader(*args)
+ end
+ CODE
end
def define_writers
- name = self.name
- mixin.redefine_method("#{name}=") do |value|
- association(name).writer(value)
- end
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}=(value)
+ association(:#{name}).writer(value)
+ end
+ CODE
end
def configure_dependency
@@ -88,10 +90,11 @@ def configure_dependency
)
end
- name = self.name
- mixin.redefine_method("#{macro}_dependent_for_#{name}") do
- association(name).handle_dependency
- end
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{macro}_dependent_for_#{name}
+ association(:#{name}).handle_dependency
+ end
+ CODE
model.before_destroy "#{macro}_dependent_for_#{name}"
end
View
57 activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -21,49 +21,42 @@ def build
def add_counter_cache_callbacks(reflection)
cache_column = reflection.counter_cache_column
- name = self.name
- method_name = "belongs_to_counter_cache_after_create_for_#{name}"
- mixin.redefine_method(method_name) do
- record = send(name)
- record.class.increment_counter(cache_column, record.id) unless record.nil?
- end
- model.after_create(method_name)
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def belongs_to_counter_cache_after_create_for_#{name}
+ record = #{name}
+ record.class.increment_counter(:#{cache_column}, record.id) unless record.nil?
+ end
- method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
- mixin.redefine_method(method_name) do
- unless marked_for_destruction?
- record = send(name)
- record.class.decrement_counter(cache_column, record.id) unless record.nil?
+ def belongs_to_counter_cache_before_destroy_for_#{name}
+ unless marked_for_destruction?
+ record = #{name}
+ record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil?
+ end
end
- end
- model.before_destroy(method_name)
+ CODE
- model.send(:module_eval,
- "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)", __FILE__, __LINE__
- )
+ model.after_create "belongs_to_counter_cache_after_create_for_#{name}"
+ model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}"
+
+ klass = reflection.class_name.safe_constantize
+ klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
end
def add_touch_callbacks(reflection)
- name = self.name
- method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
- touch = options[:touch]
-
- mixin.redefine_method(method_name) do
- record = send(name)
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def belongs_to_touch_after_save_or_destroy_for_#{name}
+ record = #{name}
- unless record.nil?
- if touch == true
- record.touch
- else
- record.touch(touch)
+ unless record.nil?
+ record.touch #{options[:touch].inspect if options[:touch] != true}
end
end
- end
+ CODE
- model.after_save(method_name)
- model.after_touch(method_name)
- model.after_destroy(method_name)
+ model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}"
+ model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}"
+ model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}"
end
def valid_dependent_options
View
18 activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -65,19 +65,21 @@ def define_callback(callback_name)
def define_readers
super
- name = self.name
- mixin.redefine_method("#{name.to_s.singularize}_ids") do
- association(name).ids_reader
- end
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name.to_s.singularize}_ids
+ association(:#{name}).ids_reader
+ end
+ CODE
end
def define_writers
super
- name = self.name
- mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
- association(name).ids_writer(ids)
- end
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name.to_s.singularize}_ids=(ids)
+ association(:#{name}).ids_writer(ids)
+ end
+ CODE
end
end
end
View
2  activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -29,7 +29,7 @@ def define_destroy_hook
model.send(:include, Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def destroy_associations
- association(#{name.to_sym.inspect}).delete_all
+ association(:#{name}).delete_all
super
end
RUBY
View
22 activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -14,19 +14,19 @@ def define_accessors
end
def define_constructors
- name = self.name
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def build_#{name}(*args, &block)
+ association(:#{name}).build(*args, &block)
+ end
- mixin.redefine_method("build_#{name}") do |*params, &block|
- association(name).build(*params, &block)
- end
+ def create_#{name}(*args, &block)
+ association(:#{name}).create(*args, &block)
+ end
- mixin.redefine_method("create_#{name}") do |*params, &block|
- association(name).create(*params, &block)
- end
-
- mixin.redefine_method("create_#{name}!") do |*params, &block|
- association(name).create!(*params, &block)
- end
+ def create_#{name}!(*args, &block)
+ association(:#{name}).create!(*args, &block)
+ end
+ CODE
end
end
end
View
4 activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -524,13 +524,13 @@ def test_dependent_delete_and_destroy_with_belongs_to
def test_invalid_belongs_to_dependent_option_nullify_raises_exception
assert_raise ArgumentError do
- Author.belongs_to :special_author_address, :dependent => :nullify
+ Class.new(Author).belongs_to :special_author_address, :dependent => :nullify
end
end
def test_invalid_belongs_to_dependent_option_restrict_raises_exception
assert_raise ArgumentError do
- Author.belongs_to :special_author_address, :dependent => :restrict
+ Class.new(Author).belongs_to :special_author_address, :dependent => :restrict
end
end
View
2  activerecord/test/cases/associations/join_model_test.rb
@@ -385,7 +385,7 @@ def test_has_many_through_has_many_find_by_id
end
def test_has_many_through_polymorphic_has_one
- assert_equal Tagging.find(1,2).sort_by { |t| t.id }, authors(:david).tagging
+ assert_equal Tagging.find(1,2).sort_by { |t| t.id }, authors(:david).taggings_2
end
def test_has_many_through_polymorphic_has_many
View
4 activerecord/test/cases/multiple_db_test.rb
@@ -1,9 +1,7 @@
require "cases/helper"
require 'models/entrant'
require 'models/bird'
-
-# So we can test whether Course.connection survives a reload.
-require_dependency 'models/course'
+require 'models/course'
class MultipleDbTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
View
28 activerecord/test/cases/timestamp_test.rb
@@ -114,9 +114,12 @@ def test_destroying_a_record_with_a_belongs_to_that_specifies_touching_the_paren
end
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_attribute_the_parent_should_update_that_attribute
- Pet.belongs_to :owner, :touch => :happy_at
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Pet'; end
+ belongs_to :owner, :touch => :happy_at
+ end
- pet = Pet.first
+ pet = klass.first
owner = pet.owner
previously_owner_happy_at = owner.happy_at
@@ -124,14 +127,15 @@ def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_at
pet.save
assert_not_equal previously_owner_happy_at, pet.owner.happy_at
- ensure
- Pet.belongs_to :owner, :touch => true
end
def test_touching_a_record_with_a_belongs_to_that_uses_a_counter_cache_should_update_the_parent
- Pet.belongs_to :owner, :counter_cache => :use_count, :touch => true
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Pet'; end
+ belongs_to :owner, :counter_cache => :use_count, :touch => true
+ end
- pet = Pet.first
+ pet = klass.first
owner = pet.owner
owner.update_columns(happy_at: 3.days.ago)
previously_owner_updated_at = owner.updated_at
@@ -140,15 +144,15 @@ def test_touching_a_record_with_a_belongs_to_that_uses_a_counter_cache_should_up
pet.save
assert_not_equal previously_owner_updated_at, pet.owner.updated_at
- ensure
- Pet.belongs_to :owner, :touch => true
end
def test_touching_a_record_touches_parent_record_and_grandparent_record
- Toy.belongs_to :pet, :touch => true
- Pet.belongs_to :owner, :touch => true
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ belongs_to :pet, :touch => true
+ end
- toy = Toy.first
+ toy = klass.first
pet = toy.pet
owner = pet.owner
time = 3.days.ago
@@ -158,8 +162,6 @@ def test_touching_a_record_touches_parent_record_and_grandparent_record
owner.reload
assert_not_equal time, owner.updated_at
- ensure
- Toy.belongs_to :pet
end
def test_timestamp_attributes_for_create
View
2  activerecord/test/models/author.rb
@@ -93,8 +93,8 @@ def testing_proxy_target
has_many :author_favorites
has_many :favorite_authors, -> { order('name') }, :through => :author_favorites
- has_many :tagging, :through => :posts
has_many :taggings, :through => :posts
+ has_many :taggings_2, :through => :posts, :source => :tagging
has_many :tags, :through => :posts
has_many :post_categories, :through => :posts, :source => :categories
has_many :tagging_tags, :through => :taggings, :source => :tag
View
5 activerecord/test/models/member.rb
@@ -24,11 +24,10 @@ class Member < ActiveRecord::Base
has_one :club_category, :through => :club, :source => :category
- has_many :current_memberships
- has_one :club_through_many, :through => :current_memberships, :source => :club
-
has_many :current_memberships, -> { where :favourite => true }
has_many :clubs, :through => :current_memberships
+
+ has_one :club_through_many, :through => :current_memberships, :source => :club
end
class SelfMember < ActiveRecord::Base
View
4 activerecord/test/support/connection.rb
@@ -1,6 +1,6 @@
require 'active_support/logger'
-require_dependency 'models/college'
-require_dependency 'models/course'
+require 'models/college'
+require 'models/course'
module ARTest
def self.connection_name
View
50 activesupport/CHANGELOG.md
@@ -1,5 +1,28 @@
## Rails 4.0.0 (unreleased) ##
+* Add default values to all `ActiveSupport::NumberHelper` methods, to avoid
+ errors with empty locales or missing values.
+
+ *Carlos Antonio da Silva*
+
+* ActiveSupport::JSON::Variable is deprecated. Define your own #as_json and
+ #encode_json methods for custom JSON string literals.
+
+ *Erich Menge*
+
+* Add String#indent. *fxn & Ace Suares*
+
+* Inflections can now be defined per locale. `singularize` and `pluralize`
+ accept locale as an extra argument.
+
+ *David Celis*
+
+* `Object#try` will now return nil instead of raise a NoMethodError if the
+ receiving object does not implement the method, but you can still get the
+ old behavior by using the new `Object#try!`.
+
+ *DHH*
+
* `ERB::Util.html_escape` now escapes single quotes. *Santiago Pastorino*
* `Time#change` now works with time values with offsets other than UTC or the local time zone. *Andrew White*
@@ -82,6 +105,33 @@
* Remove deprecated ActiveSupport::JSON::Variable. *Erich Menge*
+## Rails 3.2.8 (Aug 9, 2012) ##
+
+* Fix ActiveSupport integration with Mocha > 0.12.1. *Mike Gunderloy*
+
+* Reverted the deprecation of ActiveSupport::JSON::Variable. *Rafael Mendonça França*
+
+* ERB::Util.html_escape now escapes single quotes. *Santiago Pastorino*
+
+
+## Rails 3.2.7 (Jul 26, 2012) ##
+
+* Hash#fetch(fetch) is not the same as doing hash[key]
+
+* adds a missing require [fixes #6896]
+
+* make sure the inflection rules are loaded when cherry-picking active_support/core_ext/string/inflections.rb [fixes #6884]
+
+* Merge pull request #6857 from rsutphin/as_core_ext_time_missing_require
+
+* bump AS deprecation_horizon to 4.0
+
+
+## Rails 3.2.6 (Jun 12, 2012) ##
+
+* No changes.
+
+
## Rails 3.2.5 (Jun 1, 2012) ##
* ActiveSupport::JSON::Variable is deprecated. Define your own #as_json and #encode_json methods
View
4 activesupport/lib/active_support/core_ext/object/try.rb
@@ -37,7 +37,7 @@ def try(*a, &b)
public_send(*a, &b) if respond_to?(a.first)
end
end
-
+
# Same as #try, but will raise a NoMethodError exception if the receiving is not nil and
# does not implemented the tried method.
def try!(*a, &b)
@@ -63,7 +63,7 @@ class NilClass
def try(*args)
nil
end
-
+
def try!(*args)
nil
end
View
10 activesupport/lib/active_support/locale/en.yml
@@ -49,7 +49,7 @@ en:
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:
@@ -62,7 +62,7 @@ en:
precision: 2
significant: false
strip_insignificant_zeros: false
-
+
# Used in NumberHelper.number_to_percentage()
percentage:
format:
@@ -73,7 +73,7 @@ en:
# significant: false
# strip_insignificant_zeros: false
format: "%n%"
-
+
# Used in NumberHelper.number_to_rounded()
precision:
format:
@@ -83,7 +83,7 @@ en:
# precision:
# significant: false
# strip_insignificant_zeros: false
-
+
# Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human()
human:
format:
@@ -131,5 +131,3 @@ en:
billion: Billion
trillion: Trillion
quadrillion: Quadrillion
-
-
View
161 activesupport/lib/active_support/number_helper.rb
@@ -7,12 +7,108 @@ module ActiveSupport
module NumberHelper
extend self
+ DEFAULTS = {
+ # Used in 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 number_to_currency
+ currency: {
+ format: {
+ format: "%u%n",
+ negative_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: {
+ delimiter: "",
+ format: "%n%"
+ }
+ },
+
+ # Used in number_to_rounded
+ precision: {
+ format: {
+ delimiter: ""
+ }
+ },
+
+ # Used in number_to_human_size and number_to_human
+ human: {
+ format: {
+ # These five are to override number.format and are optional
+ 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: "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"
+ }
+ }
+ }
+ }
+
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 }
- DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
- :precision => 2, :significant => false, :strip_insignificant_zeros => false }
-
STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
# Formats a +number+ into a US phone number (e.g., (555)
@@ -106,10 +202,10 @@ def number_to_currency(number, options = {})
return unless number
options = options.symbolize_keys
- currency = translations_for('currency', options[:locale])
+ currency = i18n_format_options(options[:locale], :currency)
currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
- defaults = DEFAULT_CURRENCY_VALUES.merge(defaults_translations(options[:locale])).merge!(currency)
+ defaults = default_format_options(:currency).merge!(currency)
defaults[:negative_format] = "-" + options[:format] if options[:format]
options = defaults.merge!(options)
@@ -160,7 +256,7 @@ def number_to_percentage(number, options = {})
return unless number
options = options.symbolize_keys
- defaults = format_translations('percentage', options[:locale])
+ defaults = format_options(options[:locale], :percentage)
options = defaults.merge!(options)
format = options[:format] || "%n%"
@@ -197,7 +293,7 @@ def number_to_delimited(number, options = {})
return number unless valid_float?(number)
- options = defaults_translations(options[:locale]).merge(options)
+ options = format_options(options[:locale]).merge!(options)
parts = number.to_s.to_str.split('.')
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
@@ -248,7 +344,7 @@ def number_to_rounded(number, options = {})
number = Float(number)
options = options.symbolize_keys
- defaults = format_translations('precision', options[:locale])
+ defaults = format_options(options[:locale], :precision)
options = defaults.merge!(options)
precision = options.delete :precision
@@ -328,18 +424,18 @@ def number_to_human_size(number, options = {})
return number unless valid_float?(number)
number = Float(number)
- defaults = format_translations('human', options[:locale])
+ defaults = format_options(options[:locale], :human)
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)
+ storage_units_format = translate_number_value_with_default('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)
+ unit = translate_number_value_with_default('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
@@ -348,7 +444,7 @@ def number_to_human_size(number, options = {})
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)
+ unit = translate_number_value_with_default("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)
@@ -458,7 +554,7 @@ def number_to_human(number, options = {})
return number unless valid_float?(number)
number = Float(number)
- defaults = format_translations('human', options[:locale])
+ defaults = format_options(options[:locale], :human)
options = defaults.merge!(options)
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
@@ -473,7 +569,7 @@ def number_to_human(number, options = {})
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)
+ translate_number_value_with_default("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}
@@ -488,34 +584,47 @@ def number_to_human(number, options = {})
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)
+ translate_number_value_with_default("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")
+ decimal_format = options[:format] || translate_number_value_with_default('human.decimal_units.format', :locale => options[:locale])
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)
+ def self.private_module_and_instance_method(method_name) #:nodoc:
private method_name
private_class_method method_name
end
private_class_method :private_module_and_instance_method
- def format_translations(namespace, locale) #:nodoc:
- defaults_translations(locale).merge(translations_for(namespace, locale))
+ def format_options(locale, namespace = nil) #:nodoc:
+ default_format_options(namespace).merge!(i18n_format_options(locale, namespace))
+ end
+ private_module_and_instance_method :format_options
+
+ def default_format_options(namespace = nil) #:nodoc:
+ options = DEFAULTS[:format].dup
+ options.merge!(DEFAULTS[namespace][:format]) if namespace
+ options
end
- private_module_and_instance_method :format_translations
+ private_module_and_instance_method :default_format_options
- def defaults_translations(locale) #:nodoc:
- I18n.translate(:'number.format', :locale => locale, :default => {})
+ def i18n_format_options(locale, namespace = nil) #:nodoc:
+ options = I18n.translate(:'number.format', locale: locale, default: {}).dup
+ if namespace
+ options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {}))
+ end
+ options
end
- private_module_and_instance_method :defaults_translations
+ private_module_and_instance_method :i18n_format_options
+
+ def translate_number_value_with_default(key, i18n_options = {}) #:nodoc:
+ default = key.split('.').reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] }
- def translations_for(namespace, locale) #:nodoc:
- I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {})
+ I18n.translate(key, { default: default, scope: :number }.merge!(i18n_options))
end
- private_module_and_instance_method :translations_for
+ private_module_and_instance_method :translate_number_value_with_default
def valid_float?(number) #:nodoc:
Float(number)
View
42 activesupport/test/number_helper_i18n_test.rb
@@ -56,6 +56,13 @@ def test_number_to_currency_with_empty_i18n_store
assert_equal("-$10.00", number_to_currency(-10, :locale => 'empty'))
end
+ def test_locale_default_format_has_precedence_over_helper_defaults
+ I18n.backend.store_translations 'ts',
+ { :number => { :format => { :separator => ";" } } }
+
+ assert_equal("&$ - 10;00", number_to_currency(10, :locale => 'ts'))
+ end
+
def test_number_to_currency_without_currency_negative_format
I18n.backend.store_translations 'no_negative_format', :number => {
:currency => { :format => { :unit => '@', :format => '%n %u' } }
@@ -72,11 +79,24 @@ def test_number_with_i18n_precision
assert_equal("1.00", number_to_rounded(1.0, :locale => 'ts'))
end
+ def test_number_with_i18n_precision_and_empty_i18n_store
+ I18n.backend.store_translations 'empty', {}
+
+ assert_equal("123456789.123", number_to_rounded(123456789.123456789, :locale => 'empty'))
+ assert_equal("1.000", number_to_rounded(1.0000, :locale => 'empty'))
+ end
+
def test_number_with_i18n_delimiter
#Delimiter "," and separator "."
assert_equal("1,000,000.234", number_to_delimited(1000000.234, :locale => 'ts'))
end
+ def test_number_with_i18n_delimiter_and_empty_i18n_store
+ I18n.backend.store_translations 'empty', {}
+
+ assert_equal("1,000,000.234", number_to_delimited(1000000.234, :locale => 'empty'))
+ end
+
def test_number_to_i18n_percentage
# to see if strip_insignificant_zeros is true
assert_equal("1%", number_to_percentage(1, :locale => 'ts'))
@@ -86,12 +106,27 @@ def test_number_to_i18n_percentage
assert_equal("12434%", number_to_percentage(12434, :locale => 'ts'))
end
+ def test_number_to_i18n_percentage_and_empty_i18n_store
+ I18n.backend.store_translations 'empty', {}
+
+ assert_equal("1.000%", number_to_percentage(1, :locale => 'empty'))
+ assert_equal("1.243%", number_to_percentage(1.2434, :locale => 'empty'))
+ assert_equal("12434.000%", number_to_percentage(12434, :locale => 'empty'))
+ end
+
def test_number_to_i18n_human_size
#b for bytes and k for kbytes
assert_equal("2 k", number_to_human_size(2048, :locale => 'ts'))
assert_equal("42 b", number_to_human_size(42, :locale => 'ts'))
end
+ def test_number_to_i18n_human_size_with_empty_i18n_store
+ I18n.backend.store_translations 'empty', {}
+
+ assert_equal("2 KB", number_to_human_size(2048, :locale => 'empty'))
+ assert_equal("42 Bytes", number_to_human_size(42, :locale => 'empty'))
+ end
+
def test_number_to_human_with_default_translation_scope
#Using t for thousand
assert_equal "2 t", number_to_human(2000, :locale => 'ts')
@@ -106,6 +141,13 @@ def test_number_to_human_with_default_translation_scope
assert_equal "2 Tens", number_to_human(20, :locale => 'ts')
end
+ def test_number_to_human_with_empty_i18n_store
+ I18n.backend.store_translations 'empty', {}
+
+ assert_equal "2 Thousand", number_to_human(2000, :locale => 'empty')
+ assert_equal "1.23 Billion", number_to_human(1234567890, :locale => 'empty')
+ end
+
def test_number_to_human_with_custom_translation_scope
#Significant was set to true with precision 2, with custom translated units
assert_equal "4.3 cm", number_to_human(0.0432, :locale => 'ts', :units => :custom_units_for_number_to_human)
View
7 activesupport/test/number_helper_test.rb
@@ -4,7 +4,7 @@
module ActiveSupport
module NumberHelper
class NumberHelperTest < ActiveSupport::TestCase
-
+
class TestClassWithInstanceNumberHelpers
include ActiveSupport::NumberHelper
end
@@ -16,7 +16,7 @@ class TestClassWithClassNumberHelpers
def setup
@instance_with_helpers = TestClassWithInstanceNumberHelpers.new
end
-
+
def kilobytes(number)
number * 1024
end
@@ -362,14 +362,13 @@ def test_number_helpers_should_return_non_numeric_param_unchanged
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
View
2  guides/source/4_0_release_notes.textile
@@ -747,6 +747,8 @@ h3. Active Resource
h3. Active Support
+* Add default values to all <tt>ActiveSupport::NumberHelper</tt> methods, to avoid errors with empty locales or missing values.
+
* <tt>Time#change</tt> now works with time values with offsets other than UTC or the local time zone.
* Add <tt>Time#prev_quarter</tt> and <tt>Time#next_quarter</tt> short-hands for <tt>months_ago(3)</tt> and <tt>months_since(3)</tt>.
View
18 guides/source/association_basics.textile
@@ -967,10 +967,13 @@ end
h6(#has_one-dependent). +:dependent+
-If you set the +:dependent+ option to +:destroy+, then deleting this object will call the +destroy+ method on the associated object to delete that object. If you set the +:dependent+ option to +:delete+, then deleting this object will delete the associated object _without_ calling its +destroy+ method. If you set the +:dependent+ option to +:nullify+, then deleting this object will set the foreign key in the association object to +NULL+.
-If you set the +:dependent+ option to +:restrict+, then the deletion of the object is restricted if a dependent associated object exist and a +DeleteRestrictionError+ exception is raised.
+Controls what happens to the associated object when its owner is destroyed:
-NOTE: The default behavior for +:dependent => :restrict+ is to raise a +DeleteRestrictionError+ when associated objects exist. Since Rails 4.0 this behavior is being deprecated in favor of adding an error to the base model. To silence the warning in Rails 4.0, you should fix your code to not expect this Exception and add +config.active_record.dependent_restrict_raises = false+ to your application config.
+* +:destroy+ causes the associated object to also be destroyed
+* +:delete+ causes the asssociated object to be deleted directly from the database (so callbacks will not execute)
+* +:nullify+ causes the foreign key to be set to +NULL+. Callbacks are not executed.
+* +:restrict_with_exception+ causes an exception to be raised if there is an associated record
+* +:restrict_with_error+ causes an error to be added to the owner if there is an associated object
h6(#has_one-foreign_key). +:foreign_key+
@@ -1307,10 +1310,13 @@ end
h6(#has_many-dependent). +:dependent+
-If you set the +:dependent+ option to +:destroy+, then deleting this object will call the +destroy+ method on the associated objects to delete those objects. If you set the +:dependent+ option to +:delete_all+, then deleting this object will delete the associated objects _without_ calling their +destroy+ method. If you set the +:dependent+ option to +:nullify+, then deleting this object will set the foreign key in the associated objects to +NULL+.
-If you set the +:dependent+ option to +:restrict+, then the deletion of the object is restricted if a dependent associated object exist and a +DeleteRestrictionError+ exception is raised.
+Controls what happens to the associated objects when their owner is destroyed:
-NOTE: The default behavior for +:dependent => :restrict+ is to raise a +DeleteRestrictionError+ when associated objects exist. Since Rails 4.0 this behavior is being deprecated in favor of adding an error to the base model. To silence the warning in Rails 4.0, you should fix your code to not expect this Exception and add +config.active_record.dependent_restrict_raises = false+ to your application config.
+* +:destroy+ causes all the associated objects to also be destroyed
+* +:delete_all+ causes all the asssociated objects to be deleted directly from the database (so callbacks will not execute)
+* +:nullify+ causes the foreign keys to be set to +NULL+. Callbacks are not executed.
+* +:restrict_with_exception+ causes an exception to be raised if there are any associated records
+* +:restrict_with_error+ causes an error to be added to the owner if there are any associated objects
NOTE: This option is ignored when you use the +:through+ option on the association.
View
8 guides/source/configuring.textile
@@ -286,8 +286,6 @@ h4. Configuring Active Record
* +config.active_record.auto_explain_threshold_in_seconds+ configures the threshold for automatic EXPLAINs (+nil+ disables this feature). Queries exceeding the threshold get their query plan logged. Default is 0.5 in development mode.
-* +config.active_record.dependent_restrict_raises+ will control the behavior when an object with a <tt>:dependent => :restrict</tt> association is deleted. Setting this to false will prevent +DeleteRestrictionError+ from being raised and instead will add an error on the model object. Defaults to false in the development mode.
-
* +config.active_record.mass_assignment_sanitizer+ will determine the strictness of the mass assignment sanitization within Rails. Defaults to +:strict+. In this mode, mass assigning any non-+attr_accessible+ attribute in a +create+ or +update_attributes+ call will raise an exception. Setting this option to +:logger+ will only print to the log file when an attribute is being assigned and will not raise an exception.
The MySQL adapter adds one additional configuration option:
@@ -340,6 +338,12 @@ h4. Configuring Action Dispatch
* +config.action_dispatch.session_store+ sets the name of the store for session data. The default is +:cookie_store+; other valid options include +:active_record_store+, +:mem_cache_store+ or the name of your own custom class.
+* +config.action_dispatch.default_headers+ is a hash with HTTP headers that are set by default in each response. By default, this is defined as:
+
+<ruby>
+config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block' }
+</ruby>
+
* +config.action_dispatch.tld_length+ sets the TLD (top-level domain) length for the application. Defaults to +1+.
* +ActionDispatch::Callbacks.before+ takes a block of code to run before the request.
View
25 guides/source/contributing_to_ruby_on_rails.textile
@@ -359,6 +359,31 @@ Rails follows a simple set of coding style conventions.
The above are guidelines -- please use your best judgment in using them.
+h4. Updating the CHANGELOG
+
+The CHANGELOG is an important part of every release. It keeps the list of changes for every Rails version.
+
+You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, commiting a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG.
+
+A CHANGELOG entry should summarize what was changed and should end with author's name. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach issue's number. Here is an example CHANGELOG entry:
+
+<plain>
+* Summary of a change that briefly describes what was changed. You can use multiple
+ lines and wrap them at around 80 characters. Code examples are ok, too, if needed:
+
+ class Foo
+ def bar
+ puts 'baz'
+ end
+ end
+
+ You can continue after the code example and you can attach issue number. GH#1234
+
+ * Your Name *
+</plain>
+
+Your name can be added directly after the last word if you don't provide any code examples or don't need multiple paragraphs. Otherwise, it's best to make as a new paragraph.
+
h4. Sanity Check
You should not be the only person who looks at the code before you submit it. You know at least one other Rails developer, right? Show them what you’re doing and ask for feedback. Doing this in private before you push a patch out publicly is the “smoke test” for a patch: if you can’t convince one other developer of the beauty of your code, you’re unlikely to convince the core team either.
View
5 railties/CHANGELOG.md
@@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##
+* Correctly handle SCRIPT_NAME when generating routes to engine in application
+ that's mounted at a sub-uri. With this behavior, you *should not* use
+ default_url_options[:script_name] to set proper application's mount point by
+ yourself. *Piotr Sarnacki*
+
* The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance
rails g migration AddReferencesToProducts user:references supplier:references{polymorphic}
View
6 railties/lib/rails/engine.rb
@@ -494,7 +494,11 @@ def endpoint
# Define the Rack API for this engine.
def call(env)
- app.call(env.merge!(env_config))
+ env.merge!(env_config)
+ if env['SCRIPT_NAME']
+ env.merge! "ROUTES_#{routes.object_id}_SCRIPT_NAME" => env['SCRIPT_NAME'].dup
+ end
+ app.call(env)
end
# Defines additional Rack env configuration that is added on each call.
View
5 railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -41,11 +41,6 @@ class Application < Rails::Application
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
- config.action_dispatch.default_headers = {
- 'X-Frame-Options' => 'SAMEORIGIN',
- 'X-XSS-Protection' => '1; mode=block'
- }
-
# Use SQL instead of Active Record's schema dumper when creating the database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types.
View
48 railties/test/railties/engine_test.rb
@@ -1193,6 +1193,54 @@ def index
last_response.body.split("\n").map(&:strip)
end
+ test "paths are properly generated when application is mounted at sub-path" do
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ module Bukkits
+ class Engine < ::Rails::Engine
+ isolate_namespace Bukkits
+ end
+ end
+ RUBY
+
+ app_file "app/controllers/bar_controller.rb", <<-RUBY
+ class BarController < ApplicationController
+ def index
+ render :text => bukkits.bukkit_path
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get '/bar' => 'bar#index', :as => 'bar'
+ mount Bukkits::Engine => "/bukkits", :as => "bukkits"
+ end
+ RUBY
+
+ @plugin.write "config/routes.rb", <<-RUBY
+ Bukkits::Engine.routes.draw do
+ get '/bukkit' => 'bukkit#index'
+ end
+ RUBY
+
+
+ @plugin.write "app/controllers/bukkits/bukkit_controller.rb", <<-RUBY
+ class Bukkits::BukkitController < ActionController::Base
+ def index
+ render :text => main_app.bar_path
+ end
+ end
+ RUBY
+
+ boot_rails
+
+ get("/bukkits/bukkit", {}, {'SCRIPT_NAME' => '/foo'})
+ assert_equal '/foo/bar', last_response.body
+
+ get("/bar", {}, {'SCRIPT_NAME' => '/foo'})
+ assert_equal '/foo/bukkits/bukkit', last_response.body
+ end
+
private
def app
Rails.application
View
15 railties/test/railties/mounted_engine_test.rb
@@ -163,24 +163,14 @@ def app
end
end
- def reset_script_name!
- Rails.application.routes.default_url_options = {}
- end
-
- def script_name(script_name)
- Rails.application.routes.default_url_options = {:script_name => script_name}
- end
-
test "routes generation in engine and application" do
# test generating engine's route from engine
get "/john/blog/posts"
assert_equal "/john/blog/posts/1", last_response.body
# test generating engine's route from engine with default_url_options
- script_name "/foo"
get "/john/blog/posts", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/foo/john/blog/posts/1", last_response.body
- reset_script_name!
# test generating engine's route from application
get "/engine_route"
@@ -193,14 +183,11 @@ def script_name(script_name)
assert_equal "/john/blog/posts", last_response.body
# test generating engine's route from application with default_url_options
- script_name "/foo"
get "/engine_route", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/foo/anonymous/blog/posts", last_response.body
- script_name "/foo"
get "/url_for_engine_route", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/foo/john/blog/posts", last_response.body
- reset_script_name!
# test generating application's route from engine
get "/someone/blog/generate_application_route"
@@ -210,10 +197,8 @@ def script_name(script_name)
assert_equal "/", last_response.body
# test generating application's route from engine with default_url_options
- script_name "/foo"
get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo'
assert_equal "/foo/", last_response.body
- reset_script_name!
# test polymorphic routes
get "/polymorphic_route"
Please sign in to comment.
Something went wrong with that request. Please try again.