diff options
27 files changed, 200 insertions, 39 deletions
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index af91907f46..6fc25b3bb4 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -2,7 +2,7 @@ * Explicit multipart messages no longer set the order of the MIME parts. *Nate Berkopec* - + * Do not render views when mail() isn't called. Fix #7761 diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index dcf13e927c..95a680ac23 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -499,6 +499,7 @@ module ActionMailer # method, for instance). def initialize(method_name=nil, *args) super() + @_mail_was_called = false @_message = Mail.new process(method_name, *args) if method_name end @@ -506,7 +507,8 @@ module ActionMailer def process(*args) #:nodoc: lookup_context.skip_default_locale! - @_message = NullMail.new unless super + super + @_message = NullMail.new unless @_mail_was_called end class NullMail #:nodoc: @@ -666,6 +668,7 @@ module ActionMailer # end # def mail(headers={}, &block) + @_mail_was_called = true m = @_message # At the beginning, do not consider class default for parts order neither content_type diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 4a608a9ad4..170923673b 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -505,6 +505,12 @@ class BaseTest < ActiveSupport::TestCase mail.deliver end + test 'the return value of mailer methods is not relevant' do + mail = BaseMailer.with_nil_as_return_value + assert_equal('Welcome', mail.body.to_s.strip) + mail.deliver + end + # Before and After hooks class MyObserver diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb index 52342bc59e..8fca6177bd 100644 --- a/actionmailer/test/mailers/base_mailer.rb +++ b/actionmailer/test/mailers/base_mailer.rb @@ -118,4 +118,9 @@ class BaseMailer < ActionMailer::Base def without_mail_call end + + def with_nil_as_return_value(hash = {}) + mail(:template_name => "welcome") + nil + end end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 9db30c8cf7..5f1a6dc082 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,29 +1,31 @@ ## Rails 4.0.0 (unreleased) ## +* Prevent raising EOFError on multipart GET request (IE issue). *Adam Stankiewicz* + * Rename all action callbacks from *_filter to *_action to avoid the misconception that these callbacks are only suited for transforming or halting the response. With the new style, it's more inviting to use them as they were intended, like setting shared ivars for views. - + Example: - + class PeopleController < ActionController::Base before_action :set_person, except: [ :index, :new, :create ] before_action :ensure_permission, only: [ :edit, :update ] - + ... - + private def set_person @person = current_account.people.find(params[:id]) end - + def ensure_permission current_person.can_change?(@person) end end - + The old *_filter methods still work with no deprecation notice. - + *DHH* * Add :if / :unless conditions to fragment cache: diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 9a7b5bc8c7..6610315da7 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -12,7 +12,11 @@ module ActionDispatch # Returns both GET and POST \parameters in a single hash. def parameters @env["action_dispatch.request.parameters"] ||= begin - params = request_parameters.merge(query_parameters) + params = begin + request_parameters.merge(query_parameters) + rescue EOFError + query_parameters.dup + end params.merge!(path_parameters) encode_params(params).with_indifferent_access end diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 63c5ea26a6..399f15199c 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -123,6 +123,18 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest end end + # This can happen in Internet Explorer when redirecting after multipart form submit. + test "does not raise EOFError on GET request with multipart content-type" do + with_routing do |set| + set.draw do + get ':action', to: 'multipart_params_parsing_test/test' + end + headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" } + get "/parse", {}, headers + assert_response :ok + end + end + private def fixture(name) File.open(File.join(FIXTURE_PATH, name), 'rb') do |file| diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index b1988172cb..5913bf7809 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,20 @@ ## Rails 4.0.0 (unreleased) ## +* Add `ActiveRecord::Base.cache_timestamp_format` class attribute to control + the format of the timestamp value in the cache key. + This allows users to improve the precision of the cache key. + Fixes #8195. + + *Rafael Mendonça França* + +* Add `:nsec` date format. This can be used to improve the precision of cache key. + + *Jamie Gaskins* + +* Fix decorating columns for serialized attributes. Fixes #8441 + + *itzki* + * Session variables can be set for the `mysql`, `mysql2`, and `postgresql` adapters in the `variables: <hash>` parameter in `database.yml`. The key-value pairs of this hash will be sent in a `SET key = value` query on new database connections. See also: diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb index 7bdc1bd4c6..7f877a6471 100644 --- a/activerecord/lib/active_record/integration.rb +++ b/activerecord/lib/active_record/integration.rb @@ -1,5 +1,16 @@ module ActiveRecord module Integration + extend ActiveSupport::Concern + + included do + ## + # :singleton-method: + # Indicates the format used to generate the timestamp format in the cache key. + # This is +:number+, by default. + class_attribute :cache_timestamp_format, :instance_writer => false + self.cache_timestamp_format = :nsec + end + # Returns a String, which Action Pack uses for constructing an URL to this # object. The default implementation returns this record's id as a String, # or nil if this record's unsaved. @@ -37,7 +48,7 @@ module ActiveRecord when new_record? "#{self.class.model_name.cache_key}/new" when timestamp = self[:updated_at] - timestamp = timestamp.utc.to_s(:nsec) + timestamp = timestamp.utc.to_s(cache_timestamp_format) "#{self.class.model_name.cache_key}/#{id}-#{timestamp}" else "#{self.class.model_name.cache_key}/#{id}" diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 628ab0f566..85fb4be992 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -224,11 +224,10 @@ module ActiveRecord def decorate_columns(columns_hash) # :nodoc: return if columns_hash.empty? - serialized_attributes.each_key do |key| - columns_hash[key] = AttributeMethods::Serialization::Type.new(columns_hash[key]) - end - columns_hash.each do |name, col| + if serialized_attributes.key?(name) + columns_hash[name] = AttributeMethods::Serialization::Type.new(col) + end if create_time_zone_conversion_attribute?(name, col) columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(col) end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 8644f2f496..c5c500279e 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -23,6 +23,8 @@ require 'models/edge' require 'models/joke' require 'models/bulb' require 'models/bird' +require 'models/car' +require 'models/bulb' require 'rexml/document' require 'active_support/core_ext/exception' @@ -1442,6 +1444,21 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key end + def test_cache_key_format_for_existing_record_with_updated_at_and_cache_timestamp_format_set + dev = CachedDeveloper.first + assert_equal "cached_developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key + end + + def test_cache_key_changes_when_child_touched + car = Car.create + Bulb.create(car: car) + + key = car.cache_key + car.bulb.touch + car.reload + assert_not_equal key, car.cache_key + end + def test_cache_key_format_for_existing_record_with_nil_updated_at dev = Developer.first dev.update_columns(updated_at: nil) diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 068f3cf3cd..6962da298e 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -212,4 +212,17 @@ class SerializedAttributeTest < ActiveRecord::TestCase assert_kind_of BCrypt::Password, topic.content assert_equal(true, topic.content == password, 'password should equal') end + + def test_serialize_attribute_via_select_method_when_time_zone_available + ActiveRecord::Base.time_zone_aware_attributes = true + Topic.serialize(:content, MyObject) + + myobj = MyObject.new('value1', 'value2') + topic = Topic.create(content: myobj) + + assert_equal(myobj, Topic.select(:content).find(topic.id).content) + assert_raise(ActiveModel::MissingAttributeError) { Topic.select(:id).find(topic.id).content } + ensure + ActiveRecord::Base.time_zone_aware_attributes = false + end end diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index e4c0278c0d..0109ef4f83 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -1,6 +1,6 @@ class Bulb < ActiveRecord::Base default_scope { where(:name => 'defaulty') } - belongs_to :car + belongs_to :car, :touch => true attr_reader :scope_after_initialize, :attributes_after_initialize diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 1c048bb6b4..683cb54a10 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -234,3 +234,8 @@ class ThreadsafeDeveloper < ActiveRecord::Base limit(1) end end + +class CachedDeveloper < ActiveRecord::Base + self.table_name = "developers" + self.cache_timestamp_format = :number +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 35778d008a..af14bc7bd5 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -115,6 +115,7 @@ ActiveRecord::Schema.define do t.integer :engines_count t.integer :wheels_count t.column :lock_version, :integer, :null => false, :default => 0 + t.timestamps end create_table :categories, :force => true do |t| diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 446f767f0c..a8e33a2956 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -276,6 +276,8 @@ config.middleware.delete ActionDispatch::BestStandardsSupport * `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.cache_timestamp_format+ controls the format of the timestamp value in the cache key. Default is +:number+. + The MySQL adapter adds one additional configuration option: * `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default. diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index d8a4f15b4b..4ae8756ed0 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -99,13 +99,17 @@ module Rails end def index_name - @index_name ||= if reference? - polymorphic? ? %w(id type).map { |t| "#{name}_#{t}" } : "#{name}_id" + @index_name ||= if polymorphic? + %w(id type).map { |t| "#{name}_#{t}" } else - name + column_name end end + def column_name + @column_name ||= reference? ? "#{name}_id" : name + end + def foreign_key? !!(name =~ /_id$/) end diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index cc10fd9177..9965db98de 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -160,6 +160,13 @@ module Rails end end + def attributes_names + @attributes_names ||= attributes.each_with_object([]) do |a, names| + names << a.column_name + names << "#{a.name}_type" if a.polymorphic? + end + end + def pluralize_table_names? !defined?(ActiveRecord::Base) || ActiveRecord::Base.pluralize_table_names end diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index 08fe8a08e3..4d08b01e60 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -91,10 +91,10 @@ class <%= controller_class_name %>Controller < ApplicationController # Use this method to whitelist the permissible parameters. Example: params.require(:person).permit(:name, :age) # Also, you can specialize this method with per-user checking of permissible attributes. def <%= "#{singular_table_name}_params" %> - <%- if attributes.empty? -%> + <%- if attributes_names.empty? -%> params[<%= ":#{singular_table_name}" %>] <%- else -%> - params.require(<%= ":#{singular_table_name}" %>).permit(<%= attributes.map {|a| ":#{a.name}" }.join(', ') %>) + params.require(<%= ":#{singular_table_name}" %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>) <%- end -%> end end diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml index 5c8780aa64..7625ff975c 100644 --- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml +++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml @@ -3,12 +3,18 @@ <% unless attributes.empty? -%> one: <% attributes.each do |attribute| -%> - <%= attribute.name %>: <%= attribute.default %> + <%= attribute.column_name %>: <%= attribute.default %> + <%- if attribute.polymorphic? -%> + <%= "#{attribute.name}_type: #{attribute.human_name}" %> + <%- end -%> <% end -%> two: <% attributes.each do |attribute| -%> - <%= attribute.name %>: <%= attribute.default %> + <%= attribute.column_name %>: <%= attribute.default %> + <%- if attribute.polymorphic? -%> + <%= "#{attribute.name}_type: #{attribute.human_name}" %> + <%- end -%> <% end -%> <% else -%> # This model initially had no columns defined. If you add columns to the diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index 3b4fec2e83..8f3ecaadea 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -18,17 +18,12 @@ module TestUnit # :nodoc: private def attributes_hash - return if accessible_attributes.empty? + return if attributes_names.empty? - accessible_attributes.map do |a| - name = a.name + attributes_names.map do |name| "#{name}: @#{singular_table_name}.#{name}" end.sort.join(', ') end - - def accessible_attributes - attributes.reject(&:reference?) - end end end end diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index e94c6a2030..fe1e25d88c 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -1,7 +1,7 @@ require 'action_dispatch/routing/inspector' class Rails::InfoController < ActionController::Base - self.view_paths = File.join(File.dirname(__FILE__), 'templates') + self.view_paths = File.expand_path('../templates', __FILE__) layout 'application' before_filter :require_local! diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index f2234ab111..a8275a2e76 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -195,6 +195,16 @@ module ApplicationTests assert_no_match(/Errors running/, output) end + def test_scaffold_with_references_columns_tests_pass_by_default + output = Dir.chdir(app_path) do + `rails generate scaffold LineItems product:references cart:belongs_to; + bundle exec rake db:migrate db:test:clone test` + end + + assert_match(/7 tests, 13 assertions, 0 failures, 0 errors/, output) + assert_no_match(/Errors running/, output) + end + def test_db_test_clone_when_using_sql_format add_to_config "config.active_record.schema_format = :sql" output = Dir.chdir(app_path) do diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index 6ab1cd58c7..d08e650b62 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -117,13 +117,13 @@ class GeneratedAttributeTest < Rails::Generators::TestCase assert create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic? end end - + def test_polymorphic_reference_is_false %w(foo bar baz).each do |attribute_type| assert !create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic? end end - + def test_blank_type_defaults_to_string_raises_exception assert_equal :string, create_generated_attribute(nil, 'title').type assert_equal :string, create_generated_attribute("", 'title').type @@ -132,6 +132,13 @@ class GeneratedAttributeTest < Rails::Generators::TestCase def test_handles_index_names_for_references assert_equal "post", create_generated_attribute('string', 'post').index_name assert_equal "post_id", create_generated_attribute('references', 'post').index_name + assert_equal "post_id", create_generated_attribute('belongs_to', 'post').index_name assert_equal ["post_id", "post_type"], create_generated_attribute('references{polymorphic}', 'post').index_name end + + def test_handles_index_names_for_references + assert_equal "post", create_generated_attribute('string', 'post').column_name + assert_equal "post_id", create_generated_attribute('references', 'post').column_name + assert_equal "post_id", create_generated_attribute('belongs_to', 'post').column_name + end end diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 0c7ff0ebe7..70e080a8ab 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -273,6 +273,16 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_file "test/fixtures/accounts.yml", /name: MyString/, /age: 1/ end + def test_fixtures_use_the_references_ids + run_generator ["LineItem", "product:references", "cart:belongs_to"] + assert_file "test/fixtures/line_items.yml", /product_id: \n cart_id: / + end + + def test_fixtures_use_the_references_ids_and_type + run_generator ["LineItem", "product:references{polymorphic}", "cart:belongs_to"] + assert_file "test/fixtures/line_items.yml", /product_id: \n product_type: Product\n cart_id: / + end + def test_fixture_is_skipped run_generator ["account", "--skip-fixture"] assert_no_file "test/fixtures/accounts.yml" diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 2a88dac635..57a12d0457 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -52,6 +52,33 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase end end + def test_dont_use_require_or_permit_if_there_are_no_attributes + run_generator ["User"] + + assert_file "app/controllers/users_controller.rb" do |content| + assert_match(/def user_params/, content) + assert_match(/params\[:user\]/, content) + end + end + + def test_controller_permit_references_attributes + run_generator ["LineItem", "product:references", "cart:belongs_to"] + + assert_file "app/controllers/line_items_controller.rb" do |content| + assert_match(/def line_item_params/, content) + assert_match(/params\.require\(:line_item\)\.permit\(:product_id, :cart_id\)/, content) + end + end + + def test_controller_permit_polymorphic_references_attributes + run_generator ["LineItem", "product:references{polymorphic}"] + + assert_file "app/controllers/line_items_controller.rb" do |content| + assert_match(/def line_item_params/, content) + assert_match(/params\.require\(:line_item\)\.permit\(:product_id, :product_type\)/, content) + end + end + def test_helper_are_invoked_with_a_pluralized_name run_generator assert_file "app/helpers/users_helper.rb", /module UsersHelper/ @@ -68,13 +95,13 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase end def test_functional_tests - run_generator + run_generator ["User", "name:string", "age:integer", "organization:references{polymorphic}"] assert_file "test/controllers/users_controller_test.rb" do |content| assert_match(/class UsersControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) - assert_match(/post :create, user: \{ age: @user.age, name: @user.name \}/, content) - assert_match(/put :update, id: @user, user: \{ age: @user.age, name: @user.name \}/, content) + assert_match(/post :create, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content) + assert_match(/put :update, id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content) end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 7fcc0a7409..de62fdb1ea 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -60,8 +60,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "test/controllers/product_lines_controller_test.rb" do |test| assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test) - assert_match(/post :create, product_line: \{ title: @product_line.title \}/, test) - assert_match(/put :update, id: @product_line, product_line: \{ title: @product_line.title \}/, test) + assert_match(/post :create, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test) + assert_match(/put :update, id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test) end # Views @@ -199,7 +199,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase run_generator [ "admin/role" ], :behavior => :revoke # Model - assert_file "app/models/admin.rb" # ( should not be remove ) + assert_file "app/models/admin.rb" # ( should not be remove ) assert_no_file "app/models/admin/role.rb" assert_no_file "test/models/admin/role_test.rb" assert_no_file "test/fixtures/admin/roles.yml" |