diff options
49 files changed, 315 insertions, 106 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index adc5eb6914..6d3cf072c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,14 @@ Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the team](http://contributors.rubyonrails.org)! -Please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide before submitting any code. +* If you want to submit a bug report please make sure to follow our [reporting guidelines](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#reporting-an-issue). + +* If you want to submit a patch, please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide. *We only accept bug reports and pull requests in GitHub*. -If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk). +* If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk). -If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code. +* If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code. Thanks! :heart: :heart: :heart: <br /> Rails Team diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index b23d0668d3..69096443fe 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,10 @@ ## Rails 4.0.0 (unreleased) ## +* Include I18n locale fallbacks in view lookup. + Fixes GH#3512. + + *Juan Barreneche* + * Integration and functional tests allow headers and rack env variables to be passed when performing requests. Fixes #6513. diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index f945a5c1a2..246d9c121a 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -18,7 +18,7 @@ module ActionDispatch query_parameters.dup end params.merge!(path_parameters) - encode_params(params).with_indifferent_access + params.with_indifferent_access end end alias :params :parameters @@ -50,40 +50,33 @@ module ActionDispatch private + # Convert nested Hash to HashWithIndifferentAccess + # and UTF-8 encode both keys and values in nested Hash. + # # TODO: Validate that the characters are UTF-8. If they aren't, # you'll get a weird error down the road, but our form handling # should really prevent that from happening - def encode_params(params) + def normalize_encode_params(params) if params.is_a?(String) return params.force_encoding(Encoding::UTF_8).encode! elsif !params.is_a?(Hash) return params end - params.each_value do |v| - case v - when Hash - encode_params(v) - when Array - v.map! {|el| encode_params(el) } - else - encode_params(v) - end - end - end - - # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess - def normalize_parameters(value) - case value - when Hash - h = {} - value.each { |k, v| h[k] = normalize_parameters(v) } - h.with_indifferent_access - when Array - value.map { |e| normalize_parameters(e) } - else - value + new_hash = {} + params.each do |k, v| + new_key = k.is_a?(String) ? k.dup.force_encoding("UTF-8").encode! : k + new_hash[new_key] = + case v + when Hash + normalize_encode_params(v) + when Array + v.map! {|el| normalize_encode_params(el) } + else + normalize_encode_params(v) + end end + new_hash.with_indifferent_access end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 97a9b7be86..aff2172788 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -273,7 +273,7 @@ module ActionDispatch # Override Rack's GET method to support indifferent access def GET - @env["action_dispatch.request.query_parameters"] ||= (normalize_parameters(super) || {}) + @env["action_dispatch.request.query_parameters"] ||= (normalize_encode_params(super) || {}) rescue TypeError => e raise ActionController::BadRequest.new(:query, e) end @@ -281,7 +281,7 @@ module ActionDispatch # Override Rack's POST method to support indifferent access def POST - @env["action_dispatch.request.request_parameters"] ||= (normalize_parameters(super) || {}) + @env["action_dispatch.request.request_parameters"] ||= (normalize_encode_params(super) || {}) rescue TypeError => e raise ActionController::BadRequest.new(:request, e) end diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 67cb7fbcb5..319d0197d1 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -75,16 +75,16 @@ module ActionDispatch end module Upload # :nodoc: - # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess and replace - # file upload hash with UploadedFile objects - def normalize_parameters(value) + # Replace file upload hash with UploadedFile objects + # when normalize and encode parameters. + def normalize_encode_params(value) if Hash === value && value.has_key?(:tempfile) UploadedFile.new(value) else super end end - private :normalize_parameters + private :normalize_encode_params end end end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 3dae1fc87a..6b6a7edc1d 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -433,7 +433,7 @@ module ActionView builder = instantiate_builder(object_name, object, options) output = capture(builder, &block) - html_options[:multipart] = builder.multipart? + html_options[:multipart] ||= builder.multipart? form_tag(options[:url] || {}, html_options) { output } end diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 4e4816d983..d61cc0f304 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -43,7 +43,13 @@ module ActionView module Accessors #:nodoc: end - register_detail(:locale) { [I18n.locale, I18n.default_locale].uniq } + register_detail(:locale) do + locales = [I18n.locale] + locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks + locales << I18n.default_locale + locales.uniq! + locales + end register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] } register_detail(:handlers){ Template::Handlers.extensions } diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 720890eeb9..946db1df79 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -324,7 +324,8 @@ module ActionView end def locals_code #:nodoc: - @locals.map { |key| "#{key} = local_assigns[:#{key}];" }.join + # Double assign to suppress the dreaded 'assigned but unused variable' warning + @locals.map { |key| "#{key} = #{key} = local_assigns[:#{key}];" }.join end def method_name #:nodoc: diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb index bac1d02977..6b02eedaed 100644 --- a/actionpack/test/controller/localized_templates_test.rb +++ b/actionpack/test/controller/localized_templates_test.rb @@ -25,4 +25,13 @@ class LocalizedTemplatesTest < ActionController::TestCase ensure I18n.locale = old_locale end + + def test_use_fallback_locales + I18n.locale = :"de-AT" + I18n.backend.class.send(:include, I18n::Backend::Fallbacks) + I18n.fallbacks[:"de-AT"] = [:de] + + get :hello_world + assert_equal "Gutten Tag", @response.body + end end diff --git a/actionpack/test/controller/output_escaping_test.rb b/actionpack/test/controller/output_escaping_test.rb index 43a8c05cda..c3c549fbfc 100644 --- a/actionpack/test/controller/output_escaping_test.rb +++ b/actionpack/test/controller/output_escaping_test.rb @@ -11,8 +11,6 @@ class OutputEscapingTest < ActiveSupport::TestCase end test "escapeHTML shouldn't touch explicitly safe strings" do - # TODO this seems easier to compose and reason about, but - # this should be verified assert_equal "<", ERB::Util.h("<".html_safe) end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 9c6d02a62d..38b9794b4d 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -58,7 +58,7 @@ class TestCaseTest < ActionController::TestCase end def test_headers - render text: JSON.dump(request.headers.env) + render text: request.headers.env.to_json end def test_html_output diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 399f15199c..3c30a705e9 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -1,13 +1,15 @@ +# encoding: utf-8 require 'abstract_unit' class MultipartParamsParsingTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base class << self - attr_accessor :last_request_parameters + attr_accessor :last_request_parameters, :last_parameters end def parse self.class.last_request_parameters = request.request_parameters + self.class.last_parameters = request.parameters head :ok end @@ -30,6 +32,23 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest assert_equal({ 'foo' => { 'baz' => 'bar'}}, parse_multipart('bracketed_param')) end + test "parse single utf8 parameter" do + assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => 'Iñtërnâtiônàlizætiøn_value'}, + parse_multipart('single_utf8_param'), "request.request_parameters") + assert_equal( + 'Iñtërnâtiônàlizætiøn_value', + TestController.last_parameters['Iñtërnâtiônàlizætiøn_name'], "request.parameters") + end + + test "parse bracketed utf8 parameter" do + assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => { + 'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'} }, + parse_multipart('bracketed_utf8_param'), "request.request_parameters") + assert_equal( + {'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'}, + TestController.last_parameters['Iñtërnâtiônàlizætiøn_name'], "request.parameters") + end + test "parses text file" do params = parse_multipart('text_file') assert_equal %w(file foo), params.keys.sort diff --git a/actionpack/test/fixtures/multipart/bracketed_utf8_param b/actionpack/test/fixtures/multipart/bracketed_utf8_param new file mode 100644 index 0000000000..976ca44a45 --- /dev/null +++ b/actionpack/test/fixtures/multipart/bracketed_utf8_param @@ -0,0 +1,5 @@ +--AaB03x
+Content-Disposition: form-data; name="Iñtërnâtiônàlizætiøn_name[Iñtërnâtiônàlizætiøn_nested_name]"
+
+Iñtërnâtiônàlizætiøn_value
+--AaB03x--
diff --git a/actionpack/test/fixtures/multipart/single_utf8_param b/actionpack/test/fixtures/multipart/single_utf8_param new file mode 100644 index 0000000000..b86f62d1e1 --- /dev/null +++ b/actionpack/test/fixtures/multipart/single_utf8_param @@ -0,0 +1,5 @@ +--AaB03x
+Content-Disposition: form-data; name="Iñtërnâtiônàlizætiøn_name"
+
+Iñtërnâtiônàlizætiøn_value
+--AaB03x--
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 268bab6ad2..dff0b8bdc2 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -2791,8 +2791,8 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_html_options_adds_options_to_form_tag - form_for(@post, html: { id: 'some_form', class: 'some_class' }) do |f| end - expected = whole_form("/posts/123", "some_form", "some_class", method: "patch") + form_for(@post, html: { id: 'some_form', class: 'some_class', multipart: true }) do |f| end + expected = whole_form("/posts/123", "some_form", "some_class", method: "patch", multipart: "multipart/form-data") assert_dom_equal expected, output_buffer end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 5d87c96605..e359f47975 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -51,7 +51,6 @@ class UrlHelperTest < ActiveSupport::TestCase assert_equal 'javascript:history.back()', url_for(:back) end - # TODO: missing test cases def test_button_to_with_straight_url assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com") end diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb index 086e7266ff..c4c34b0be7 100644 --- a/activemodel/test/cases/callbacks_test.rb +++ b/activemodel/test/cases/callbacks_test.rb @@ -107,7 +107,7 @@ class CallbacksTest < ActiveModel::TestCase test "after_create callbacks with both callbacks declared in one line" do assert_equal ["callback1", "callback2"], Violin1.new.create.history end - test "after_create callbacks with both callbacks declared in differnt lines" do + test "after_create callbacks with both callbacks declared in different lines" do assert_equal ["callback1", "callback2"], Violin2.new.create.history end diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index 457f553661..daa7a8d5c4 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -50,7 +50,7 @@ class ValidatesWithTest < ActiveModel::TestCase end end - test "vaidation with class that adds errors" do + test "validation with class that adds errors" do Topic.validates_with(ValidatorThatAddsErrors) topic = Topic.new assert topic.invalid?, "A class that adds errors causes the record to be invalid" diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index d3723fa45b..2934e70c2f 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -26,11 +26,11 @@ class ValidationsTest < ActiveModel::TestCase def test_single_field_validation r = Reply.new r.title = "There's no content!" - assert r.invalid?, "A reply without content shouldn't be saveable" + assert r.invalid?, "A reply without content shouldn't be savable" assert r.after_validation_performed, "after_validation callback should be called" r.content = "Messa content!" - assert r.valid?, "A reply with content should be saveable" + assert r.valid?, "A reply with content should be savable" assert r.after_validation_performed, "after_validation callback should be called" end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index a8151cd23e..e5ab6bac58 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,51 @@ ## Rails 4.0.0 (unreleased) ## +* `rake db:create` does not change permissions of the MySQL root user. + Fixes #8079. + + *Yves Senn* + +* The length of the `version` column in the `schema_migrations` table + created by the `mysql2` adapter is 191 if the encoding is "utf8mb4". + + The "utf8" encoding in MySQL has support for a maximum of 3 bytes per character, + and only contains characters from the BMP. The recently added + [utf8mb4](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html) + encoding extends the support to four bytes. As of this writing, said encoding + is supported in the betas of the `mysql2` gem. + + Setting the encoding to "utf8mb4" has + [a few implications](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-upgrading.html). + This change addresses the max length for indexes, which is 191 instead of 255. + + *Xavier Noria* + +* Counter caches on associations will now stay valid when attributes are + updated (not just when records are created or destroyed), for example, + when calling `update_attributes`. The following code now works: + + class Comment < ActiveRecord::Base + belongs_to :post, counter_cache: true + end + + class Post < ActiveRecord::Base + has_many :comments + end + + post = Post.create + comment = Comment.create + + post.comments << comment + post.save.reload.comments_count # => 1 + comment.update_attributes(post_id: nil) + + post.save.reload.comments_count # => 0 + + Updating the id of a `belongs_to` object with the id of a new object will + also keep the count accurate. + + *John Wang* + * Referencing join tables implicitly was deprecated. There is a possibility that these deprecation warnings are shown even if you don't make use of that feature. You can now disable the feature entirely. diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 97b1ff18e2..fbcb21118d 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -21,11 +21,13 @@ module ActiveRecord::Associations::Builder def add_counter_cache_callbacks(reflection) cache_column = reflection.counter_cache_column + foreign_key = reflection.foreign_key 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? + @_after_create_counter_called = true end def belongs_to_counter_cache_before_destroy_for_#{name} @@ -34,10 +36,28 @@ module ActiveRecord::Associations::Builder record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil? end end + + def belongs_to_counter_cache_after_update_for_#{name} + if (@_after_create_counter_called ||= false) + @_after_create_counter_called = false + elsif self.#{foreign_key}_changed? && !new_record? && defined?(#{name.to_s.camelize}) + model = #{name.to_s.camelize} + foreign_key_was = self.#{foreign_key}_was + foreign_key = self.#{foreign_key} + + if foreign_key && model.respond_to?(:increment_counter) + model.increment_counter(:#{cache_column}, foreign_key) + end + if foreign_key_was && model.respond_to?(:decrement_counter) + model.decrement_counter(:#{cache_column}, foreign_key_was) + end + end + end CODE model.after_create "belongs_to_counter_cache_after_create_for_#{name}" model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}" + model.after_update "belongs_to_counter_cache_after_update_for_#{name}" klass = reflection.class_name.safe_constantize klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 9c659dd0af..902dbd148e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -25,7 +25,7 @@ module ActiveRecord end def primary_key? - type == :primary_key + type.to_sym == :primary_key end def to_sql @@ -34,7 +34,7 @@ module ActiveRecord column_options[:null] = null unless null.nil? column_options[:default] = default unless default.nil? column_options[:column] = self - add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key + add_column_options!(column_sql, column_options) unless primary_key? column_sql end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 20a5ca2baa..25b8aef617 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -38,6 +38,15 @@ module ActiveRecord configure_connection end + MAX_INDEX_LENGTH_FOR_UTF8MB4 = 191 + def initialize_schema_migrations_table + if @config[:encoding] == 'utf8mb4' + ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_UTF8MB4) + else + ActiveRecord::SchemaMigration.create_table + end + end + def supports_explain? true end diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index 9830abe7d8..6077144265 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -13,18 +13,21 @@ module ActiveRecord "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}" end - def self.create_table + def self.create_table(limit=nil) unless connection.table_exists?(table_name) - connection.create_table(table_name, :id => false) do |t| - t.column :version, :string, :null => false + version_options = {null: false} + version_options[:limit] = limit if limit + + connection.create_table(table_name, id: false) do |t| + t.column :version, :string, version_options end - connection.add_index table_name, :version, :unique => true, :name => index_name + connection.add_index table_name, :version, unique: true, name: index_name end end def self.drop_table if connection.table_exists?(table_name) - connection.remove_index table_name, :name => index_name + connection.remove_index table_name, name: index_name connection.drop_table(table_name) end end diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index 10696258c9..50569d2462 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -26,7 +26,9 @@ module ActiveRecord $stdout.print error.error establish_connection root_configuration_without_database connection.create_database configuration['database'], creation_options - connection.execute grant_statement.gsub(/\s+/, ' ').strip + if configuration['username'] != 'root' + connection.execute grant_statement.gsub(/\s+/, ' ').strip + end establish_connection configuration else $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}" diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 33718ef0e9..4dbd668fcf 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -160,7 +160,7 @@ module ActiveRecord # end # end # - # only "Kotori" is created. (This works on MySQL and PostgreSQL, but not on SQLite3.) + # only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it. # # Most databases don't support true nested transactions. At the time of # writing, the only database that we're aware of that supports true nested diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb new file mode 100644 index 0000000000..9ecd901eac --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb @@ -0,0 +1,26 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class Mysql2Adapter + class SchemaMigrationsTest < ActiveRecord::TestCase + def test_initializes_schema_migrations_for_encoding_utf8mb4 + conn = ActiveRecord::Base.connection + + smtn = ActiveRecord::Migrator.schema_migrations_table_name + conn.drop_table(smtn) if conn.table_exists?(smtn) + + config = conn.instance_variable_get(:@config) + original_encoding = config[:encoding] + + config[:encoding] = 'utf8mb4' + conn.initialize_schema_migrations_table + + assert conn.column_exists?(smtn, :version, :string, limit: Mysql2Adapter::MAX_INDEX_LENGTH_FOR_UTF8MB4) + ensure + config[:encoding] = original_encoding + end + 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 1ddd380f23..781b87741d 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -789,6 +789,37 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end + def test_calling_update_attributes_on_id_changes_the_counter_cache + topic = Topic.order("id ASC").first + original_count = topic.replies.to_a.size + assert_equal original_count, topic.replies_count + + first_reply = topic.replies.first + first_reply.update_attributes(:parent_id => nil) + assert_equal original_count - 1, topic.reload.replies_count + + first_reply.update_attributes(:parent_id => topic.id) + assert_equal original_count, topic.reload.replies_count + end + + def test_calling_update_attributes_changing_ids_doesnt_change_counter_cache + topic1 = Topic.find(1) + topic2 = Topic.find(3) + original_count1 = topic1.replies.to_a.size + original_count2 = topic2.replies.to_a.size + + reply1 = topic1.replies.first + reply2 = topic2.replies.first + + reply1.update_attributes(:parent_id => topic2.id) + assert_equal original_count1 - 1, topic1.reload.replies_count + assert_equal original_count2 + 1, topic2.reload.replies_count + + reply2.update_attributes(:parent_id => topic1.id) + assert_equal original_count1, topic1.reload.replies_count + assert_equal original_count2, topic2.reload.replies_count + end + def test_deleting_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 960c27da3e..445322486c 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -307,20 +307,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal("last_read", ex.errors[0].attribute) end - def test_initialize_abstract_class - e = assert_raises(NotImplementedError) do - FirstAbstractClass.new - end - assert_equal("FirstAbstractClass is an abstract class and can not be instantiated.", e.message) - end - - def test_initialize_base - e = assert_raises(NotImplementedError) do - ActiveRecord::Base.new - end - assert_equal("ActiveRecord::Base is an abstract class and can not be instantiated.", e.message) - end - def test_create_after_initialize_without_block cb = CustomBulb.create(:name => 'Dude') assert_equal('Dude', cb.name) diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index 7de2ceae88..7d06fb5093 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -118,7 +118,7 @@ class CounterCacheTest < ActiveRecord::TestCase test "reset the right counter if two have the same foreign key" do michael = people(:michael) assert_nothing_raised(ActiveRecord::StatementInvalid) do - Person.reset_counters(michael.id, :followers) + Person.reset_counters(michael.id, :friends_too) end end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 0b9ccbf60a..99d54e7526 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -172,6 +172,20 @@ class InheritanceTest < ActiveRecord::TestCase assert_equal Firm, firm.class end + def test_new_with_abstract_class + e = assert_raises(NotImplementedError) do + AbstractCompany.new + end + assert_equal("AbstractCompany is an abstract class and can not be instantiated.", e.message) + end + + def test_new_with_ar_base + e = assert_raises(NotImplementedError) do + ActiveRecord::Base.new + end + assert_equal("ActiveRecord::Base is an abstract class and can not be instantiated.", e.message) + end + def test_new_with_invalid_type assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'InvalidType') } end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index a0a3e6cb0d..0c896beb1d 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -341,9 +341,6 @@ end # is so cumbersome. Will deadlock Ruby threads if the underlying db.execute # blocks, so separate script called by Kernel#system is needed. # (See exec vs. async_exec in the PostgreSQL adapter.) - -# TODO: The Sybase, and OpenBase adapters currently have no support for pessimistic locking - unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? class PessimisticLockingTest < ActiveRecord::TestCase self.use_transactional_fixtures = false diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index fed1b4a8e8..9008c2785e 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1227,10 +1227,11 @@ class RelationTest < ActiveRecord::TestCase def test_turn_off_eager_loading_with_conditions_on_joins original_value = ActiveRecord::Base.disable_implicit_join_references ActiveRecord::Base.disable_implicit_join_references = true + scope = Topic.where(author_email_address: 'my.example@gmail.com').includes(:replies) assert_not scope.eager_loading? ensure - ActiveRecord::Base.time_zone_aware_attributes = original_value + ActiveRecord::Base.disable_implicit_join_references = original_value end def test_ordering_with_extra_spaces diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index dadcca5b7f..816bd62751 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -71,7 +71,7 @@ module ActiveRecord return skip("only tested on mysql") end - @connection = stub(:create_database => true, :execute => true) + @connection = stub("Connection", create_database: true) @error = Mysql::Error.new "Invalid permissions" @configuration = { 'adapter' => 'mysql', @@ -90,6 +90,7 @@ module ActiveRecord end def test_root_password_is_requested + assert_permissions_granted_for "pat" skip "only if mysql is available" unless defined?(::Mysql) $stdin.expects(:gets).returns("secret\n") @@ -97,6 +98,7 @@ module ActiveRecord end def test_connection_established_as_root + assert_permissions_granted_for "pat" ActiveRecord::Base.expects(:establish_connection).with( 'adapter' => 'mysql', 'database' => nil, @@ -108,6 +110,7 @@ module ActiveRecord end def test_database_created_by_root + assert_permissions_granted_for "pat" @connection.expects(:create_database). with('my-app-db', :charset => 'utf8', :collation => 'utf8_unicode_ci') @@ -115,12 +118,18 @@ module ActiveRecord end def test_grant_privileges_for_normal_user - @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON my-app-db.* TO 'pat'@'localhost' IDENTIFIED BY 'wossname' WITH GRANT OPTION;") + assert_permissions_granted_for "pat" + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end + def test_do_not_grant_privileges_for_root_user + @configuration['username'] = 'root' + @configuration['password'] = '' ActiveRecord::Tasks::DatabaseTasks.create @configuration end def test_connection_established_as_normal_user + assert_permissions_granted_for "pat" ActiveRecord::Base.expects(:establish_connection).returns do ActiveRecord::Base.expects(:establish_connection).with( 'adapter' => 'mysql', @@ -142,6 +151,13 @@ module ActiveRecord ActiveRecord::Tasks::DatabaseTasks.create @configuration end + + private + def assert_permissions_granted_for(db_user) + db_name = @configuration['database'] + db_password = @configuration['password'] + @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON #{db_name}.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;") + end end class MySQLDBDropTest < ActiveRecord::TestCase diff --git a/activerecord/test/fixtures/friendships.yml b/activerecord/test/fixtures/friendships.yml index 1ee09175bf..ae0abe0162 100644 --- a/activerecord/test/fixtures/friendships.yml +++ b/activerecord/test/fixtures/friendships.yml @@ -1,4 +1,4 @@ Connection 1: id: 1 - person_id: 1 - friend_id: 2
\ No newline at end of file + friend_id: 1 + follower_id: 2 diff --git a/activerecord/test/fixtures/people.yml b/activerecord/test/fixtures/people.yml index e640a38f1f..0ec05e8d56 100644 --- a/activerecord/test/fixtures/people.yml +++ b/activerecord/test/fixtures/people.yml @@ -5,6 +5,7 @@ michael: number1_fan_id: 3 gender: M followers_count: 1 + friends_too_count: 1 david: id: 2 first_name: David @@ -12,6 +13,7 @@ david: number1_fan_id: 1 gender: M followers_count: 1 + friends_too_count: 1 susan: id: 3 first_name: Susan @@ -19,3 +21,4 @@ susan: number1_fan_id: 1 gender: F followers_count: 1 + friends_too_count: 1 diff --git a/activerecord/test/models/friendship.rb b/activerecord/test/models/friendship.rb index 6b4f7acc38..4b411ca8e0 100644 --- a/activerecord/test/models/friendship.rb +++ b/activerecord/test/models/friendship.rb @@ -1,4 +1,6 @@ class Friendship < ActiveRecord::Base belongs_to :friend, class_name: 'Person' - belongs_to :follower, foreign_key: 'friend_id', class_name: 'Person', counter_cache: :followers_count + # friend_too exists to test a bug, and probably shouldn't be used elsewhere + belongs_to :friend_too, foreign_key: 'friend_id', class_name: 'Person', counter_cache: :friends_too_count + belongs_to :follower, class_name: 'Person' end diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index fa717ef8d6..2985160d28 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -8,7 +8,10 @@ class Person < ActiveRecord::Base has_many :posts_with_no_comments, -> { includes(:comments).where('comments.id is null').references(:comments) }, :through => :readers, :source => :post - has_many :followers, foreign_key: 'friend_id', class_name: 'Friendship' + has_many :friendships, foreign_key: 'friend_id' + # friends_too exists to test a bug, and probably shouldn't be used elsewhere + has_many :friends_too, foreign_key: 'friend_id', class_name: 'Friendship' + has_many :followers, through: :friendships has_many :references has_many :bad_references diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index ae779c702a..a952738e84 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -280,7 +280,7 @@ ActiveRecord::Schema.define do create_table :friendships, :force => true do |t| t.integer :friend_id - t.integer :person_id + t.integer :follower_id end create_table :goofy_string_id, :force => true, :id => false do |t| @@ -494,6 +494,7 @@ ActiveRecord::Schema.define do t.integer :lock_version, :null => false, :default => 0 t.string :comments t.integer :followers_count, :default => 0 + t.integer :friends_too_count, :default => 0 t.references :best_friend t.references :best_friend_of t.integer :insures, null: false, default: 0 diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 9d26b8ba3e..b629688591 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,6 +1,11 @@ ## Rails 4.0.0 (unreleased) ## -* Fix deletion of empty directories in ActiveSupport::Cache::FileStore. +* `ActiveSupport::TimeWithZone` raises `NoMethodError` in proper context. + Fixes #9772. + + *Yves Senn* + +* Fix deletion of empty directories in `ActiveSupport::Cache::FileStore`. *Charles Jones* diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 822c9d98ae..e3788ed54f 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -15,8 +15,6 @@ namespace :test do end end -# Create compressed packages -dist_dirs = [ "lib", "test"] spec = eval(File.read('activesupport.gemspec')) diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 98c866ac43..4a032b0ad0 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -366,6 +366,8 @@ module ActiveSupport # TimeWithZone with the existing +time_zone+. def method_missing(sym, *args, &block) wrap_with_time_zone time.__send__(sym, *args, &block) + rescue NoMethodError => e + raise e, e.message.sub(time.inspect, self.inspect), e.backtrace end private diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb index 8c7d00cae1..3744d50864 100644 --- a/activesupport/test/core_ext/numeric_ext_test.rb +++ b/activesupport/test/core_ext/numeric_ext_test.rb @@ -153,22 +153,16 @@ end class NumericExtSizeTest < ActiveSupport::TestCase def test_unit_in_terms_of_another - relationships = { - 1024.bytes => 1.kilobyte, - 1024.kilobytes => 1.megabyte, - 3584.0.kilobytes => 3.5.megabytes, - 3584.0.megabytes => 3.5.gigabytes, - 1.kilobyte ** 4 => 1.terabyte, - 1024.kilobytes + 2.megabytes => 3.megabytes, - 2.gigabytes / 4 => 512.megabytes, - 256.megabytes * 20 + 5.gigabytes => 10.gigabytes, - 1.kilobyte ** 5 => 1.petabyte, - 1.kilobyte ** 6 => 1.exabyte - } - - relationships.each do |left, right| - assert_equal right, left - end + assert_equal 1024.bytes, 1.kilobyte + assert_equal 1024.kilobytes, 1.megabyte + assert_equal 3584.0.kilobytes, 3.5.megabytes + assert_equal 3584.0.megabytes, 3.5.gigabytes + assert_equal 1.kilobyte ** 4, 1.terabyte + assert_equal 1024.kilobytes + 2.megabytes, 3.megabytes + assert_equal 2.gigabytes / 4, 512.megabytes + assert_equal 256.megabytes * 20 + 5.gigabytes, 10.gigabytes + assert_equal 1.kilobyte ** 5, 1.petabyte + assert_equal 1.kilobyte ** 6, 1.exabyte end def test_units_as_bytes_independently diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb index 8d1a8c628c..92f996f9a4 100644 --- a/activesupport/test/core_ext/object/to_query_test.rb +++ b/activesupport/test/core_ext/object/to_query_test.rb @@ -47,7 +47,7 @@ class ToQueryTest < ActiveSupport::TestCase end private - def assert_query_equal(expected, actual, message = nil) + def assert_query_equal(expected, actual) assert_equal expected.split('&'), actual.to_query.split('&') end end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 0f5699fd63..98a87ab9e6 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -779,6 +779,14 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", (twz - 1.year).inspect end + def test_no_method_error_has_proper_context + e = assert_raises(NoMethodError) { + @twz.this_method_does_not_exist + } + assert_equal "undefined method `this_method_does_not_exist' for Fri, 31 Dec 1999 19:00:00 EST -05:00:Time", e.message + assert_no_match "rescue", e.backtrace.first + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 4806ce07f6..22cb61ffd6 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -324,7 +324,7 @@ class InflectorTest < ActiveSupport::TestCase end def test_underscore_as_reverse_of_dasherize - UnderscoresToDashes.each do |underscored, dasherized| + UnderscoresToDashes.each_key do |underscored| assert_equal(underscored, ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.dasherize(underscored))) end end diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb index b5d8142458..ce91c443e1 100644 --- a/activesupport/test/transliterate_test.rb +++ b/activesupport/test/transliterate_test.rb @@ -17,7 +17,7 @@ class TransliterateTest < ActiveSupport::TestCase # some reason or other are floating in the middle of all the letters. string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include?(c)}.pack("U*") string.each_char do |char| - assert_match %r{^[a-zA-Z']*$}, ActiveSupport::Inflector.transliterate(string) + assert_match %r{^[a-zA-Z']*$}, ActiveSupport::Inflector.transliterate(char) end end diff --git a/guides/assets/images/getting_started/unknown_action_create_for_posts.png b/guides/assets/images/getting_started/unknown_action_create_for_posts.png Binary files differindex c6750e1ae1..c0de53555d 100644 --- a/guides/assets/images/getting_started/unknown_action_create_for_posts.png +++ b/guides/assets/images/getting_started/unknown_action_create_for_posts.png diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index a1864c123e..cd23b5ee15 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -568,7 +568,7 @@ interested in. We also use an instance variable (prefixed by `@`) to hold a reference to the post object. We do this because Rails will pass all instance variables to the view. -Now, create a new file `app/view/posts/show.html.erb` with the following +Now, create a new file `app/views/posts/show.html.erb` with the following content: ```html+erb |