diff options
61 files changed, 607 insertions, 206 deletions
diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000000..387b8fea9c --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,24 @@ +# Number of days of inactivity before an issue becomes stale +days: 90 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - With reproduction steps + - attached PR + - regression + - release blocker +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not been commented on for at least three months. + + The resources of the Rails team are limited, and so we are asking for your help. + + If you can still reproduce this error on the `5-1-stable` branch or on `master`, + please reply with all of the information you have about it in order to keep the issue open. + + Thank you for all your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.travis.yml b/.travis.yml index ad9ef4161d..ed4e0fba3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,9 @@ services: addons: postgresql: "9.4" + apt: + packages: + - postgresql-9.4 bundler_args: --without test --jobs 3 --retry 3 before_install: @@ -101,6 +104,12 @@ matrix: jdk: oraclejdk8 env: - "GEM=am,amo,aj" + # Test with old (< 9.4.2) postgresql + - rvm: 2.4.0 + env: + - "GEM=ar:postgresql" + addons: + postgresql: "9.4" allow_failures: - rvm: ruby-head - rvm: jruby-9.1.8.0 diff --git a/actionpack/test/controller/api/with_helpers_test.rb b/actionpack/test/controller/api/with_helpers_test.rb new file mode 100644 index 0000000000..9882a25ae1 --- /dev/null +++ b/actionpack/test/controller/api/with_helpers_test.rb @@ -0,0 +1,26 @@ +require "abstract_unit" + +module ApiWithHelper + def my_helper + "helper" + end +end + +class WithHelpersController < ActionController::API + include ActionController::Helpers + helper ApiWithHelper + + def with_helpers + render plain: self.class.helpers.my_helper + end +end + +class WithHelpersTest < ActionController::TestCase + tests WithHelpersController + + def test_with_helpers + get :with_helpers + + assert_equal "helper", response.body + end +end diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index 1eb92abae4..2a41d57b26 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -32,13 +32,13 @@ class ParamsWrapperTest < ActionController::TestCase def self.attribute_names [] end - end - class Person - def self.stores_attributes + def self.stored_attributes { settings: [:color, :size] } end + end + class Person def self.attribute_names [] end @@ -67,11 +67,13 @@ class ParamsWrapperTest < ActionController::TestCase end def test_store_accessors_wrapped - with_default_wrapper_options do - @request.env["CONTENT_TYPE"] = "application/json" - post :parse, params: { "username" => "sikachu", "color" => "blue", "size" => "large" } - assert_parameters("username" => "sikachu", "color" => "blue", "size" => "large", - "user" => { "username" => "sikachu", "color" => "blue", "size" => "large" }) + assert_called(User, :attribute_names, times: 2, returns: ["username"]) do + with_default_wrapper_options do + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "color" => "blue", "size" => "large" } + assert_parameters("username" => "sikachu", "color" => "blue", "size" => "large", + "user" => { "username" => "sikachu", "color" => "blue", "size" => "large" }) + end end end diff --git a/actionview/app/assets/javascripts/features/disable.coffee b/actionview/app/assets/javascripts/features/disable.coffee index e8cce7da40..90aa3bdf0e 100644 --- a/actionview/app/assets/javascripts/features/disable.coffee +++ b/actionview/app/assets/javascripts/features/disable.coffee @@ -2,6 +2,10 @@ { matches, getData, setData, stopEverything, formElements } = Rails +Rails.handleDisabledElement = (e) -> + element = this + stopEverything(e) if element.disabled + # Unified function to enable an element (link, button and form) Rails.enableElement = (e) -> element = if e instanceof Event then e.target else e diff --git a/actionview/app/assets/javascripts/rails-ujs.coffee b/actionview/app/assets/javascripts/rails-ujs.coffee index df889ce067..afe7d6f7a3 100644 --- a/actionview/app/assets/javascripts/rails-ujs.coffee +++ b/actionview/app/assets/javascripts/rails-ujs.coffee @@ -12,7 +12,7 @@ fire, delegate getData, $ refreshCSRFTokens, CSRFProtection - enableElement, disableElement + enableElement, disableElement, handleDisabledElement handleConfirm handleRemote, formSubmitButtonClick, handleMetaClick handleMethod @@ -44,19 +44,23 @@ Rails.start = -> delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement + delegate document, Rails.linkClickSelector, 'click', handleDisabledElement delegate document, Rails.linkClickSelector, 'click', handleConfirm delegate document, Rails.linkClickSelector, 'click', handleMetaClick delegate document, Rails.linkClickSelector, 'click', disableElement delegate document, Rails.linkClickSelector, 'click', handleRemote delegate document, Rails.linkClickSelector, 'click', handleMethod + delegate document, Rails.buttonClickSelector, 'click', handleDisabledElement delegate document, Rails.buttonClickSelector, 'click', handleConfirm delegate document, Rails.buttonClickSelector, 'click', disableElement delegate document, Rails.buttonClickSelector, 'click', handleRemote + delegate document, Rails.inputChangeSelector, 'change', handleDisabledElement delegate document, Rails.inputChangeSelector, 'change', handleConfirm delegate document, Rails.inputChangeSelector, 'change', handleRemote + delegate document, Rails.formSubmitSelector, 'submit', handleDisabledElement delegate document, Rails.formSubmitSelector, 'submit', handleConfirm delegate document, Rails.formSubmitSelector, 'submit', handleRemote # Normal mode submit @@ -65,6 +69,7 @@ Rails.start = -> delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement + delegate document, Rails.formInputClickSelector, 'click', handleDisabledElement delegate document, Rails.formInputClickSelector, 'click', handleConfirm delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick diff --git a/actionview/test/ujs/public/test/data-confirm.js b/actionview/test/ujs/public/test/data-confirm.js index 28190c2250..229b9e1466 100644 --- a/actionview/test/ujs/public/test/data-confirm.js +++ b/actionview/test/ujs/public/test/data-confirm.js @@ -26,6 +26,13 @@ module('data-confirm', { 'data-confirm': 'Are you absolutely sure?' })) + $('#qunit-fixture').append($('<button />', { + type: 'submit', + form: 'confirm', + disabled: 'disabled', + 'data-confirm': 'Are you absolutely sure?' + })) + this.windowConfirm = window.confirm }, teardown: function() { @@ -286,3 +293,24 @@ asyncTest('clicking on the children of a link should also trigger a confirm', 6, .find('strong') .triggerNative('click') }) + +asyncTest('clicking on the children of a disabled button should not trigger a confirm.', 1, function() { + var message + // auto-decline: + window.confirm = function(msg) { message = msg; return false } + + $('button[data-confirm][disabled]') + .html("<strong>Click me</strong>") + .bindNative('confirm', function() { + App.assertCallbackNotInvoked('confirm') + }) + .find('strong') + .bindNative('click', function() { + App.assertCallbackInvoked('click') + }) + .triggerNative('click') + + setTimeout(function() { + start() + }, 50) +}) diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 3bb5005275..16b1b50cb3 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1 +1,36 @@ +* Fix methods `#keys`, `#values` in `ActiveModel::Errors`. + + Change `#keys` to only return the keys that don't have empty messages. + + Change `#values` to only return the not empty values. + + Example: + + # Before + person = Person.new + person.errors.keys # => [] + person.errors.values # => [] + person.errors.messages # => {} + person.errors[:name] # => [] + person.errors.messages # => {:name => []} + person.errors.keys # => [:name] + person.errors.values # => [[]] + + # After + person = Person.new + person.errors.keys # => [] + person.errors.values # => [] + person.errors.messages # => {} + person.errors[:name] # => [] + person.errors.messages # => {:name => []} + person.errors.keys # => [] + person.errors.values # => [] + + *bogdanvlviv* + +* Avoid converting integer as a string into float. + + *namusyaka* + + Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 9df4ca51fe..5d3472802b 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -132,15 +132,6 @@ module ActiveModel # # person.errors[:name] # => ["cannot be nil"] # person.errors['name'] # => ["cannot be nil"] - # - # Note that, if you try to get errors of an attribute which has - # no errors associated with it, this method will instantiate - # an empty error list for it and +keys+ will return an array - # of error keys which includes this attribute. - # - # person.errors.keys # => [] - # person.errors[:name] # => [] - # person.errors.keys # => [:name] def [](attribute) messages[attribute.to_sym] end @@ -181,7 +172,9 @@ module ActiveModel # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]} # person.errors.values # => [["cannot be nil", "must be specified"]] def values - messages.values + messages.reject do |key, value| + value.empty? + end.values end # Returns all message keys. @@ -189,7 +182,9 @@ module ActiveModel # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]} # person.errors.keys # => [:name] def keys - messages.keys + messages.reject do |key, value| + value.empty? + end.keys end # Returns +true+ if no errors are found, +false+ otherwise. diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 30a9ef472d..995b331245 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -70,6 +70,7 @@ module ActiveModel end def parse_raw_value_as_a_number(raw_value) + return raw_value.to_i if is_integer?(raw_value) Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/ end diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index 0872084cf5..43aee5a814 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -99,6 +99,14 @@ class ErrorsTest < ActiveModel::TestCase assert_equal ["omg", "zomg"], errors.values end + test "values returns an empty array after try to get a message only" do + errors = ActiveModel::Errors.new(self) + errors.messages[:foo] + errors.messages[:baz] + + assert_equal [], errors.values + end + test "keys returns the error keys" do errors = ActiveModel::Errors.new(self) errors.messages[:foo] << "omg" @@ -107,6 +115,14 @@ class ErrorsTest < ActiveModel::TestCase assert_equal [:foo, :baz], errors.keys end + test "keys returns an empty array after try to get a message only" do + errors = ActiveModel::Errors.new(self) + errors.messages[:foo] + errors.messages[:baz] + + assert_equal [], errors.keys + end + test "detecting whether there are errors with empty?, blank?, include?" do person = Person.new person.errors[:foo] diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index a1be2de578..c0158e075f 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -260,6 +260,15 @@ class NumericalityValidationTest < ActiveModel::TestCase Person.clear_validators! end + def test_validates_numericality_with_exponent_number + base = 10_000_000_000_000_000 + Topic.validates_numericality_of :approved, less_than_or_equal_to: base + topic = Topic.new + topic.approved = (base + 1).to_s + + assert topic.invalid? + end + def test_validates_numericality_with_invalid_args assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, greater_than_or_equal_to: "foo" } assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, less_than_or_equal_to: "foo" } diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb index 44a7338bf5..730e7c7137 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb @@ -6,8 +6,50 @@ module ActiveRecord true end - def disable_referential_integrity # :nodoc: + def disable_referential_integrity(&block) # :nodoc: if supports_disable_referential_integrity? + if supports_alter_constraint? + disable_referential_integrity_with_alter_constraint(&block) + else + disable_referential_integrity_with_disable_trigger(&block) + end + else + yield + end + end + + private + + def disable_referential_integrity_with_alter_constraint + tables_constraints = execute(<<-SQL).values + SELECT table_name, constraint_name + FROM information_schema.table_constraints + WHERE constraint_type = 'FOREIGN KEY' + AND is_deferrable = 'NO' + SQL + + execute( + tables_constraints.collect { |table, constraint| + "ALTER TABLE #{quote_table_name(table)} ALTER CONSTRAINT #{constraint} DEFERRABLE" + }.join(";") + ) + + begin + transaction do + execute("SET CONSTRAINTS ALL DEFERRED") + + yield + end + ensure + execute( + tables_constraints.collect { |table, constraint| + "ALTER TABLE #{quote_table_name(table)} ALTER CONSTRAINT #{constraint} NOT DEFERRABLE" + }.join(";") + ) + end + end + + def disable_referential_integrity_with_disable_trigger original_exception = nil begin @@ -39,10 +81,7 @@ Rails needs superuser privileges to disable referential integrity. end rescue ActiveRecord::ActiveRecordError end - else - yield end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 93c6fad039..0ad114165e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -306,6 +306,12 @@ module ActiveRecord postgresql_version >= 90400 end + def supports_alter_constraint? + # PostgreSQL 9.4 introduces ALTER TABLE ... ALTER CONSTRAINT but it has a bug and fixed in 9.4.2 + # https://www.postgresql.org/docs/9.4/static/release-9-4-2.html + postgresql_version >= 90402 + end + def get_advisory_lock(lock_id) # :nodoc: unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63 raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer") diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 1c7206aca4..f3e2df8786 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -77,6 +77,8 @@ db_namespace = namespace :db do namespace :migrate do # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).' task redo: [:environment, :load_config] do + raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty? + if ENV["VERSION"] db_namespace["migrate:down"].invoke db_namespace["migrate:up"].invoke @@ -91,16 +93,17 @@ db_namespace = namespace :db do # desc 'Runs the "up" for a given migration VERSION.' task up: [:environment, :load_config] do + raise "VERSION is required" if ENV["VERSION"] && ENV["VERSION"].empty? + version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil - raise "VERSION is required" unless version ActiveRecord::Migrator.run(:up, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version) db_namespace["_dump"].invoke end # desc 'Runs the "down" for a given migration VERSION.' task down: [:environment, :load_config] do + raise "VERSION is required - To go down one migration, use db:rollback" if ENV["VERSION"] && ENV["VERSION"].empty? version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil - raise "VERSION is required - To go down one migration, run db:rollback" unless version ActiveRecord::Migrator.run(:down, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version) db_namespace["_dump"].invoke end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 18ae10a652..fca914aedd 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -87,14 +87,19 @@ module ActiveRecord binds = [] attributes.each do |column_name, value| + binds.concat(value.bound_attributes) if value.is_a?(Relation) case when value.is_a?(Hash) && !table.has_column?(column_name) attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value) result[column_name] = attrs binds += bvs - next - when value.is_a?(Relation) - binds += value.bound_attributes + when table.associated_with?(column_name) + # Find the foreign key when using queries such as: + # Post.where(author: author) + # + # For polymorphic relationships, find the foreign key and type: + # PriceEstimate.where(estimate_of: treasure) + result[column_name] = AssociationQueryHandler.value_for(table, column_name, value) when value.is_a?(Range) && !table.type(column_name).respond_to?(:subtype) first = value.begin last = value.end @@ -114,15 +119,6 @@ module ActiveRecord binds << build_bind_param(column_name, value) end end - - # Find the foreign key when using queries such as: - # Post.where(author: author) - # - # For polymorphic relationships, find the foreign key and type: - # PriceEstimate.where(estimate_of: treasure) - if table.associated_with?(column_name) - result[column_name] = AssociationQueryHandler.value_for(table, column_name, value) - end end [result, binds] @@ -155,7 +151,6 @@ module ActiveRecord end def can_be_bound?(column_name, value) - return if table.associated_with?(column_name) case value when Array, Range table.type(column_name).respond_to?(:subtype) diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 82604a915f..46fa8a70a3 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -162,9 +162,11 @@ module ActiveRecord end def migrate + raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty? + verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil - scope = ENV["SCOPE"] + scope = ENV["SCOPE"] verbose_was, Migration.verbose = Migration.verbose, verbose Migrator.migrate(migrations_paths, version) do |migration| scope.blank? || scope == migration.scope diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb index 0ff04bfa27..c5c540cebc 100644 --- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb +++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb @@ -1,111 +1,150 @@ require "cases/helper" require "support/connection_helper" -class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase - self.use_transactional_tests = false +if ActiveRecord::Base.connection.respond_to?(:supports_alter_constraint?) && + ActiveRecord::Base.connection.supports_alter_constraint? + class PostgreSQLReferentialIntegrityWithAlterConstraintTest < ActiveRecord::PostgreSQLTestCase + self.use_transactional_tests = false - include ConnectionHelper + include ConnectionHelper - IS_REFERENTIAL_INTEGRITY_SQL = lambda do |sql| - sql.match(/DISABLE TRIGGER ALL/) || sql.match(/ENABLE TRIGGER ALL/) - end + IS_REFERENTIAL_INTEGRITY_SQL = lambda do |sql| + sql.match(/SET CONSTRAINTS ALL DEFERRED/) + end - module MissingSuperuserPrivileges - def execute(sql) - if IS_REFERENTIAL_INTEGRITY_SQL.call(sql) - super "BROKEN;" rescue nil # put transaction in broken state - raise ActiveRecord::StatementInvalid, "PG::InsufficientPrivilege" - else - super + module ProgrammerMistake + def execute(sql) + if IS_REFERENTIAL_INTEGRITY_SQL.call(sql) + raise ArgumentError, "something is not right." + else + super + end end end - end - module ProgrammerMistake - def execute(sql) - if IS_REFERENTIAL_INTEGRITY_SQL.call(sql) - raise ArgumentError, "something is not right." - else - super + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + reset_connection + end + + def test_errors_bubble_up + @connection.extend ProgrammerMistake + + assert_raises ArgumentError do + @connection.disable_referential_integrity {} end end end +else + class PostgreSQLReferentialIntegrityWithDisableTriggerTest < ActiveRecord::PostgreSQLTestCase + self.use_transactional_tests = false - def setup - @connection = ActiveRecord::Base.connection - end + include ConnectionHelper - def teardown - reset_connection - if ActiveRecord::Base.connection.is_a?(MissingSuperuserPrivileges) - raise "MissingSuperuserPrivileges patch was not removed" + IS_REFERENTIAL_INTEGRITY_SQL = lambda do |sql| + sql.match(/DISABLE TRIGGER ALL/) || sql.match(/ENABLE TRIGGER ALL/) end - end - def test_should_reraise_invalid_foreign_key_exception_and_show_warning - @connection.extend MissingSuperuserPrivileges + module MissingSuperuserPrivileges + def execute(sql) + if IS_REFERENTIAL_INTEGRITY_SQL.call(sql) + super "BROKEN;" rescue nil # put transaction in broken state + raise ActiveRecord::StatementInvalid, "PG::InsufficientPrivilege" + else + super + end + end + end - warning = capture(:stderr) do - e = assert_raises(ActiveRecord::InvalidForeignKey) do - @connection.disable_referential_integrity do - raise ActiveRecord::InvalidForeignKey, "Should be re-raised" + module ProgrammerMistake + def execute(sql) + if IS_REFERENTIAL_INTEGRITY_SQL.call(sql) + raise ArgumentError, "something is not right." + else + super end end - assert_equal "Should be re-raised", e.message end - assert_match (/WARNING: Rails was not able to disable referential integrity/), warning - assert_match (/cause: PG::InsufficientPrivilege/), warning - end - def test_does_not_print_warning_if_no_invalid_foreign_key_exception_was_raised - @connection.extend MissingSuperuserPrivileges + def setup + @connection = ActiveRecord::Base.connection + end - warning = capture(:stderr) do - e = assert_raises(ActiveRecord::StatementInvalid) do - @connection.disable_referential_integrity do - raise ActiveRecord::StatementInvalid, "Should be re-raised" + def teardown + reset_connection + if ActiveRecord::Base.connection.is_a?(MissingSuperuserPrivileges) + raise "MissingSuperuserPrivileges patch was not removed" + end + end + + def test_should_reraise_invalid_foreign_key_exception_and_show_warning + @connection.extend MissingSuperuserPrivileges + + warning = capture(:stderr) do + e = assert_raises(ActiveRecord::InvalidForeignKey) do + @connection.disable_referential_integrity do + raise ActiveRecord::InvalidForeignKey, "Should be re-raised" + end end + assert_equal "Should be re-raised", e.message end - assert_equal "Should be re-raised", e.message + assert_match (/WARNING: Rails was not able to disable referential integrity/), warning + assert_match (/cause: PG::InsufficientPrivilege/), warning end - assert warning.blank?, "expected no warnings but got:\n#{warning}" - end - def test_does_not_break_transactions - @connection.extend MissingSuperuserPrivileges + def test_does_not_print_warning_if_no_invalid_foreign_key_exception_was_raised + @connection.extend MissingSuperuserPrivileges - @connection.transaction do - @connection.disable_referential_integrity do - assert_transaction_is_not_broken + warning = capture(:stderr) do + e = assert_raises(ActiveRecord::StatementInvalid) do + @connection.disable_referential_integrity do + raise ActiveRecord::StatementInvalid, "Should be re-raised" + end + end + assert_equal "Should be re-raised", e.message end - assert_transaction_is_not_broken + assert warning.blank?, "expected no warnings but got:\n#{warning}" end - end - def test_does_not_break_nested_transactions - @connection.extend MissingSuperuserPrivileges + def test_does_not_break_transactions + @connection.extend MissingSuperuserPrivileges - @connection.transaction do - @connection.transaction(requires_new: true) do + @connection.transaction do @connection.disable_referential_integrity do assert_transaction_is_not_broken end + assert_transaction_is_not_broken end - assert_transaction_is_not_broken end - end - def test_only_catch_active_record_errors_others_bubble_up - @connection.extend ProgrammerMistake + def test_does_not_break_nested_transactions + @connection.extend MissingSuperuserPrivileges - assert_raises ArgumentError do - @connection.disable_referential_integrity {} + @connection.transaction do + @connection.transaction(requires_new: true) do + @connection.disable_referential_integrity do + assert_transaction_is_not_broken + end + end + assert_transaction_is_not_broken + end end - end - private + def test_only_catch_active_record_errors_others_bubble_up + @connection.extend ProgrammerMistake - def assert_transaction_is_not_broken - assert_equal 1, @connection.select_value("SELECT 1") + assert_raises ArgumentError do + @connection.disable_referential_integrity {} + end end + + private + + def assert_transaction_is_not_broken + assert_equal 1, @connection.select_value("SELECT 1") + end + end end diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index 5fd2411f6f..7721bd5cd9 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -7,7 +7,7 @@ require "models/computer" require "models/company" class AssociationCallbacksTest < ActiveRecord::TestCase - fixtures :posts, :authors, :projects, :developers + fixtures :posts, :authors, :author_addresses, :projects, :developers def setup @david = authors(:david) diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index ddb5c7a4aa..3638c87968 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -12,7 +12,7 @@ require "models/vertex" require "models/edge" class CascadedEagerLoadingTest < ActiveRecord::TestCase - fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments, + fixtures :authors, :author_addresses, :mixins, :companies, :posts, :topics, :accounts, :comments, :categorizations, :people, :categories, :edges, :vertices def test_eager_association_loading_with_cascaded_two_levels diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 173063e068..e2f044c139 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -40,7 +40,7 @@ require "models/zine" require "models/interest" class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase - fixtures :authors, :posts, :comments + fixtures :authors, :author_addresses, :posts, :comments def test_should_generate_valid_sql author = authors(:david) @@ -51,7 +51,7 @@ class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCa end class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase - fixtures :authors, :essays, :subscribers, :subscriptions, :people + fixtures :authors, :author_addresses, :essays, :subscribers, :subscriptions, :people def test_custom_primary_key_on_new_record_should_fetch_with_query subscriber = Subscriber.new(nick: "webster132") @@ -100,7 +100,7 @@ end class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, - :developers_projects, :topics, :authors, :comments, + :developers_projects, :topics, :authors, :author_addresses, :comments, :posts, :readers, :taggings, :cars, :jobs, :tags, :categorizations, :zines, :interests diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index 38a729d2d4..28b883586d 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -23,7 +23,7 @@ require "models/customer_carrier" class HasOneThroughAssociationsTest < ActiveRecord::TestCase fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans, - :dashboards, :speedometers, :authors, :posts, :comments, :categories, :essays, :owners + :dashboards, :speedometers, :authors, :author_addresses, :posts, :comments, :categories, :essays, :owners def setup @member = members(:groucho) diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index 7414869c8f..ddf5bc6f0b 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -10,7 +10,7 @@ require "models/tagging" require "models/tag" class InnerJoinAssociationTest < ActiveRecord::TestCase - fixtures :authors, :essays, :posts, :comments, :categories, :categories_posts, :categorizations, + fixtures :authors, :author_addresses, :essays, :posts, :comments, :categories, :categories_posts, :categorizations, :taggings, :tags def test_construct_finder_sql_applies_aliases_tables_on_association_conditions diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index a4345f3857..c078cef064 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -19,7 +19,7 @@ require "models/car" class AssociationsJoinModelTest < ActiveRecord::TestCase self.use_transactional_tests = false unless supports_savepoints? - fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books, + fixtures :posts, :authors, :author_addresses, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books, # Reload edges table from fixtures as otherwise repeated test was failing :edges diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb index 42dbbad1c8..2aca3523c4 100644 --- a/activerecord/test/cases/associations/left_outer_join_association_test.rb +++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb @@ -7,7 +7,7 @@ require "models/categorization" require "models/person" class LeftOuterJoinAssociationTest < ActiveRecord::TestCase - fixtures :authors, :essays, :posts, :comments, :categorizations, :people + fixtures :authors, :essays, :posts, :comments, :categorizations, :people, :author_addresses def test_construct_finder_sql_applies_aliases_tables_on_association_conditions result = Author.left_outer_joins(:thinking_posts, :welcome_posts).to_a diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index dc26f6a383..67ff7355b3 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -24,7 +24,7 @@ require "models/membership" require "models/essay" class NestedThroughAssociationsTest < ActiveRecord::TestCase - fixtures :authors, :books, :posts, :subscriptions, :subscribers, :tags, :taggings, + fixtures :authors, :author_addresses, :books, :posts, :subscriptions, :subscribers, :tags, :taggings, :people, :readers, :references, :jobs, :ratings, :comments, :members, :member_details, :member_types, :sponsors, :clubs, :organizations, :categories, :categories_posts, :categorizations, :memberships, :essays diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 26056f6f63..4ab690bfc6 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -22,7 +22,7 @@ require "models/interest" class AssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :developers_projects, - :computers, :people, :readers, :authors, :author_favorites + :computers, :people, :readers, :authors, :author_addresses, :author_favorites def test_eager_loading_should_not_change_count_of_children liquid = Liquid.create(name: "salty") @@ -111,7 +111,7 @@ class AssociationsTest < ActiveRecord::TestCase end class AssociationProxyTest < ActiveRecord::TestCase - fixtures :authors, :posts, :categorizations, :categories, :developers, :projects, :developers_projects + fixtures :authors, :author_addresses, :posts, :categorizations, :categories, :developers, :projects, :developers_projects def test_push_does_not_load_target david = authors(:david) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 979a59f566..ee1be09358 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -64,7 +64,7 @@ class LintTest < ActiveRecord::TestCase end class BasicsTest < ActiveRecord::TestCase - fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :categorizations, :categories, :posts + fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, "warehouse-things", :authors, :author_addresses, :categorizations, :categories, :posts def test_column_names_are_escaped conn = ActiveRecord::Base.connection diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index a0f83df80b..6032aa9250 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -7,7 +7,7 @@ if ActiveRecord::Base.connection.supports_statement_cache? && ActiveRecord::Base.connection.prepared_statements module ActiveRecord class BindParameterTest < ActiveRecord::TestCase - fixtures :topics, :authors, :posts + fixtures :topics, :authors, :author_addresses, :posts class LogListener attr_accessor :calls diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 51133e9495..3720b0cc1a 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -766,7 +766,7 @@ end class FasterFixturesTest < ActiveRecord::TestCase self.use_transactional_tests = false - fixtures :categories, :authors + fixtures :categories, :authors, :author_addresses def load_extra_fixture(name) fixture = create_fixtures(name).first diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb index 5a1d066aef..9b4b61b16e 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -168,7 +168,7 @@ class JsonSerializationTest < ActiveRecord::TestCase end class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase - fixtures :authors, :posts, :comments, :tags, :taggings + fixtures :authors, :author_addresses, :posts, :comments, :tags, :taggings include JsonSerializationHelpers diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb index a93061b516..24b678310d 100644 --- a/activerecord/test/cases/readonly_test.rb +++ b/activerecord/test/cases/readonly_test.rb @@ -10,7 +10,7 @@ require "models/person" require "models/ship" class ReadOnlyTest < ActiveRecord::TestCase - fixtures :authors, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers + fixtures :authors, :author_addresses, :posts, :comments, :developers, :projects, :developers_projects, :people, :readers def test_cant_save_readonly_record dev = Developer.find(1) diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index 278dac8171..9e95149ede 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -8,7 +8,7 @@ require "models/project" require "models/rating" class RelationMergingTest < ActiveRecord::TestCase - fixtures :developers, :comments, :authors, :posts + fixtures :developers, :comments, :authors, :author_addresses, :posts def test_relation_merging devs = Developer.where("salary >= 80000").merge(Developer.limit(2)).merge(Developer.order("id ASC").where("id < 3")) @@ -114,7 +114,7 @@ class RelationMergingTest < ActiveRecord::TestCase end class MergingDifferentRelationsTest < ActiveRecord::TestCase - fixtures :posts, :authors, :developers + fixtures :posts, :authors, :author_addresses, :developers test "merging where relations" do hello_by_bob = Post.where(body: "hello").joins(:author). diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index ed8ffddcf5..c7b2ac90fb 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -15,7 +15,7 @@ require "models/vertex" module ActiveRecord class WhereTest < ActiveRecord::TestCase - fixtures :posts, :edges, :authors, :binaries, :essays, :cars, :treasures, :price_estimates + fixtures :posts, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates def test_where_copies_bind_params author = authors(:david) diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 1241bb54a4..5fb32270b7 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -6,7 +6,7 @@ require "models/rating" module ActiveRecord class RelationTest < ActiveRecord::TestCase - fixtures :posts, :comments, :authors + fixtures :posts, :comments, :authors, :author_addresses FakeKlass = Struct.new(:table_name, :name) do extend ActiveRecord::Delegation::DelegateCache diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index e17cb1795e..8c06b1537a 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -22,7 +22,7 @@ require "models/categorization" require "models/edge" class RelationTest < ActiveRecord::TestCase - fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments, + fixtures :authors, :author_addresses, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments, :tags, :taggings, :cars, :minivans class TopicWithCallbacks < ActiveRecord::Base diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index a1ae57fdbb..3fbff7664b 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -10,7 +10,7 @@ require "models/person" require "models/reference" class RelationScopingTest < ActiveRecord::TestCase - fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects + fixtures :authors, :author_addresses, :developers, :projects, :comments, :posts, :developers_projects setup do developers(:david) @@ -238,7 +238,7 @@ class RelationScopingTest < ActiveRecord::TestCase end class NestedRelationScopingTest < ActiveRecord::TestCase - fixtures :authors, :developers, :projects, :comments, :posts + fixtures :authors, :author_addresses, :developers, :projects, :comments, :posts def test_merge_options Developer.where("salary = 80000").scoping do diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 5653fd83fd..baa41a3a47 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -350,6 +350,14 @@ module ActiveRecord ENV["VERBOSE"], ENV["VERSION"] = verbose, version end + def test_migrate_raise_error_on_empty_version + version = ENV["VERSION"] + ENV["VERSION"] = "" + assert_raise(RuntimeError, "Empty VERSION provided") { ActiveRecord::Tasks::DatabaseTasks.migrate } + ensure + ENV["VERSION"] = version + end + def test_migrate_clears_schema_cache_afterward ActiveRecord::Base.expects(:clear_cache!) ActiveRecord::Tasks::DatabaseTasks.migrate diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 111495c481..5c6d78b574 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -10,7 +10,7 @@ require "models/movie" class TransactionTest < ActiveRecord::TestCase self.use_transactional_tests = false - fixtures :topics, :developers, :authors, :posts + fixtures :topics, :developers, :authors, :author_addresses, :posts def setup @first, @second = Topic.find(1, 2).sort_by(&:id) diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb index 1571b31329..ab0e67cd9d 100644 --- a/activerecord/test/cases/yaml_serialization_test.rb +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -5,7 +5,7 @@ require "models/post" require "models/author" class YamlSerializationTest < ActiveRecord::TestCase - fixtures :topics, :authors, :posts + fixtures :topics, :authors, :author_addresses, :posts def test_to_yaml_with_time_with_zone_should_not_raise_exception with_timezone_config aware_attributes: true, zone: "Pacific Time (US & Canada)" do diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 7a2fc5c1b5..b27cfe53f4 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -100,12 +100,17 @@ class String # a nicer looking title. +titleize+ is meant for creating pretty output. It is not # used in the Rails internals. # + # The trailing '_id','Id'.. can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true. + # By default, this parameter is false. + # # +titleize+ is also aliased as +titlecase+. # - # 'man from the boondocks'.titleize # => "Man From The Boondocks" - # 'x-men: the last stand'.titleize # => "X Men: The Last Stand" - def titleize - ActiveSupport::Inflector.titleize(self) + # 'man from the boondocks'.titleize # => "Man From The Boondocks" + # 'x-men: the last stand'.titleize # => "X Men: The Last Stand" + # 'string_ending_with_id'.titleize(keep_id_suffix: true) # => "String Ending With Id" + def titleize(keep_id_suffix: false) + ActiveSupport::Inflector.titleize(self, keep_id_suffix: keep_id_suffix) end alias_method :titlecase, :titleize @@ -202,7 +207,7 @@ class String ActiveSupport::Inflector.classify(self) end - # Capitalizes the first word, turns underscores into spaces, and strips a + # Capitalizes the first word, turns underscores into spaces, and (by default)strips a # trailing '_id' if present. # Like +titleize+, this is meant for creating pretty output. # @@ -210,12 +215,17 @@ class String # optional parameter +capitalize+ to false. # By default, this parameter is true. # - # 'employee_salary'.humanize # => "Employee salary" - # 'author_id'.humanize # => "Author" - # 'author_id'.humanize(capitalize: false) # => "author" - # '_id'.humanize # => "Id" - def humanize(options = {}) - ActiveSupport::Inflector.humanize(self, options) + # The trailing '_id' can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true. + # By default, this parameter is false. + # + # 'employee_salary'.humanize # => "Employee salary" + # 'author_id'.humanize # => "Author" + # 'author_id'.humanize(capitalize: false) # => "author" + # '_id'.humanize # => "Id" + # 'author_id'.humanize(keep_id_suffix: true) # => "Author Id" + def humanize(capitalize: true, keep_id_suffix: false) + ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix) end # Converts just the first character to uppercase. diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb index 8e0dc71dca..f59f5d17dc 100644 --- a/activesupport/lib/active_support/evented_file_update_checker.rb +++ b/activesupport/lib/active_support/evented_file_update_checker.rb @@ -125,6 +125,11 @@ module ActiveSupport dtw.compact! dtw.uniq! + normalized_gem_paths = Gem.path.map { |path| File.join path, "" } + dtw = dtw.reject do |path| + normalized_gem_paths.any? { |gem_path| path.to_s.start_with?(gem_path) } + end + @ph.filter_out_descendants(dtw) end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 51c221ac0e..1b089a7538 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -108,33 +108,38 @@ module ActiveSupport # * Replaces underscores with spaces, if any. # * Downcases all words except acronyms. # * Capitalizes the first word. - # # The capitalization of the first word can be turned off by setting the # +:capitalize+ option to false (default is true). # - # humanize('employee_salary') # => "Employee salary" - # humanize('author_id') # => "Author" - # humanize('author_id', capitalize: false) # => "author" - # humanize('_id') # => "Id" + # The trailing '_id' can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true (default is false). + # + # humanize('employee_salary') # => "Employee salary" + # humanize('author_id') # => "Author" + # humanize('author_id', capitalize: false) # => "author" + # humanize('_id') # => "Id" + # humanize('author_id', keep_id_suffix: true) # => "Author Id" # # If "SSL" was defined to be an acronym: # # humanize('ssl_error') # => "SSL error" # - def humanize(lower_case_and_underscored_word, options = {}) + def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false) result = lower_case_and_underscored_word.to_s.dup inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) } result.sub!(/\A_+/, "".freeze) - result.sub!(/_id\z/, "".freeze) + unless keep_id_suffix + result.sub!(/_id\z/, "".freeze) + end result.tr!("_".freeze, " ".freeze) result.gsub!(/([a-z\d]*)/i) do |match| "#{inflections.acronyms[match] || match.downcase}" end - if options.fetch(:capitalize, true) + if capitalize result.sub!(/\A\w/) { |match| match.upcase } end @@ -154,14 +159,21 @@ module ActiveSupport # create a nicer looking title. +titleize+ is meant for creating pretty # output. It is not used in the Rails internals. # + # The trailing '_id','Id'.. can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true. + # By default, this parameter is false. + # # +titleize+ is also aliased as +titlecase+. # - # titleize('man from the boondocks') # => "Man From The Boondocks" - # titleize('x-men: the last stand') # => "X Men: The Last Stand" - # titleize('TheManWithoutAPast') # => "The Man Without A Past" - # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark" - def titleize(word) - humanize(underscore(word)).gsub(/\b(?<!\w['’`])[a-z]/) { |match| match.capitalize } + # titleize('man from the boondocks') # => "Man From The Boondocks" + # titleize('x-men: the last stand') # => "X Men: The Last Stand" + # titleize('TheManWithoutAPast') # => "The Man Without A Past" + # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark" + # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id" + def titleize(word, keep_id_suffix: false) + humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`])[a-z]/) do |match| + match.capitalize + end end # Creates the name of a table like Rails does for models to table names. diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 18477b9f6b..ce5207546d 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -250,14 +250,21 @@ module ActiveSupport # for time zones in the country specified by its ISO 3166-1 Alpha2 code. def country_zones(country_code) code = country_code.to_s.upcase - @country_zones[code] ||= - TZInfo::Country.get(code).zone_identifiers.map do |tz_id| - name = MAPPING.key(tz_id) - name && self[name] - end.compact.sort! + @country_zones[code] ||= load_country_zones(code) end private + def load_country_zones(code) + country = TZInfo::Country.get(code) + country.zone_identifiers.map do |tz_id| + if MAPPING.value?(tz_id) + self[MAPPING.key(tz_id)] + else + create(tz_id, nil, TZInfo::Timezone.new(tz_id)) + end + end.sort! + end + def zones_map @zones_map ||= begin MAPPING.each_key { |place| self[place] } # load all the zones diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 41cc9888c6..a98951e889 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -78,6 +78,12 @@ class StringInflectionsTest < ActiveSupport::TestCase end end + def test_titleize_with_keep_id_suffix + MixtureToTitleCaseWithKeepIdSuffix.each do |before, titleized| + assert_equal(titleized, before.titleize(keep_id_suffix: true)) + end + end + def test_upcase_first assert_equal "What a Lovely Day", "what a Lovely Day".upcase_first end @@ -199,6 +205,12 @@ class StringInflectionsTest < ActiveSupport::TestCase end end + def test_humanize_with_keep_id_suffix + UnderscoreToHumanWithKeepIdSuffix.each do |underscore, human| + assert_equal(human, underscore.humanize(keep_id_suffix: true)) + end + end + def test_humanize_with_html_escape assert_equal "Hello", ERB::Util.html_escape("hello").humanize end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 03d7b3fe94..14bc10513b 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -119,6 +119,13 @@ class InflectorTest < ActiveSupport::TestCase end end + MixtureToTitleCaseWithKeepIdSuffix.each_with_index do |(before, titleized), index| + define_method "test_titleize_with_keep_id_suffix_mixture_to_title_case_#{index}" do + assert_equal(titleized, ActiveSupport::Inflector.titleize(before, keep_id_suffix: true), + "mixture to TitleCase with keep_id_suffix failed for #{before}") + end + end + def test_camelize CamelToUnderscore.each do |camel, underscore| assert_equal(camel, ActiveSupport::Inflector.camelize(underscore)) @@ -324,6 +331,12 @@ class InflectorTest < ActiveSupport::TestCase end end + def test_humanize_with_keep_id_suffix + UnderscoreToHumanWithKeepIdSuffix.each do |underscore, human| + assert_equal(human, ActiveSupport::Inflector.humanize(underscore, keep_id_suffix: true)) + end + end + def test_humanize_by_rule ActiveSupport::Inflector.inflections do |inflect| inflect.human(/_cnt$/i, '\1_count') diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index f3352e3301..d61ca3fc18 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -248,12 +248,27 @@ module InflectorTestCases "_external_id" => "External" } + UnderscoreToHumanWithKeepIdSuffix = { + "this_is_a_string_ending_with_id" => "This is a string ending with id", + "employee_id" => "Employee id", + "employee_id_something_else" => "Employee id something else", + "underground" => "Underground", + "_id" => "Id", + "_external_id" => "External id" + } + UnderscoreToHumanWithoutCapitalize = { "employee_salary" => "employee salary", "employee_id" => "employee", "underground" => "underground" } + MixtureToTitleCaseWithKeepIdSuffix = { + "this_is_a_string_ending_with_id" => "This Is A String Ending With Id", + "EmployeeId" => "Employee Id", + "Author Id" => "Author Id" + } + MixtureToTitleCase = { "active_record" => "Active Record", "ActiveRecord" => "Active Record", diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 1615d8fdb2..de111cc40e 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -714,6 +714,10 @@ class TimeZoneTest < ActiveSupport::TestCase assert_not_includes ActiveSupport::TimeZone.country_zones(:ru), ActiveSupport::TimeZone["Kuala Lumpur"] end + def test_country_zones_without_mappings + assert_includes ActiveSupport::TimeZone.country_zones(:sv), ActiveSupport::TimeZone["America/El_Salvador"] + end + def test_to_yaml assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n", ActiveSupport::TimeZone["Hawaii"].to_yaml) assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Europe/London\n", ActiveSupport::TimeZone["Europe/London"].to_yaml) diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 66873748ce..6032d2e1a1 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,9 +1 @@ -* Specify form field ids when generating a scaffold. - - This makes sure that the labels are linked up with the fields. The - regression was introduced when the template was switched to - `form_with`. - - *Yves Senn* - Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/railties/CHANGELOG.md) for previous changes. diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index c3b91b8af9..7c49fabba5 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -14,9 +14,8 @@ module Rails :ssl_options, :public_file_server, :session_options, :time_zone, :reload_classes_only_on_change, :beginning_of_week, :filter_redirect, :x, :enable_dependency_loading, - :read_encrypted_secrets + :read_encrypted_secrets, :log_level - attr_writer :log_level attr_reader :encoding, :api_only def initialize(*) @@ -35,7 +34,7 @@ module Rails @session_store = nil @time_zone = "UTC" @beginning_of_week = :monday - @log_level = nil + @log_level = :debug @generators = app_generators @cache_store = [ :file_store, "#{root}/tmp/cache/" ] @railties_order = [:all] @@ -152,10 +151,6 @@ module Rails raise e, "Cannot load `Rails.application.database_configuration`:\n#{e.message}", e.backtrace end - def log_level - @log_level ||= (Rails.env.production? ? :info : :debug) - end - def colorize_logging ActiveSupport::LogSubscriber.colorize_logging end diff --git a/railties/lib/rails/commands/destroy/destroy_command.rb b/railties/lib/rails/commands/destroy/destroy_command.rb index 794673851d..281732a936 100644 --- a/railties/lib/rails/commands/destroy/destroy_command.rb +++ b/railties/lib/rails/commands/destroy/destroy_command.rb @@ -5,6 +5,9 @@ module Rails class DestroyCommand < Base # :nodoc: no_commands do def help + require_application_and_environment! + load_generators + Rails::Generators.help self.class.command_name end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index fbb6b5039c..9109be5e04 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -31,9 +31,6 @@ module Rails class_option :database, type: :string, aliases: "-d", default: "sqlite3", desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})" - class_option :javascript, type: :string, aliases: "-j", - desc: "Preconfigure for selected JavaScript library" - class_option :webpack, type: :string, default: nil, desc: "Preconfigure for app-like JavaScript with Webpack (options: #{WEBPACKS.join('/')})" @@ -342,11 +339,6 @@ module Rails gems = [javascript_runtime_gemfile_entry] gems << coffee_gemfile_entry unless options[:skip_coffee] - if options[:javascript] - gems << GemfileEntry.version("#{options[:javascript]}-rails", nil, - "Use #{options[:javascript]} as the JavaScript library") - end - unless options[:skip_turbolinks] gems << GemfileEntry.version("turbolinks", "~> 5", "Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks") @@ -413,6 +405,10 @@ module Rails !options[:skip_spring] && !options.dev? && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin") end + def depends_on_system_test? + !(options[:skip_system_test] || options[:skip_test] || options[:api]) + end + def depend_on_listen? !options[:skip_listen] && os_supports_listen_out_of_the_box? end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index b32878cf0b..324843a5f5 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -84,6 +84,16 @@ module Rails chmod "bin", 0755 & ~File.umask, verbose: false end + def bin_when_updating + bin_yarn_exist = File.exist?("bin/yarn") + + bin + + if options[:api] && !bin_yarn_exist + remove_file "bin/yarn" + end + end + def config empty_directory "config" @@ -106,6 +116,8 @@ module Rails cookie_serializer_config_exist = File.exist?("config/initializers/cookies_serializer.rb") action_cable_config_exist = File.exist?("config/cable.yml") rack_cors_config_exist = File.exist?("config/initializers/cors.rb") + assets_config_exist = File.exist?("config/initializers/assets.rb") + new_framework_defaults_5_1_exist = File.exist?("config/initializers/new_framework_defaults_5_1.rb") config @@ -120,6 +132,22 @@ module Rails unless rack_cors_config_exist remove_file "config/initializers/cors.rb" end + + if options[:api] + unless cookie_serializer_config_exist + remove_file "config/initializers/cookies_serializer.rb" + end + + unless assets_config_exist + remove_file "config/initializers/assets.rb" + end + + # Sprockets owns the only new default for 5.1: + # In API-only Applications, we don't want the file. + unless new_framework_defaults_5_1_exist + remove_file "config/initializers/new_framework_defaults_5_1.rb" + end + end end def database_yml @@ -230,6 +258,11 @@ module Rails build(:bin) end + def update_bin_files + build(:bin_when_updating) + end + remove_task :update_bin_files + def create_config_files build(:config) end @@ -275,7 +308,7 @@ module Rails end def create_system_test_files - build(:system_test) unless options[:skip_system_test] || options[:skip_test] || options[:api] + build(:system_test) if depends_on_system_test? end def create_tmp_files diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index ab4aa04fff..06f0dd6d6d 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -32,7 +32,7 @@ end group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] - <%- unless options.skip_system_test? || options.api? -%> + <%- if depends_on_system_test? -%> # Adds support for Capybara system testing and selenium driver gem 'capybara', '~> 2.13.0' gem 'selenium-webdriver' diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index 511b4a82eb..b75b65c8df 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -13,6 +13,7 @@ Rails.application.configure do config.consider_all_requests_local = true # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index f5586b53f0..32a6b109bc 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -63,7 +63,7 @@ namespace :app do # desc "Adds new executables to the application bin/ directory" task :bin do - RailsUpdate.invoke_from_app_generator :create_bin_files + RailsUpdate.invoke_from_app_generator :update_bin_files end task :upgrade_guide_info do diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index ee0d697599..fe581db286 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -189,6 +189,9 @@ module ApplicationTests FileUtils.cd(rails_root) do output = `bin/rails generate --help` assert_no_match "active_record:migration", output + + output = `bin/rails destroy --help` + assert_no_match "active_record:migration", output end end end diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb index dc1d816dc5..93b5263bf7 100644 --- a/railties/test/application/middleware/cache_test.rb +++ b/railties/test/application/middleware/cache_test.rb @@ -117,12 +117,12 @@ module ApplicationTests assert_equal "miss, store", last_response.headers["X-Rack-Cache"] assert_equal "public", last_response.headers["Cache-Control"] - body = last_response.body etag = last_response.headers["ETag"] - get "/expires/expires_etag", {}, "If-None-Match" => etag + get "/expires/expires_etag", {}, "HTTP_IF_NONE_MATCH" => etag assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"] - assert_equal body, last_response.body + assert_equal 304, last_response.status + assert_equal "", last_response.body end def test_cache_works_with_etags_private @@ -137,7 +137,7 @@ module ApplicationTests body = last_response.body etag = last_response.headers["ETag"] - get "/expires/expires_etag", { private: true }, "If-None-Match" => etag + get "/expires/expires_etag", { private: true }, "HTTP_IF_NONE_MATCH" => etag assert_equal "miss", last_response.headers["X-Rack-Cache"] assert_not_equal body, last_response.body end @@ -151,12 +151,12 @@ module ApplicationTests assert_equal "miss, store", last_response.headers["X-Rack-Cache"] assert_equal "public", last_response.headers["Cache-Control"] - body = last_response.body last = last_response.headers["Last-Modified"] - get "/expires/expires_last_modified", {}, "If-Modified-Since" => last + get "/expires/expires_last_modified", {}, "HTTP_IF_MODIFIED_SINCE" => last assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"] - assert_equal body, last_response.body + assert_equal 304, last_response.status + assert_equal "", last_response.body end def test_cache_works_with_last_modified_private @@ -171,7 +171,7 @@ module ApplicationTests body = last_response.body last = last_response.headers["Last-Modified"] - get "/expires/expires_last_modified", { private: true }, "If-Modified-Since" => last + get "/expires/expires_last_modified", { private: true }, "HTTP_IF_MODIFIED_SINCE" => last assert_equal "miss", last_response.headers["X-Rack-Cache"] assert_not_equal body, last_response.body end diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb index 76cb302c62..00f6620188 100644 --- a/railties/test/application/rake/migrations_test.rb +++ b/railties/test/application/rake/migrations_test.rb @@ -37,6 +37,22 @@ module ApplicationTests end end + test "migration with empty version" do + Dir.chdir(app_path) do + output = `bin/rails db:migrate VERSION= 2>&1` + assert_match(/Empty VERSION provided/, output) + + output = `bin/rails db:migrate:redo VERSION= 2>&1` + assert_match(/Empty VERSION provided/, output) + + output = `bin/rails db:migrate:up VERSION= 2>&1` + assert_match(/VERSION is required/, output) + + output = `bin/rails db:migrate:down VERSION= 2>&1` + assert_match(/VERSION is required - To go down one migration, use db:rollback/, output) + end + end + test "model and migration generator with change syntax" do Dir.chdir(app_path) do `bin/rails generate model user username:string password:string; diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index bb8b29501a..2edb39c8e8 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -61,6 +61,28 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase end end + def test_app_update_does_not_generate_unnecessary_config_files + run_generator + + generator = Rails::Generators::AppGenerator.new ["rails"], + { api: true, update: true }, destination_root: destination_root, shell: @shell + quietly { generator.send(:update_config_files) } + + assert_no_file "config/initializers/cookies_serializer.rb" + assert_no_file "config/initializers/assets.rb" + assert_no_file "config/initializers/new_framework_defaults_5_1.rb" + end + + def test_app_update_does_not_generate_unnecessary_bin_files + run_generator + + generator = Rails::Generators::AppGenerator.new ["rails"], + { api: true, update: true }, destination_root: destination_root, shell: @shell + quietly { generator.send(:update_bin_files) } + + assert_no_file "bin/yarn" + end + private def default_files diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index f6cdbd34fe..7965bd68d0 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -436,6 +436,30 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile", /^# gem 'redis'/ end + def test_generator_if_skip_test_is_given + run_generator [destination_root, "--skip-test"] + assert_file "Gemfile" do |content| + assert_no_match(/capybara/, content) + assert_no_match(/selenium-webdriver/, content) + end + end + + def test_generator_if_skip_system_test_is_given + run_generator [destination_root, "--skip_system_test"] + assert_file "Gemfile" do |content| + assert_no_match(/capybara/, content) + assert_no_match(/selenium-webdriver/, content) + end + end + + def test_generator_if_api_is_given + run_generator [destination_root, "--api"] + assert_file "Gemfile" do |content| + assert_no_match(/capybara/, content) + assert_no_match(/selenium-webdriver/, content) + end + end + def test_inclusion_of_javascript_runtime run_generator if defined?(JRUBY_VERSION) @@ -452,14 +476,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_inclusion_of_javascript_libraries_if_required - run_generator [destination_root, "-j", "jquery"] - assert_file "app/assets/javascripts/application.js" do |contents| - assert_match %r{^//= require jquery}, contents - end - assert_gem "jquery-rails" - end - def test_javascript_is_skipped_if_required run_generator [destination_root, "--skip-javascript"] |