diff options
104 files changed, 1313 insertions, 700 deletions
diff --git a/.travis.yml b/.travis.yml index ae4d78a31f..2006291052 100644 --- a/.travis.yml +++ b/.travis.yml @@ -100,7 +100,7 @@ matrix: - rvm: jruby-9.1.7.0 jdk: oraclejdk8 env: - - "GEM=am,aj" + - "GEM=am,amo,aj" allow_failures: - rvm: ruby-head - rvm: jruby-9.1.7.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6ebef7e89..b44486c75a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,9 @@ #### **Did you find a bug?** +* **Do not open up a GitHub issue if the bug is a security vulnerability + in Rails**, and instead to refer to our [security policy](http://rubyonrails.org/security/). + * **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/rails/rails/issues). * If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/rails/rails/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. @@ -71,13 +71,17 @@ and may also be used independently outside Rails. * [Getting Started with Rails](http://guides.rubyonrails.org/getting_started.html) * [Ruby on Rails Guides](http://guides.rubyonrails.org) * [The API Documentation](http://api.rubyonrails.org) - * [Ruby on Rails Tutorial](http://www.railstutorial.org/book) + * [Ruby on Rails Tutorial](https://www.railstutorial.org/book) ## Contributing We encourage you to contribute to Ruby on Rails! Please check out the [Contributing to Ruby on Rails guide](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) for guidelines about how to proceed. [Join us!](http://contributors.rubyonrails.org) +Trying to report a possible security vulnerability in Rails? Please +check out our [security policy](http://rubyonrails.org/security/) for +guidelines about how to proceed. + Everyone interacting in Rails and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](http://rubyonrails.org/conduct/). ## Code Status diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 641af029aa..6bb1c63610 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,10 @@ +* Commit flash changes when using a redirect route. + + Fixes #27992. + + *Andrew White* + + ## Rails 5.1.0.beta1 (February 23, 2017) ## * Prefer `remove_method` over `undef_method` when reloading routes @@ -13,7 +20,7 @@ ``` ruby resource :basket - resolve(class: "Basket") { [:basket] } + resolve("Basket") { [:basket] } ``` ``` erb diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 10d733e477..6e06c70dc2 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1904,7 +1904,7 @@ module ActionDispatch ast = Journey::Parser.parse path mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options) - @set.add_route(mapping, ast, as, anchor) + @set.add_route(mapping, as) end def match_root_route(options) diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index dabc045007..e8f47b8640 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -36,6 +36,8 @@ module ActionDispatch uri.host ||= req.host uri.port ||= req.port unless req.standard_port? + req.commit_flash + body = %(<html><body>You are being <a href="#{ERB::Util.unwrapped_html_escape(uri.to_s)}">redirected</a>.</body></html>) headers = { diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 2672cd24ed..c4719f8a71 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -565,7 +565,7 @@ module ActionDispatch routes.empty? end - def add_route(mapping, path_ast, name, anchor) + def add_route(mapping, name) raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i) if name && named_routes[name] diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb index 70a5b75781..d6388d8fb7 100644 --- a/actionpack/lib/action_dispatch/system_test_case.rb +++ b/actionpack/lib/action_dispatch/system_test_case.rb @@ -105,9 +105,16 @@ module ActionDispatch # # driven_by :selenium, screen_size: [800, 800] def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400]) - SystemTesting::Driver.new(driver).run + driver = if selenium?(driver) + SystemTesting::Browser.new(using, screen_size) + else + SystemTesting::Driver.new(driver) + end + + setup { driver.use } + teardown { driver.reset } + SystemTesting::Server.new.run - SystemTesting::Browser.new(using, screen_size).run if selenium?(driver) end def self.selenium?(driver) # :nodoc: diff --git a/actionpack/lib/action_dispatch/system_testing/browser.rb b/actionpack/lib/action_dispatch/system_testing/browser.rb index c9a6628516..14ea06459d 100644 --- a/actionpack/lib/action_dispatch/system_testing/browser.rb +++ b/actionpack/lib/action_dispatch/system_testing/browser.rb @@ -1,14 +1,17 @@ +require "action_dispatch/system_testing/driver" + module ActionDispatch module SystemTesting - class Browser # :nodoc: + class Browser < Driver # :nodoc: def initialize(name, screen_size) + super(name) @name = name @screen_size = screen_size end - def run + def use register - setup + super end private @@ -19,10 +22,6 @@ module ActionDispatch end end end - - def setup - Capybara.default_driver = @name.to_sym - end end end end diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb index 7c2ad84e19..8decb54419 100644 --- a/actionpack/lib/action_dispatch/system_testing/driver.rb +++ b/actionpack/lib/action_dispatch/system_testing/driver.rb @@ -5,14 +5,14 @@ module ActionDispatch @name = name end - def run - register + def use + @current = Capybara.current_driver + Capybara.current_driver = @name end - private - def register - Capybara.default_driver = @name - end + def reset + Capybara.current_driver = @current + end end end end diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb index 491559eedf..1c89bfacfa 100644 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb @@ -10,8 +10,8 @@ module ActionDispatch end def after_teardown - super take_failed_screenshot + super Capybara.reset_sessions! end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 53758a4fbc..d563df91df 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -4913,3 +4913,52 @@ class TestInternalRoutingParams < ActionDispatch::IntegrationTest ) end end + +class FlashRedirectTest < ActionDispatch::IntegrationTest + SessionKey = "_myapp_session" + Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33") + + class KeyGeneratorMiddleware + def initialize(app) + @app = app + end + + def call(env) + env["action_dispatch.key_generator"] ||= Generator + @app.call(env) + end + end + + class FooController < ActionController::Base + def bar + render plain: (flash[:foo] || "foo") + end + end + + Routes = ActionDispatch::Routing::RouteSet.new + Routes.draw do + get "/foo", to: redirect { |params, req| req.flash[:foo] = "bar"; "/bar" } + get "/bar", to: "flash_redirect_test/foo#bar" + end + + APP = build_app Routes do |middleware| + middleware.use KeyGeneratorMiddleware + middleware.use ActionDispatch::Session::CookieStore, key: SessionKey + middleware.use ActionDispatch::Flash + middleware.delete ActionDispatch::ShowExceptions + end + + def app + APP + end + + include Routes.url_helpers + + def test_block_redirect_commits_flash + get "/foo", env: { "action_dispatch.key_generator" => Generator } + assert_response :redirect + + follow_redirect! + assert_equal "bar", response.body + end +end diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index bd8318f5f6..3082d1072b 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -224,7 +224,7 @@ module StaticTests def assert_gzip(file_name, response) expected = File.read("#{FIXTURE_LOAD_PATH}/#{public_path}" + file_name) - actual = Zlib::GzipReader.new(StringIO.new(response.body)).read + actual = ActiveSupport::Gzip.decompress(response.body) assert_equal expected, actual end diff --git a/actionpack/test/dispatch/system_testing/system_test_case_test.rb b/actionpack/test/dispatch/system_testing/system_test_case_test.rb index a384902a14..ff01d6739a 100644 --- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb +++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb @@ -1,21 +1,23 @@ require "abstract_unit" -class SystemTestCaseTest < ActiveSupport::TestCase - test "driven_by sets Capybara's default driver to poltergeist" do - ActionDispatch::SystemTestCase.driven_by :poltergeist - - assert_equal :poltergeist, Capybara.default_driver +class DrivenByCaseTestTest < ActiveSupport::TestCase + test "selenium? returns false if driver is poltergeist" do + assert_not ActionDispatch::SystemTestCase.selenium?(:poltergeist) end +end - test "driven_by sets Capybara's drivers respectively" do - ActionDispatch::SystemTestCase.driven_by :selenium, using: :chrome +class DrivenByRackTestTest < ActionDispatch::SystemTestCase + driven_by :rack_test - assert_includes Capybara.drivers, :selenium - assert_includes Capybara.drivers, :chrome - assert_equal :chrome, Capybara.default_driver + test "uses rack_test" do + assert_equal :rack_test, Capybara.current_driver end +end - test "selenium? returns false if driver is poltergeist" do - assert_not ActionDispatch::SystemTestCase.selenium?(:poltergeist) +class DrivenBySeleniumWithChromeTest < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome + + test "uses selenium" do + assert_equal :chrome, Capybara.current_driver end end diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index f5d2c9f23b..f2ae74454a 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,10 @@ +* Remove the option `encode_special_chars` misnomer from `strip_tags` + + As of rails-html-sanitizer v1.0.3 sanitizer will ignore the + `encode_special_chars` option. Fixes #28060. + + *Andrew Hood* + ## Rails 5.1.0.beta1 (February 23, 2017) ## * Change the ERB handler from Erubis to Erubi. diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 1e9b813d3d..0abd5bc5dc 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -13,6 +13,7 @@ module ActionView # It also strips href/src attributes with unsafe protocols like # <tt>javascript:</tt>, while also protecting against attempts to use Unicode, # ASCII, and hex character references to work around these protocol filters. + # All special characters will be escaped. # # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information. @@ -20,8 +21,7 @@ module ActionView # Custom sanitization rules can also be provided. # # Please note that sanitizing user-provided text does not guarantee that the - # resulting markup is valid or even well-formed. For example, the output may still - # contain unescaped characters like <tt><</tt>, <tt>></tt>, or <tt>&</tt>. + # resulting markup is valid or even well-formed. # # ==== Options # @@ -86,7 +86,7 @@ module ActionView self.class.white_list_sanitizer.sanitize_css(style) end - # Strips all HTML tags from +html+, including comments. + # Strips all HTML tags from +html+, including comments and special characters. # # strip_tags("Strip <i>these</i> tags!") # # => Strip these tags! @@ -96,8 +96,11 @@ module ActionView # # strip_tags("<div id='top-bar'>Welcome to my website!</div>") # # => Welcome to my website! + # + # strip_tags("> A quote from Smith & Wesson") + # # => > A quote from Smith & Wesson def strip_tags(html) - self.class.full_sanitizer.sanitize(html, encode_special_chars: false) + self.class.full_sanitizer.sanitize(html) end # Strips all link tags from +html+ leaving just the link text. @@ -110,6 +113,9 @@ module ActionView # # strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.') # # => Blog: Visit. + # + # strip_links('<<a href="https://example.org">malformed & link</a>') + # # => <malformed & link def strip_links(html) self.class.link_sanitizer.sanitize(html) end diff --git a/actionview/test/template/sanitize_helper_test.rb b/actionview/test/template/sanitize_helper_test.rb index c8963fee9c..11ed55456f 100644 --- a/actionview/test/template/sanitize_helper_test.rb +++ b/actionview/test/template/sanitize_helper_test.rb @@ -10,6 +10,7 @@ class SanitizeHelperTest < ActionView::TestCase assert_equal "on my mind\nall day long", strip_links("<a href='almost'>on my mind</a>\n<A href='almost'>all day long</A>") assert_equal "Magic", strip_links("<a href='http://www.rubyonrails.com/'>Mag<a href='http://www.ruby-lang.org/'>ic") assert_equal "My mind\nall <b>day</b> long", strip_links("<a href='almost'>My mind</a>\n<A href='almost'>all <b>day</b> long</A>") + assert_equal "<malformed & link", strip_links('<<a href="https://example.org">malformed & link</a>') end def test_sanitize_form @@ -26,6 +27,7 @@ class SanitizeHelperTest < ActionView::TestCase assert_equal("Dont touch me", strip_tags("Dont touch me")) assert_equal("This is a test.", strip_tags("<p>This <u>is<u> a <a href='test.html'><strong>test</strong></a>.</p>")) assert_equal "This has a here.", strip_tags("This has a <!-- comment --> here.") + assert_equal("Jekyll & Hyde", strip_tags("Jekyll & Hyde")) assert_equal "", strip_tags("<script>") end diff --git a/activemodel/lib/active_model/type/date.rb b/activemodel/lib/active_model/type/date.rb index 6e313fbca8..eefd080351 100644 --- a/activemodel/lib/active_model/type/date.rb +++ b/activemodel/lib/active_model/type/date.rb @@ -12,7 +12,7 @@ module ActiveModel end def type_cast_for_schema(value) - "'#{value.to_s(:db)}'" + value.to_s(:db).inspect end private diff --git a/activemodel/lib/active_model/type/decimal.rb b/activemodel/lib/active_model/type/decimal.rb index 541a12c8a1..e6805c5f6b 100644 --- a/activemodel/lib/active_model/type/decimal.rb +++ b/activemodel/lib/active_model/type/decimal.rb @@ -21,8 +21,14 @@ module ActiveModel case value when ::Float convert_float_to_big_decimal(value) - when ::Numeric, ::String + when ::Numeric BigDecimal(value, precision || BIGDECIMAL_PRECISION) + when ::String + begin + value.to_d + rescue ArgumentError + BigDecimal(0) + end else if value.respond_to?(:to_d) value.to_d diff --git a/activemodel/lib/active_model/type/helpers/time_value.rb b/activemodel/lib/active_model/type/helpers/time_value.rb index e57a52104b..53cf7c6029 100644 --- a/activemodel/lib/active_model/type/helpers/time_value.rb +++ b/activemodel/lib/active_model/type/helpers/time_value.rb @@ -38,7 +38,7 @@ module ActiveModel end def type_cast_for_schema(value) - "'#{value.to_s(:db)}'" + value.to_s(:db).inspect end def user_input_in_time_zone(value) diff --git a/activemodel/test/cases/type/decimal_test.rb b/activemodel/test/cases/type/decimal_test.rb index 46a913258e..c3b43725cc 100644 --- a/activemodel/test/cases/type/decimal_test.rb +++ b/activemodel/test/cases/type/decimal_test.rb @@ -11,6 +11,14 @@ module ActiveModel assert_equal BigDecimal.new("1"), type.cast(:"1") end + def test_type_cast_decimal_from_invalid_string + type = Decimal.new + assert_nil type.cast("") + assert_equal BigDecimal.new("1"), type.cast("1ignore") + assert_equal BigDecimal.new("0"), type.cast("bad1") + assert_equal BigDecimal.new("0"), type.cast("bad") + end + def test_type_cast_decimal_from_float_with_large_precision type = Decimal.new(precision: ::Float::DIG + 2) assert_equal BigDecimal.new("123.0"), type.cast(123.0) diff --git a/activemodel/test/cases/type/float_test.rb b/activemodel/test/cases/type/float_test.rb index 2e34f57f7e..8026d63ad5 100644 --- a/activemodel/test/cases/type/float_test.rb +++ b/activemodel/test/cases/type/float_test.rb @@ -9,6 +9,14 @@ module ActiveModel assert_equal 1.0, type.cast("1") end + def test_type_cast_float_from_invalid_string + type = Type::Float.new + assert_nil type.cast("") + assert_equal 1.0, type.cast("1ignore") + assert_equal 0.0, type.cast("bad1") + assert_equal 0.0, type.cast("bad") + end + def test_changing_float type = Type::Float.new diff --git a/activemodel/test/cases/type/integer_test.rb b/activemodel/test/cases/type/integer_test.rb index 2b9b03f3cf..a91144036b 100644 --- a/activemodel/test/cases/type/integer_test.rb +++ b/activemodel/test/cases/type/integer_test.rb @@ -7,6 +7,7 @@ module ActiveModel class IntegerTest < ActiveModel::TestCase test "simple values" do type = Type::Integer.new + assert_nil type.cast("") assert_equal 1, type.cast(1) assert_equal 1, type.cast("1") assert_equal 1, type.cast("1ignore") diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index aeabd5b39d..55773f2172 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,19 @@ +* Use `max_identifier_length` for `index_name_length` in PostgreSQL adapter. + + *Ryuta Kamizono* + +* Deprecate `supports_migrations?` on connection adapters. + + *Ryuta Kamizono* + +* Fix regression of #1969 with SELECT aliases in HAVING clause. + + *Eugene Kenny* + +* Deprecate using `#quoted_id` in quoting. + + *Ryuta Kamizono* + * Fix `wait_timeout` to configurable for mysql2 adapter. Fixes #26556. diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 31c1e687dc..6aa414ba6b 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -50,7 +50,7 @@ module ActiveRecord super.tap do @previous_mutation_tracker = nil clear_mutation_trackers - @changed_attributes = HashWithIndifferentAccess.new + @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end end @@ -70,13 +70,13 @@ module ActiveRecord def changes_applied @previous_mutation_tracker = mutation_tracker - @changed_attributes = HashWithIndifferentAccess.new + @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new clear_mutation_trackers end def clear_changes_information @previous_mutation_tracker = nil - @changed_attributes = HashWithIndifferentAccess.new + @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new forget_attribute_assignments clear_mutation_trackers end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 7f4132accf..e5a24b2aca 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -7,8 +7,13 @@ module ActiveRecord # Quotes the column value to help prevent # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection]. def quote(value) - # records are quoted as their primary key - return value.quoted_id if value.respond_to?(:quoted_id) + value = id_value_for_database(value) if value.is_a?(Base) + + if value.respond_to?(:quoted_id) + ActiveSupport::Deprecation.warn \ + "Using #quoted_id is deprecated and will be removed in Rails 5.2." + return value.quoted_id + end _quote(value) end @@ -17,6 +22,8 @@ module ActiveRecord # SQLite does not understand dates, so this method will convert a Date # to a String. def type_cast(value, column = nil) + value = id_value_for_database(value) if value.is_a?(Base) + if value.respond_to?(:quoted_id) && value.respond_to?(:id) return value.id end @@ -151,6 +158,12 @@ module ActiveRecord binds.map { |attr| type_cast(attr.value_for_database) } end + def id_value_for_database(value) + if primary_key = value.class.primary_key + value.instance_variable_get(:@attributes)[primary_key].value_for_database + end + end + def types_which_need_no_typecasting [nil, Numeric, String] end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index c43a2d1508..e683106527 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -334,18 +334,16 @@ module ActiveRecord # part_id int NOT NULL, # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 # - def create_join_table(table_1, table_2, options = {}) + def create_join_table(table_1, table_2, column_options: {}, **options) join_table_name = find_join_table_name(table_1, table_2, options) - column_options = options.delete(:column_options) || {} - column_options.reverse_merge!(null: false) - type = column_options.delete(:type) || :integer + column_options.reverse_merge!(null: false, index: false) - t1_column, t2_column = [table_1, table_2].map { |t| t.to_s.singularize.foreign_key } + t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize } create_table(join_table_name, options.merge!(id: false)) do |td| - td.send type, t1_column, column_options - td.send type, t2_column, column_options + td.references t1_ref, column_options + td.references t2_ref, column_options yield td if block_given? end end @@ -857,6 +855,7 @@ module ActiveRecord else foreign_key_options = { to_table: reference_name } end + foreign_key_options[:column] ||= "#{ref_name}_id" remove_foreign_key(table_name, **foreign_key_options) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index b31ce0a181..ef1d9f81a9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -154,8 +154,8 @@ module ActiveRecord Arel::Visitors::ToSql.new(self) end - def valid_type?(type) - false + def valid_type?(type) # :nodoc: + !native_database_types[type].nil? end def schema_creation @@ -232,10 +232,10 @@ module ActiveRecord self.class::ADAPTER_NAME end - # Does this adapter support migrations? - def supports_migrations? - false + def supports_migrations? # :nodoc: + true end + deprecate :supports_migrations? def supports_primary_key? # :nodoc: true @@ -439,6 +439,9 @@ module ActiveRecord # This is done under the hood by calling #active?. If the connection # is no longer active, then this method will reconnect to the database. def verify!(*ignored) + if ignored.size > 0 + ActiveSupport::Deprecation.warn("Passing arguments to #verify method of the connection has no effect and has been deprecated. Please remove all arguments from the #verify method call.") + end reconnect! unless active? end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 5f86a11c2d..e3b6327dd8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -89,11 +89,6 @@ module ActiveRecord /mariadb/i.match?(full_version) end - # Returns true, since this connection adapter supports migrations. - def supports_migrations? - true - end - def supports_bulk_alter? #:nodoc: true end @@ -648,10 +643,6 @@ module ActiveRecord self.class.type_cast_config_to_boolean(@config.fetch(:strict, true)) end - def valid_type?(type) - !native_database_types[type].nil? - end - def default_index_type?(index) # :nodoc: index.using == :btree || super end @@ -786,11 +777,11 @@ module ActiveRecord def change_column_sql(table_name, column_name, type, options = {}) column = column_for(table_name, column_name) - unless options_include_default?(options) + unless options.key?(:default) options[:default] = column.default end - unless options.has_key?(:null) + unless options.key?(:null) options[:null] = column.null end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index a61d920a73..afef0da5c7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -489,7 +489,7 @@ module ActiveRecord end execute sql - change_column_default(table_name, column_name, options[:default]) if options_include_default?(options) + change_column_default(table_name, column_name, options[:default]) if options.key?(:default) change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment) end @@ -615,10 +615,6 @@ module ActiveRecord end end - def index_name_length - 63 - end - # Maps logical Rails types to PostgreSQL-specific data types. def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc: sql = \ diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index c89e29ba44..bc04565434 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -215,7 +215,7 @@ module ActiveRecord # @local_tz is initialized as nil to avoid warnings when connect tries to use it @local_tz = nil - @table_alias_length = nil + @max_identifier_length = nil connect add_pg_encoders @@ -281,11 +281,6 @@ module ActiveRecord NATIVE_DATABASE_TYPES end - # Returns true, since this connection adapter supports migrations. - def supports_migrations? - true - end - def set_standard_conforming_strings execute("SET standard_conforming_strings = on", "SCHEMA") end @@ -363,8 +358,9 @@ module ActiveRecord # Returns the configured supported identifier length supported by PostgreSQL def table_alias_length - @table_alias_length ||= query("SHOW max_identifier_length", "SCHEMA")[0][0].to_i + @max_identifier_length ||= select_value("SHOW max_identifier_length", "SCHEMA").to_i end + alias index_name_length table_alias_length # Set the authorized user for this session def session_auth=(user) @@ -376,10 +372,6 @@ module ActiveRecord @use_insert_returning end - def valid_type?(type) - !native_database_types[type].nil? - end - def update_table_definition(table_name, base) #:nodoc: PostgreSQL::Table.new(table_name, base) end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 16ef195bfc..8b627a6d4d 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -117,11 +117,6 @@ module ActiveRecord true end - # Returns true, since this connection adapter supports migrations. - def supports_migrations? #:nodoc: - true - end - def requires_reloading? true end @@ -163,10 +158,6 @@ module ActiveRecord true end - def valid_type?(type) - true - end - # Returns 62. SQLite supports index names up to 64 # characters. The rest is used by Rails internally to perform # temporary rename operations @@ -420,11 +411,10 @@ module ActiveRecord def change_column(table_name, column_name, type, options = {}) #:nodoc: alter_table(table_name) do |definition| - include_default = options_include_default?(options) definition[column_name].instance_eval do self.type = type self.limit = options[:limit] if options.include?(:limit) - self.default = options[:default] if include_default + self.default = options[:default] if options.include?(:default) self.null = options[:null] if options.include?(:null) self.precision = options[:precision] if options.include?(:precision) self.scale = options[:scale] if options.include?(:scale) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 40f6226315..263a8a7da3 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -548,12 +548,10 @@ module ActiveRecord end def call(env) - if connection.supports_migrations? - mtime = ActiveRecord::Migrator.last_migration.mtime.to_i - if @last_check < mtime - ActiveRecord::Migration.check_pending!(connection) - @last_check = mtime - end + mtime = ActiveRecord::Migrator.last_migration.mtime.to_i + if @last_check < mtime + ActiveRecord::Migration.check_pending!(connection) + @last_check = mtime end @app.call(env) end @@ -1098,8 +1096,6 @@ module ActiveRecord end def initialize(direction, migrations, target_version = nil) - raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations? - @direction = direction @target_version = target_version @migrated_versions = nil diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 246d330b76..fabd326649 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -288,8 +288,7 @@ db_namespace = namespace :db do current_config = ActiveRecord::Tasks::DatabaseTasks.current_config ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) - if ActiveRecord::Base.connection.supports_migrations? && - ActiveRecord::SchemaMigration.table_exists? + if ActiveRecord::SchemaMigration.table_exists? File.open(filename, "a") do |f| f.puts ActiveRecord::Base.connection.dump_schema_information f.print "\n" diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 35c670f1a1..f4cdaf3948 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -232,7 +232,7 @@ module ActiveRecord query_builder = build_count_subquery(spawn, column_name, distinct) else # PostgreSQL doesn't like ORDER BY when there are no GROUP BY - relation = unscope(:order) + relation = unscope(:order).distinct!(false) column = aggregate_column(column_name) @@ -282,7 +282,7 @@ module ActiveRecord operation, distinct).as(aggregate_alias) ] - select_values += select_values unless having_clause.empty? + select_values += self.select_values unless having_clause.empty? select_values.concat group_columns.map { |aliaz, field| if field.respond_to?(:as) @@ -292,7 +292,7 @@ module ActiveRecord end } - relation = except(:group) + relation = except(:group).distinct!(false) relation.group_values = group_fields relation.select_values = select_values diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 4548944fe6..5d24f5f5ca 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -147,7 +147,7 @@ module ActiveRecord def last(limit = nil) return find_last(limit) if loaded? || limit_value - result = limit(limit || 1) + result = limit(limit) result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key result = result.reverse_order! @@ -430,140 +430,142 @@ module ActiveRecord reflections.none?(&:collection?) end - private + def find_with_ids(*ids) + raise UnknownPrimaryKey.new(@klass) if primary_key.nil? - def find_with_ids(*ids) - raise UnknownPrimaryKey.new(@klass) if primary_key.nil? + expects_array = ids.first.kind_of?(Array) + return ids.first if expects_array && ids.first.empty? - expects_array = ids.first.kind_of?(Array) - return ids.first if expects_array && ids.first.empty? + ids = ids.flatten.compact.uniq - ids = ids.flatten.compact.uniq - - case ids.size - when 0 - raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" - when 1 - result = find_one(ids.first) - expects_array ? [ result ] : result - else - find_some(ids) - end - rescue ::RangeError - raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID" + case ids.size + when 0 + raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" + when 1 + result = find_one(ids.first) + expects_array ? [ result ] : result + else + find_some(ids) end + rescue ::RangeError + raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID" + end - def find_one(id) - if ActiveRecord::Base === id - raise ArgumentError, <<-MSG.squish - You are passing an instance of ActiveRecord::Base to `find`. - Please pass the id of the object by calling `.id`. - MSG - end - - relation = where(primary_key => id) - record = relation.take - - raise_record_not_found_exception!(id, 0, 1) unless record - - record + def find_one(id) + if ActiveRecord::Base === id + raise ArgumentError, <<-MSG.squish + You are passing an instance of ActiveRecord::Base to `find`. + Please pass the id of the object by calling `.id`. + MSG end - def find_some(ids) - return find_some_ordered(ids) unless order_values.present? + relation = where(primary_key => id) + record = relation.take - result = where(primary_key => ids).to_a + raise_record_not_found_exception!(id, 0, 1) unless record - expected_size = - if limit_value && ids.size > limit_value - limit_value - else - ids.size - end + record + end - # 11 ids with limit 3, offset 9 should give 2 results. - if offset_value && (ids.size - offset_value < expected_size) - expected_size = ids.size - offset_value - end + def find_some(ids) + return find_some_ordered(ids) unless order_values.present? - if result.size == expected_size - result + result = where(primary_key => ids).to_a + + expected_size = + if limit_value && ids.size > limit_value + limit_value else - raise_record_not_found_exception!(ids, result.size, expected_size) + ids.size end + + # 11 ids with limit 3, offset 9 should give 2 results. + if offset_value && (ids.size - offset_value < expected_size) + expected_size = ids.size - offset_value end - def find_some_ordered(ids) - ids = ids.slice(offset_value || 0, limit_value || ids.size) || [] + if result.size == expected_size + result + else + raise_record_not_found_exception!(ids, result.size, expected_size) + end + end - result = except(:limit, :offset).where(primary_key => ids).records + def find_some_ordered(ids) + ids = ids.slice(offset_value || 0, limit_value || ids.size) || [] - if result.size == ids.size - pk_type = @klass.type_for_attribute(primary_key) + result = except(:limit, :offset).where(primary_key => ids).records - records_by_id = result.index_by(&:id) - ids.map { |id| records_by_id.fetch(pk_type.cast(id)) } - else - raise_record_not_found_exception!(ids, result.size, ids.size) - end - end + if result.size == ids.size + pk_type = @klass.type_for_attribute(primary_key) - def find_take - if loaded? - records.first - else - @take ||= limit(1).records.first - end + records_by_id = result.index_by(&:id) + ids.map { |id| records_by_id.fetch(pk_type.cast(id)) } + else + raise_record_not_found_exception!(ids, result.size, ids.size) end + end - def find_take_with_limit(limit) - if loaded? - records.take(limit) - else - limit(limit).to_a - end + def find_take + if loaded? + records.first + else + @take ||= limit(1).records.first end + end - def find_nth(index) - @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first + def find_take_with_limit(limit) + if loaded? + records.take(limit) + else + limit(limit).to_a end + end + + def find_nth(index) + @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first + end - def find_nth_with_limit(index, limit) - if loaded? - records[index, limit] || [] + def find_nth_with_limit(index, limit) + if loaded? + records[index, limit] || [] + else + relation = if order_values.empty? && primary_key + order(arel_attribute(primary_key).asc) else - relation = if order_values.empty? && primary_key - order(arel_attribute(primary_key).asc) - else - self - end + self + end + if limit_value.nil? || index < limit_value relation = relation.offset(offset_index + index) unless index.zero? relation.limit(limit).to_a + else + [] end end + end - def find_nth_from_last(index) - if loaded? - records[-index] + def find_nth_from_last(index) + if loaded? + records[-index] + else + relation = if order_values.empty? && primary_key + order(arel_attribute(primary_key).asc) else - relation = if order_values.empty? && primary_key - order(arel_attribute(primary_key).asc) - else - self - end - - relation.to_a[-index] - # TODO: can be made more performant on large result sets by - # for instance, last(index)[-index] (which would require - # refactoring the last(n) finder method to make test suite pass), - # or by using a combination of reverse_order, limit, and offset, - # e.g., reverse_order.offset(index-1).first + self end - end - def find_last(limit) - limit ? records.last(limit) : records.last + relation.to_a[-index] + # TODO: can be made more performant on large result sets by + # for instance, last(index)[-index] (which would require + # refactoring the last(n) finder method to make test suite pass), + # or by using a combination of reverse_order, limit, and offset, + # e.g., reverse_order.offset(index-1).first end + end + + def find_last(limit) + limit ? records.last(limit) : records.last + end end end diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 9ed70a9c2b..26b1d48e9e 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -41,10 +41,15 @@ module ActiveRecord @column_types = column_types end + # Returns the number of elements in the rows array. def length @rows.length end + # Calls the given block once for each element in row collection, passing + # row as parameter. + # + # Returns an +Enumerator+ if no block is given. def each if block_given? hash_rows.each { |row| yield row } @@ -53,6 +58,7 @@ module ActiveRecord end end + # Returns an array of hashes representing each row record. def to_hash hash_rows end @@ -60,11 +66,12 @@ module ActiveRecord alias :map! :map alias :collect! :map - # Returns true if there are no records. + # Returns true if there are no records, otherwise false. def empty? rows.empty? end + # Returns an array of hashes representing each row record. def to_ary hash_rows end @@ -73,11 +80,15 @@ module ActiveRecord hash_rows[idx] end + # Returns the first record from the rows collection. + # If the rows collection is empty, returns +nil+. def first return nil if @rows.empty? Hash[@columns.zip(@rows.first)] end + # Returns the last record from the rows collection. + # If the rows collection is empty, returns +nil+. def last return nil if @rows.empty? Hash[@columns.zip(@rows.last)] diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 427c0019c6..64bda1539c 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -1,4 +1,3 @@ - module ActiveRecord module Sanitization extend ActiveSupport::Concern @@ -207,9 +206,9 @@ module ActiveRecord end end - # TODO: Deprecate this def quoted_id # :nodoc: self.class.connection.quote(@attributes[self.class.primary_key].value_for_database) end + deprecate :quoted_id end end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 15533f0151..2bbfd01698 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -85,7 +85,7 @@ HEADER end def tables(stream) - sorted_tables = @connection.data_sources.sort - @connection.views + sorted_tables = @connection.tables.sort sorted_tables.each do |table_name| table(table_name, stream) unless ignored?(table_name) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 08417aaa0f..690deee508 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -283,7 +283,7 @@ module ActiveRecord fire_on = Array(options[:on]) assert_valid_transaction_action(fire_on) options[:if] = Array(options[:if]) - options[:if] << "transaction_include_any_action?(#{fire_on})" + options[:if].unshift("transaction_include_any_action?(#{fire_on})") end end diff --git a/activerecord/lib/active_record/type/decimal_without_scale.rb b/activerecord/lib/active_record/type/decimal_without_scale.rb index 7ce33e9cd3..53a5e205da 100644 --- a/activerecord/lib/active_record/type/decimal_without_scale.rb +++ b/activerecord/lib/active_record/type/decimal_without_scale.rb @@ -4,6 +4,10 @@ module ActiveRecord def type :decimal end + + def type_cast_for_schema(value) + value.to_s.inspect + end end end end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 0c9d1dff9d..070fca240f 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -30,6 +30,16 @@ module ActiveRecord assert_nothing_raised { Book.destroy(0) } end + def test_valid_column + @connection.native_database_types.each_key do |type| + assert @connection.valid_type?(type) + end + end + + def test_invalid_column + assert_not @connection.valid_type?(:foobar) + end + def test_tables tables = @connection.tables assert_includes tables, "accounts" diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index ae9ea1c573..a2faf43b0d 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -42,7 +42,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase @connection.update("set @@wait_timeout=1") sleep 2 assert !@connection.active? - + ensure # Repair all fixture connections so other tests won't break. @fixture_connections.each(&:verify!) end @@ -63,6 +63,18 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase assert @connection.active? end + def test_verify_with_args_is_deprecated + assert_deprecated do + @connection.verify!(option: true) + end + assert_deprecated do + @connection.verify!([]) + end + assert_deprecated do + @connection.verify!({}) + end + end + def test_execute_after_disconnect @connection.disconnect! diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb index aab3dcb724..565130c38f 100644 --- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb @@ -17,17 +17,6 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase end end - def test_valid_column - with_example_table do - column = @conn.columns("ex").find { |col| col.name == "id" } - assert @conn.valid_type?(column.type) - end - end - - def test_invalid_column - assert_not @conn.valid_type?(:foobar) - end - def test_columns_for_distinct_zero_orders assert_equal "posts.id", @conn.columns_for_distinct("posts.id", []) diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 3cbd4ca212..c52d9e37cc 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -105,7 +105,7 @@ module ActiveRecord end def test_table_alias_length_logs_name - @connection.instance_variable_set("@table_alias_length", nil) + @connection.instance_variable_set("@max_identifier_length", nil) @connection.table_alias_length assert_equal "SCHEMA", @subscriber.logged[0][1] end @@ -177,7 +177,7 @@ module ActiveRecord assert_not_equal original_connection_pid, new_connection_pid, "umm -- looks like you didn't break the connection, because we're still " \ "successfully querying with the same connection pid." - + ensure # Repair all fixture connections so other tests won't break. @fixture_connections.each(&:verify!) end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 3054f0271f..003e6e62e7 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -21,17 +21,6 @@ module ActiveRecord end end - def test_valid_column - with_example_table do - column = @connection.columns("ex").find { |col| col.name == "id" } - assert @connection.valid_type?(column.type) - end - end - - def test_invalid_column - assert_not @connection.valid_type?(:foobar) - end - def test_primary_key with_example_table do assert_equal "id", @connection.primary_key("ex") diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index 141baffa5b..a1e966b915 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require "ipaddr" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 9750840051..aefbb309e6 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -1,6 +1,5 @@ require "cases/helper" require "bigdecimal" -require "yaml" require "securerandom" class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase @@ -15,31 +14,6 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase assert_equal expected, @conn.type_cast(binary) end - def test_type_cast_symbol - assert_equal "foo", @conn.type_cast(:foo) - end - - def test_type_cast_date - date = Date.today - expected = @conn.quoted_date(date) - assert_equal expected, @conn.type_cast(date) - end - - def test_type_cast_time - time = Time.now - expected = @conn.quoted_date(time) - assert_equal expected, @conn.type_cast(time) - end - - def test_type_cast_numeric - assert_equal 10, @conn.type_cast(10) - assert_equal 2.2, @conn.type_cast(2.2) - end - - def test_type_cast_nil - assert_nil @conn.type_cast(nil) - end - def test_type_cast_true assert_equal "t", @conn.type_cast(true) end @@ -53,31 +27,6 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase assert_equal bd.to_f, @conn.type_cast(bd) end - def test_type_cast_unknown_should_raise_error - obj = Class.new.new - assert_raise(TypeError) { @conn.type_cast(obj) } - end - - def test_type_cast_object_which_responds_to_quoted_id - quoted_id_obj = Class.new { - def quoted_id - "'zomg'" - end - - def id - 10 - end - }.new - assert_equal 10, @conn.type_cast(quoted_id_obj) - - quoted_id_obj = Class.new { - def quoted_id - "'zomg'" - end - }.new - assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) } - end - def test_quoting_binary_strings value = "hello".encode("ascii-8bit") type = ActiveRecord::Type::String.new diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index a6afb7816b..fdd7b0157a 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -49,22 +49,6 @@ module ActiveRecord end end - def test_valid_column - with_example_table do - column = @conn.columns("ex").find { |col| col.name == "id" } - assert @conn.valid_type?(column.type) - end - end - - # sqlite3 databases should be able to support any type and not just the - # ones mentioned in the native_database_types. - # - # Therefore test_invalid column should always return true even if the - # type is not valid. - def test_invalid_column - assert @conn.valid_type?(:foobar) - end - def test_column_types owner = Owner.create!(name: "hello".encode("ascii-8bit")) owner.reload diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 397ac599b9..5b608d8e83 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -1,146 +1,143 @@ require "cases/helper" -if ActiveRecord::Base.connection.supports_migrations? +class ActiveRecordSchemaTest < ActiveRecord::TestCase + self.use_transactional_tests = false + + setup do + @original_verbose = ActiveRecord::Migration.verbose + ActiveRecord::Migration.verbose = false + @connection = ActiveRecord::Base.connection + ActiveRecord::SchemaMigration.drop_table + end - class ActiveRecordSchemaTest < ActiveRecord::TestCase - self.use_transactional_tests = false + teardown do + @connection.drop_table :fruits rescue nil + @connection.drop_table :nep_fruits rescue nil + @connection.drop_table :nep_schema_migrations rescue nil + @connection.drop_table :has_timestamps rescue nil + @connection.drop_table :multiple_indexes rescue nil + ActiveRecord::SchemaMigration.delete_all rescue nil + ActiveRecord::Migration.verbose = @original_verbose + end - setup do - @original_verbose = ActiveRecord::Migration.verbose - ActiveRecord::Migration.verbose = false - @connection = ActiveRecord::Base.connection - ActiveRecord::SchemaMigration.drop_table - end + def test_has_primary_key + old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type + ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore + assert_equal "version", ActiveRecord::SchemaMigration.primary_key - teardown do - @connection.drop_table :fruits rescue nil - @connection.drop_table :nep_fruits rescue nil - @connection.drop_table :nep_schema_migrations rescue nil - @connection.drop_table :has_timestamps rescue nil - @connection.drop_table :multiple_indexes rescue nil - ActiveRecord::SchemaMigration.delete_all rescue nil - ActiveRecord::Migration.verbose = @original_verbose + ActiveRecord::SchemaMigration.create_table + assert_difference "ActiveRecord::SchemaMigration.count", 1 do + ActiveRecord::SchemaMigration.create version: 12 end + ensure + ActiveRecord::SchemaMigration.drop_table + ActiveRecord::Base.primary_key_prefix_type = old_primary_key_prefix_type + end - def test_has_primary_key - old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type - ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore - assert_equal "version", ActiveRecord::SchemaMigration.primary_key - - ActiveRecord::SchemaMigration.create_table - assert_difference "ActiveRecord::SchemaMigration.count", 1 do - ActiveRecord::SchemaMigration.create version: 12 + def test_schema_define + ActiveRecord::Schema.define(version: 7) do + create_table :fruits do |t| + t.column :color, :string + t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle + t.column :texture, :string + t.column :flavor, :string end - ensure - ActiveRecord::SchemaMigration.drop_table - ActiveRecord::Base.primary_key_prefix_type = old_primary_key_prefix_type end - def test_schema_define - ActiveRecord::Schema.define(version: 7) do - create_table :fruits do |t| - t.column :color, :string - t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle - t.column :texture, :string - t.column :flavor, :string - end - end - - assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } - assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" } - assert_equal 7, ActiveRecord::Migrator::current_version - end + assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } + assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" } + assert_equal 7, ActiveRecord::Migrator::current_version + end - def test_schema_define_w_table_name_prefix - table_name = ActiveRecord::SchemaMigration.table_name - old_table_name_prefix = ActiveRecord::Base.table_name_prefix - ActiveRecord::Base.table_name_prefix = "nep_" - ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}" - ActiveRecord::Schema.define(version: 7) do - create_table :fruits do |t| - t.column :color, :string - t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle - t.column :texture, :string - t.column :flavor, :string - end + def test_schema_define_w_table_name_prefix + table_name = ActiveRecord::SchemaMigration.table_name + old_table_name_prefix = ActiveRecord::Base.table_name_prefix + ActiveRecord::Base.table_name_prefix = "nep_" + ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}" + ActiveRecord::Schema.define(version: 7) do + create_table :fruits do |t| + t.column :color, :string + t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle + t.column :texture, :string + t.column :flavor, :string end - assert_equal 7, ActiveRecord::Migrator::current_version - ensure - ActiveRecord::Base.table_name_prefix = old_table_name_prefix - ActiveRecord::SchemaMigration.table_name = table_name end + assert_equal 7, ActiveRecord::Migrator::current_version + ensure + ActiveRecord::Base.table_name_prefix = old_table_name_prefix + ActiveRecord::SchemaMigration.table_name = table_name + end - def test_schema_raises_an_error_for_invalid_column_type - assert_raise NoMethodError do - ActiveRecord::Schema.define(version: 8) do - create_table :vegetables do |t| - t.unknown :color - end + def test_schema_raises_an_error_for_invalid_column_type + assert_raise NoMethodError do + ActiveRecord::Schema.define(version: 8) do + create_table :vegetables do |t| + t.unknown :color end end end + end - def test_schema_subclass - Class.new(ActiveRecord::Schema).define(version: 9) do - create_table :fruits - end - assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } + def test_schema_subclass + Class.new(ActiveRecord::Schema).define(version: 9) do + create_table :fruits end + assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } + end - def test_normalize_version - assert_equal "118", ActiveRecord::SchemaMigration.normalize_migration_number("0000118") - assert_equal "002", ActiveRecord::SchemaMigration.normalize_migration_number("2") - assert_equal "017", ActiveRecord::SchemaMigration.normalize_migration_number("0017") - assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947") - end + def test_normalize_version + assert_equal "118", ActiveRecord::SchemaMigration.normalize_migration_number("0000118") + assert_equal "002", ActiveRecord::SchemaMigration.normalize_migration_number("2") + assert_equal "017", ActiveRecord::SchemaMigration.normalize_migration_number("0017") + assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947") + end - def test_schema_load_with_multiple_indexes_for_column_of_different_names - ActiveRecord::Schema.define do - create_table :multiple_indexes do |t| - t.string "foo" - t.index ["foo"], name: "multiple_indexes_foo_1" - t.index ["foo"], name: "multiple_indexes_foo_2" - end + def test_schema_load_with_multiple_indexes_for_column_of_different_names + ActiveRecord::Schema.define do + create_table :multiple_indexes do |t| + t.string "foo" + t.index ["foo"], name: "multiple_indexes_foo_1" + t.index ["foo"], name: "multiple_indexes_foo_2" end + end - indexes = @connection.indexes("multiple_indexes") + indexes = @connection.indexes("multiple_indexes") - assert_equal 2, indexes.length - assert_equal ["multiple_indexes_foo_1", "multiple_indexes_foo_2"], indexes.collect(&:name).sort - end + assert_equal 2, indexes.length + assert_equal ["multiple_indexes_foo_1", "multiple_indexes_foo_2"], indexes.collect(&:name).sort + end - def test_timestamps_without_null_set_null_to_false_on_create_table - ActiveRecord::Schema.define do - create_table :has_timestamps do |t| - t.timestamps - end + def test_timestamps_without_null_set_null_to_false_on_create_table + ActiveRecord::Schema.define do + create_table :has_timestamps do |t| + t.timestamps end - - assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null - assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null end - def test_timestamps_without_null_set_null_to_false_on_change_table - ActiveRecord::Schema.define do - create_table :has_timestamps + assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null + end - change_table :has_timestamps do |t| - t.timestamps default: Time.now - end - end + def test_timestamps_without_null_set_null_to_false_on_change_table + ActiveRecord::Schema.define do + create_table :has_timestamps - assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null - assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null + change_table :has_timestamps do |t| + t.timestamps default: Time.now + end end - def test_timestamps_without_null_set_null_to_false_on_add_timestamps - ActiveRecord::Schema.define do - create_table :has_timestamps - add_timestamps :has_timestamps, default: Time.now - end + assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null + end - assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null - assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null + def test_timestamps_without_null_set_null_to_false_on_add_timestamps + ActiveRecord::Schema.define do + create_table :has_timestamps + add_timestamps :has_timestamps, default: Time.now end + + assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null end end diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb index 5d1c1c4b9b..16eff15026 100644 --- a/activerecord/test/cases/associations/eager_singularization_test.rb +++ b/activerecord/test/cases/associations/eager_singularization_test.rb @@ -1,147 +1,146 @@ require "cases/helper" -if ActiveRecord::Base.connection.supports_migrations? - class EagerSingularizationTest < ActiveRecord::TestCase - class Virus < ActiveRecord::Base - belongs_to :octopus - end - - class Octopus < ActiveRecord::Base - has_one :virus - end - - class Pass < ActiveRecord::Base - belongs_to :bus - end - - class Bus < ActiveRecord::Base - has_many :passes - end - - class Mess < ActiveRecord::Base - has_and_belongs_to_many :crises - end - - class Crisis < ActiveRecord::Base - has_and_belongs_to_many :messes - has_many :analyses, dependent: :destroy - has_many :successes, through: :analyses - has_many :dresses, dependent: :destroy - has_many :compresses, through: :dresses - end - - class Analysis < ActiveRecord::Base - belongs_to :crisis - belongs_to :success - end - - class Success < ActiveRecord::Base - has_many :analyses, dependent: :destroy - has_many :crises, through: :analyses - end - - class Dress < ActiveRecord::Base - belongs_to :crisis - has_many :compresses - end - - class Compress < ActiveRecord::Base - belongs_to :dress - end - - def setup - connection.create_table :viri do |t| - t.column :octopus_id, :integer - t.column :species, :string - end - connection.create_table :octopi do |t| - t.column :species, :string - end - connection.create_table :passes do |t| - t.column :bus_id, :integer - t.column :rides, :integer - end - connection.create_table :buses do |t| - t.column :name, :string - end - connection.create_table :crises_messes, id: false do |t| - t.column :crisis_id, :integer - t.column :mess_id, :integer - end - connection.create_table :messes do |t| - t.column :name, :string - end - connection.create_table :crises do |t| - t.column :name, :string - end - connection.create_table :successes do |t| - t.column :name, :string - end - connection.create_table :analyses do |t| - t.column :crisis_id, :integer - t.column :success_id, :integer - end - connection.create_table :dresses do |t| - t.column :crisis_id, :integer - end - connection.create_table :compresses do |t| - t.column :dress_id, :integer - end - end - - teardown do - connection.drop_table :viri - connection.drop_table :octopi - connection.drop_table :passes - connection.drop_table :buses - connection.drop_table :crises_messes - connection.drop_table :messes - connection.drop_table :crises - connection.drop_table :successes - connection.drop_table :analyses - connection.drop_table :dresses - connection.drop_table :compresses - end +class EagerSingularizationTest < ActiveRecord::TestCase + class Virus < ActiveRecord::Base + belongs_to :octopus + end - def connection - ActiveRecord::Base.connection + class Octopus < ActiveRecord::Base + has_one :virus + end + + class Pass < ActiveRecord::Base + belongs_to :bus + end + + class Bus < ActiveRecord::Base + has_many :passes + end + + class Mess < ActiveRecord::Base + has_and_belongs_to_many :crises + end + + class Crisis < ActiveRecord::Base + has_and_belongs_to_many :messes + has_many :analyses, dependent: :destroy + has_many :successes, through: :analyses + has_many :dresses, dependent: :destroy + has_many :compresses, through: :dresses + end + + class Analysis < ActiveRecord::Base + belongs_to :crisis + belongs_to :success + end + + class Success < ActiveRecord::Base + has_many :analyses, dependent: :destroy + has_many :crises, through: :analyses + end + + class Dress < ActiveRecord::Base + belongs_to :crisis + has_many :compresses + end + + class Compress < ActiveRecord::Base + belongs_to :dress + end + + def setup + connection.create_table :viri do |t| + t.column :octopus_id, :integer + t.column :species, :string end + connection.create_table :octopi do |t| + t.column :species, :string + end + connection.create_table :passes do |t| + t.column :bus_id, :integer + t.column :rides, :integer + end + connection.create_table :buses do |t| + t.column :name, :string + end + connection.create_table :crises_messes, id: false do |t| + t.column :crisis_id, :integer + t.column :mess_id, :integer + end + connection.create_table :messes do |t| + t.column :name, :string + end + connection.create_table :crises do |t| + t.column :name, :string + end + connection.create_table :successes do |t| + t.column :name, :string + end + connection.create_table :analyses do |t| + t.column :crisis_id, :integer + t.column :success_id, :integer + end + connection.create_table :dresses do |t| + t.column :crisis_id, :integer + end + connection.create_table :compresses do |t| + t.column :dress_id, :integer + end + end - def test_eager_no_extra_singularization_belongs_to - assert_nothing_raised do - Virus.all.merge!(includes: :octopus).to_a - end + teardown do + connection.drop_table :viri + connection.drop_table :octopi + connection.drop_table :passes + connection.drop_table :buses + connection.drop_table :crises_messes + connection.drop_table :messes + connection.drop_table :crises + connection.drop_table :successes + connection.drop_table :analyses + connection.drop_table :dresses + connection.drop_table :compresses + end + + def test_eager_no_extra_singularization_belongs_to + assert_nothing_raised do + Virus.all.merge!(includes: :octopus).to_a end + end - def test_eager_no_extra_singularization_has_one - assert_nothing_raised do - Octopus.all.merge!(includes: :virus).to_a - end + def test_eager_no_extra_singularization_has_one + assert_nothing_raised do + Octopus.all.merge!(includes: :virus).to_a end + end - def test_eager_no_extra_singularization_has_many - assert_nothing_raised do - Bus.all.merge!(includes: :passes).to_a - end + def test_eager_no_extra_singularization_has_many + assert_nothing_raised do + Bus.all.merge!(includes: :passes).to_a end + end - def test_eager_no_extra_singularization_has_and_belongs_to_many - assert_nothing_raised do - Crisis.all.merge!(includes: :messes).to_a - Mess.all.merge!(includes: :crises).to_a - end + def test_eager_no_extra_singularization_has_and_belongs_to_many + assert_nothing_raised do + Crisis.all.merge!(includes: :messes).to_a + Mess.all.merge!(includes: :crises).to_a end + end - def test_eager_no_extra_singularization_has_many_through_belongs_to - assert_nothing_raised do - Crisis.all.merge!(includes: :successes).to_a - end + def test_eager_no_extra_singularization_has_many_through_belongs_to + assert_nothing_raised do + Crisis.all.merge!(includes: :successes).to_a end + end - def test_eager_no_extra_singularization_has_many_through_has_many - assert_nothing_raised do - Crisis.all.merge!(includes: :compresses).to_a - end + def test_eager_no_extra_singularization_has_many_through_has_many + assert_nothing_raised do + Crisis.all.merge!(includes: :compresses).to_a end end + + private + def connection + ActiveRecord::Base.connection + end end diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb index f8b686721e..45e1803858 100644 --- a/activerecord/test/cases/associations/required_test.rb +++ b/activerecord/test/cases/associations/required_test.rb @@ -22,14 +22,21 @@ class RequiredAssociationsTest < ActiveRecord::TestCase @connection.drop_table "children", if_exists: true end - test "belongs_to associations are not required by default" do - model = subclass_of(Child) do - belongs_to :parent, inverse_of: false, - class_name: "RequiredAssociationsTest::Parent" - end + test "belongs_to associations can be optional by default" do + begin + original_value = ActiveRecord::Base.belongs_to_required_by_default + ActiveRecord::Base.belongs_to_required_by_default = false + + model = subclass_of(Child) do + belongs_to :parent, inverse_of: false, + class_name: "RequiredAssociationsTest::Parent" + end - assert model.new.save - assert model.new(parent: Parent.new).save + assert model.new.save + assert model.new(parent: Parent.new).save + ensure + ActiveRecord::Base.belongs_to_required_by_default = original_value + end end test "required belongs_to associations have presence validated" do @@ -46,6 +53,27 @@ class RequiredAssociationsTest < ActiveRecord::TestCase assert record.save end + test "belongs_to associations can be required by default" do + begin + original_value = ActiveRecord::Base.belongs_to_required_by_default + ActiveRecord::Base.belongs_to_required_by_default = true + + model = subclass_of(Child) do + belongs_to :parent, inverse_of: false, + class_name: "RequiredAssociationsTest::Parent" + end + + record = model.new + assert_not record.save + assert_equal ["Parent must exist"], record.errors.full_messages + + record.parent = Parent.new + assert record.save + ensure + ActiveRecord::Base.belongs_to_required_by_default = original_value + end + end + test "has_one associations are not required by default" do model = subclass_of(Parent) do has_one :child, inverse_of: false, diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 1813534b62..3214d778d4 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -227,6 +227,20 @@ class CalculationsTest < ActiveRecord::TestCase assert_match "credit_limit, firm_name", e.message end + def test_apply_distinct_in_count + queries = assert_sql do + Account.distinct.count + Account.group(:firm_id).distinct.count + end + + queries.each do |query| + # `table_alias_length` in `column_alias_for` would execute + # "SHOW max_identifier_length" statement in PostgreSQL adapter. + next if query == "SHOW max_identifier_length" + assert_match %r{\ASELECT(?! DISTINCT) COUNT\(DISTINCT\b}, query + end + end + def test_should_group_by_summed_field_having_condition c = Account.group(:firm_id).having("sum(credit_limit) > 50").sum(:credit_limit) assert_nil c[1] @@ -235,7 +249,8 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_summed_field_having_condition_from_select - c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("MIN(credit_limit) > 50").sum(:credit_limit) + skip unless current_adapter?(:Mysql2Adapter, :SQLite3Adapter) + c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("min_credit_limit > 50").sum(:credit_limit) assert_nil c[1] assert_equal 60, c[2] assert_equal 53, c[9] diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index afd0ac2dd4..7e88c9cf7a 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -307,14 +307,17 @@ module ActiveRecord end end - def test_automatic_reconnect= + def test_automatic_reconnect_restores_after_disconnect pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec assert pool.automatic_reconnect assert pool.connection pool.disconnect! assert pool.connection + end + def test_automatic_reconnect_can_be_disabled + pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec pool.disconnect! pool.automatic_reconnect = false diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index 3bc08f80ec..ad7da9de70 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -52,7 +52,7 @@ class DateTimeTest < ActiveRecord::TestCase end def test_assign_in_local_timezone - now = DateTime.now + now = DateTime.civil(2017, 3, 1, 12, 0, 0) with_timezone_config default: :local do task = Task.new starting: now assert_equal now, task.starting diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index a43c06cd6e..c13a962e3e 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -566,19 +566,17 @@ class DirtyTest < ActiveRecord::TestCase travel_back end - if ActiveRecord::Base.connection.supports_migrations? - class Testings < ActiveRecord::Base; end - def test_field_named_field - ActiveRecord::Base.connection.create_table :testings do |t| - t.string :field - end - assert_nothing_raised do - Testings.new.attributes - end - ensure - ActiveRecord::Base.connection.drop_table :testings rescue nil - ActiveRecord::Base.clear_cache! + class Testings < ActiveRecord::Base; end + def test_field_named_field + ActiveRecord::Base.connection.create_table :testings do |t| + t.string :field end + assert_nothing_raised do + Testings.new.attributes + end + ensure + ActiveRecord::Base.connection.drop_table :testings rescue nil + ActiveRecord::Base.clear_cache! end def test_datetime_attribute_can_be_updated_with_fractional_seconds diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index deec669935..89d8a8bdca 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -497,7 +497,7 @@ class FinderTest < ActiveRecord::TestCase assert_nil Topic.offset(5).second_to_last #test with limit - # assert_nil Topic.limit(1).second # TODO: currently failing + assert_nil Topic.limit(1).second assert_nil Topic.limit(1).second_to_last end @@ -526,9 +526,9 @@ class FinderTest < ActiveRecord::TestCase assert_nil Topic.offset(5).third_to_last # test with limit - # assert_nil Topic.limit(1).third # TODO: currently failing + assert_nil Topic.limit(1).third assert_nil Topic.limit(1).third_to_last - # assert_nil Topic.limit(2).third # TODO: currently failing + assert_nil Topic.limit(2).third assert_nil Topic.limit(2).third_to_last end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index afe761cb55..51133e9495 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -104,64 +104,62 @@ class FixturesTest < ActiveRecord::TestCase assert_nil(second_row["author_email_address"]) end - if ActiveRecord::Base.connection.supports_migrations? - def test_inserts_with_pre_and_suffix - # Reset cache to make finds on the new table work - ActiveRecord::FixtureSet.reset_cache - - ActiveRecord::Base.connection.create_table :prefix_other_topics_suffix do |t| - t.column :title, :string - t.column :author_name, :string - t.column :author_email_address, :string - t.column :written_on, :datetime - t.column :bonus_time, :time - t.column :last_read, :date - t.column :content, :string - t.column :approved, :boolean, default: true - t.column :replies_count, :integer, default: 0 - t.column :parent_id, :integer - t.column :type, :string, limit: 50 - end + def test_inserts_with_pre_and_suffix + # Reset cache to make finds on the new table work + ActiveRecord::FixtureSet.reset_cache - # Store existing prefix/suffix - old_prefix = ActiveRecord::Base.table_name_prefix - old_suffix = ActiveRecord::Base.table_name_suffix + ActiveRecord::Base.connection.create_table :prefix_other_topics_suffix do |t| + t.column :title, :string + t.column :author_name, :string + t.column :author_email_address, :string + t.column :written_on, :datetime + t.column :bonus_time, :time + t.column :last_read, :date + t.column :content, :string + t.column :approved, :boolean, default: true + t.column :replies_count, :integer, default: 0 + t.column :parent_id, :integer + t.column :type, :string, limit: 50 + end - # Set a prefix/suffix we can test against - ActiveRecord::Base.table_name_prefix = "prefix_" - ActiveRecord::Base.table_name_suffix = "_suffix" + # Store existing prefix/suffix + old_prefix = ActiveRecord::Base.table_name_prefix + old_suffix = ActiveRecord::Base.table_name_suffix - other_topic_klass = Class.new(ActiveRecord::Base) do - def self.name - "OtherTopic" - end + # Set a prefix/suffix we can test against + ActiveRecord::Base.table_name_prefix = "prefix_" + ActiveRecord::Base.table_name_suffix = "_suffix" + + other_topic_klass = Class.new(ActiveRecord::Base) do + def self.name + "OtherTopic" end + end - topics = [create_fixtures("other_topics")].flatten.first + topics = [create_fixtures("other_topics")].flatten.first - # This checks for a caching problem which causes a bug in the fixtures - # class-level configuration helper. - assert_not_nil topics, "Fixture data inserted, but fixture objects not returned from create" + # This checks for a caching problem which causes a bug in the fixtures + # class-level configuration helper. + assert_not_nil topics, "Fixture data inserted, but fixture objects not returned from create" - first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'David'") - assert_not_nil first_row, "The prefix_other_topics_suffix table appears to be empty despite create_fixtures: the row with author_name = 'David' was not found" - assert_equal("The First Topic", first_row["title"]) + first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'David'") + assert_not_nil first_row, "The prefix_other_topics_suffix table appears to be empty despite create_fixtures: the row with author_name = 'David' was not found" + assert_equal("The First Topic", first_row["title"]) - second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'Mary'") - assert_nil(second_row["author_email_address"]) + second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'Mary'") + assert_nil(second_row["author_email_address"]) - assert_equal :prefix_other_topics_suffix, topics.table_name.to_sym - # This assertion should preferably be the last in the list, because calling - # other_topic_klass.table_name sets a class-level instance variable - assert_equal :prefix_other_topics_suffix, other_topic_klass.table_name.to_sym + assert_equal :prefix_other_topics_suffix, topics.table_name.to_sym + # This assertion should preferably be the last in the list, because calling + # other_topic_klass.table_name sets a class-level instance variable + assert_equal :prefix_other_topics_suffix, other_topic_klass.table_name.to_sym - ensure - # Restore prefix/suffix to its previous values - ActiveRecord::Base.table_name_prefix = old_prefix - ActiveRecord::Base.table_name_suffix = old_suffix + ensure + # Restore prefix/suffix to its previous values + ActiveRecord::Base.table_name_prefix = old_prefix + ActiveRecord::Base.table_name_suffix = old_suffix - ActiveRecord::Base.connection.drop_table :prefix_other_topics_suffix rescue nil - end + ActiveRecord::Base.connection.drop_table :prefix_other_topics_suffix rescue nil end def test_insert_with_datetime diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb index 55c06da411..2329888345 100644 --- a/activerecord/test/cases/migration/columns_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -225,6 +225,16 @@ module ActiveRecord assert_nil TestModel.new.contributor end + def test_change_column_to_drop_default_with_null_false + add_column "test_models", "contributor", :boolean, default: true, null: false + assert TestModel.new.contributor? + + change_column "test_models", "contributor", :boolean, default: nil, null: false + TestModel.reset_column_information + assert_not TestModel.new.contributor? + assert_nil TestModel.new.contributor + end + def test_change_column_with_new_default add_column "test_models", "administrator", :boolean, default: true assert TestModel.new.administrator? diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb index 26b1bb4419..c4896f3d6e 100644 --- a/activerecord/test/cases/migration/create_join_table_test.rb +++ b/activerecord/test/cases/migration/create_join_table_test.rb @@ -12,7 +12,7 @@ module ActiveRecord teardown do %w(artists_musics musics_videos catalog).each do |table_name| - connection.drop_table table_name if connection.table_exists?(table_name) + connection.drop_table table_name, if_exists: true end end @@ -78,6 +78,17 @@ module ActiveRecord assert_equal [%w(artist_id music_id)], connection.indexes(:artists_musics).map(&:columns) end + def test_create_join_table_respects_reference_key_type + connection.create_join_table :artists, :musics do |t| + t.references :video + end + + artist_id, music_id, video_id = connection.columns(:artists_musics).sort_by(&:name) + + assert_equal video_id.sql_type, artist_id.sql_type + assert_equal video_id.sql_type, music_id.sql_type + end + def test_drop_join_table connection.create_join_table :artists, :musics connection.drop_join_table :artists, :musics diff --git a/activerecord/test/cases/migration/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb index 61f5a061b0..6970fdcc87 100644 --- a/activerecord/test/cases/migration/pending_migrations_test.rb +++ b/activerecord/test/cases/migration/pending_migrations_test.rb @@ -21,8 +21,6 @@ module ActiveRecord end def test_errors_if_pending - @connection.expect :supports_migrations?, true - ActiveRecord::Migrator.stub :needs_migration?, true do assert_raise ActiveRecord::PendingMigrationError do @pending.call(nil) @@ -31,22 +29,12 @@ module ActiveRecord end def test_checks_if_supported - @connection.expect :supports_migrations?, true @app.expect :call, nil, [:foo] ActiveRecord::Migrator.stub :needs_migration?, false do @pending.call(:foo) end end - - def test_doesnt_check_if_unsupported - @connection.expect :supports_migrations?, false - @app.expect :call, nil, [:foo] - - ActiveRecord::Migrator.stub :needs_migration?, true do - @pending.call(:foo) - end - end end end end diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb index 9418995ea0..f1ddac1ee2 100644 --- a/activerecord/test/cases/migration/references_foreign_key_test.rb +++ b/activerecord/test/cases/migration/references_foreign_key_test.rb @@ -203,6 +203,22 @@ if ActiveRecord::Base.connection.supports_foreign_keys? assert_equal([["testings", "testing_parents", "parent1_id"], ["testings", "testing_parents", "parent2_id"]], fk_definitions) end + + test "multiple foreign keys can be removed to the selected one" do + @connection.create_table :testings do |t| + t.references :parent1, foreign_key: { to_table: :testing_parents } + t.references :parent2, foreign_key: { to_table: :testing_parents } + end + + assert_difference "@connection.foreign_keys('testings').size", -1 do + @connection.remove_reference :testings, :parent1, foreign_key: { to_table: :testing_parents } + end + + fks = @connection.foreign_keys("testings").sort_by(&:column) + + fk_definitions = fks.map { |fk| [fk.from_table, fk.to_table, fk.column] } + assert_equal([["testings", "testing_parents", "parent2_id"]], fk_definitions) + end end end end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index de16ecf442..fabb1662a3 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1142,4 +1142,8 @@ class CopyMigrationsTest < ActiveRecord::TestCase def test_deprecate_migration_keys assert_deprecated { ActiveRecord::Base.connection.migration_keys } end + + def test_deprecate_supports_migrations + assert_deprecated { ActiveRecord::Base.connection.supports_migrations? } + end end diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 5ff5e3c735..f260d043e4 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -82,7 +82,7 @@ module ActiveRecord end def test_quote_with_quoted_id - assert_equal 1, @quoter.quote(Struct.new(:quoted_id).new(1)) + assert_deprecated { assert_equal 1, @quoter.quote(Struct.new(:quoted_id).new(1)) } end def test_quote_nil @@ -150,6 +150,62 @@ module ActiveRecord end end + class TypeCastingTest < ActiveRecord::TestCase + def setup + @conn = ActiveRecord::Base.connection + end + + def test_type_cast_symbol + assert_equal "foo", @conn.type_cast(:foo) + end + + def test_type_cast_date + date = Date.today + expected = @conn.quoted_date(date) + assert_equal expected, @conn.type_cast(date) + end + + def test_type_cast_time + time = Time.now + expected = @conn.quoted_date(time) + assert_equal expected, @conn.type_cast(time) + end + + def test_type_cast_numeric + assert_equal 10, @conn.type_cast(10) + assert_equal 2.2, @conn.type_cast(2.2) + end + + def test_type_cast_nil + assert_nil @conn.type_cast(nil) + end + + def test_type_cast_unknown_should_raise_error + obj = Class.new.new + assert_raise(TypeError) { @conn.type_cast(obj) } + end + + def test_type_cast_object_which_responds_to_quoted_id + quoted_id_obj = Class.new { + def quoted_id + "'zomg'" + end + + def id + 10 + end + }.new + assert_equal 10, @conn.type_cast(quoted_id_obj) + + quoted_id_obj = Class.new { + def quoted_id + "'zomg'" + end + }.new + assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) } + end + end + class QuoteBooleanTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection @@ -165,5 +221,32 @@ module ActiveRecord assert_predicate @connection.type_cast(false), :frozen? end end + + if subsecond_precision_supported? + class QuoteARBaseTest < ActiveRecord::TestCase + class DatetimePrimaryKey < ActiveRecord::Base + end + + def setup + @time = ::Time.utc(2017, 2, 14, 12, 34, 56, 789999) + @connection = ActiveRecord::Base.connection + @connection.create_table :datetime_primary_keys, id: :datetime, precision: 3, force: true + end + + def teardown + @connection.drop_table :datetime_primary_keys, if_exists: true + end + + def test_quote_ar_object + value = DatetimePrimaryKey.new(id: @time) + assert_equal "'2017-02-14 12:34:56.789000'", @connection.quote(value) + end + + def test_type_cast_ar_object + value = DatetimePrimaryKey.new(id: @time) + assert_equal "2017-02-14 12:34:56.789000", @connection.type_cast(value) + end + end + end end end diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 23bcb0af1e..72f09186e2 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -152,11 +152,15 @@ class SanitizeTest < ActiveRecord::TestCase end def test_bind_record - o = Struct.new(:quoted_id).new(1) - assert_equal "1", bind("?", o) + o = Class.new { + def quoted_id + 1 + end + }.new + assert_deprecated { assert_equal "1", bind("?", o) } os = [o] * 3 - assert_equal "1,1,1", bind("?", os) + assert_deprecated { assert_equal "1,1,1", bind("?", os) } end def test_named_bind_with_postgresql_type_casts diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 9584318e86..fccba4738f 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -422,11 +422,12 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase setup do @connection = ActiveRecord::Base.connection - @connection.create_table :defaults, force: true do |t| + @connection.create_table :dump_defaults, force: true do |t| t.string :string_with_default, default: "Hello!" t.date :date_with_default, default: "2014-06-05" t.datetime :datetime_with_default, default: "2014-06-05 07:17:04" t.time :time_with_default, default: "07:17:04" + t.decimal :decimal_with_default, default: "1234567890.0123456789", precision: 20, scale: 10 end if current_adapter?(:PostgreSQLAdapter) @@ -438,17 +439,17 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase end teardown do - return unless @connection - @connection.drop_table "defaults", if_exists: true + @connection.drop_table "dump_defaults", if_exists: true end def test_schema_dump_defaults_with_universally_supported_types - output = dump_table_schema("defaults") + output = dump_table_schema("dump_defaults") assert_match %r{t\.string\s+"string_with_default",.*?default: "Hello!"}, output - assert_match %r{t\.date\s+"date_with_default",\s+default: '2014-06-05'}, output - assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: '2014-06-05 07:17:04'}, output - assert_match %r{t\.time\s+"time_with_default",\s+default: '2000-01-01 07:17:04'}, output + assert_match %r{t\.date\s+"date_with_default",\s+default: "2014-06-05"}, output + assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: "2014-06-05 07:17:04"}, output + assert_match %r{t\.time\s+"time_with_default",\s+default: "2000-01-01 07:17:04"}, output + assert_match %r{t\.decimal\s+"decimal_with_default",\s+precision: 20,\s+scale: 10,\s+default: "1234567890.0123456789"}, output end def test_schema_dump_with_float_column_infinity_default diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 995ff4dfc5..d261fd5321 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -161,7 +161,7 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_first_and_last_should_allow_integers_for_limit - assert_equal Topic.base.first(2), Topic.base.to_a.first(2) + assert_equal Topic.base.first(2), Topic.base.order("id").to_a.first(2) assert_equal Topic.base.last(2), Topic.base.order("id").to_a.last(2) end diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 391bbe8877..eaa4dd09a9 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -551,3 +551,43 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase assert_equal [:rollback], @topic.history end end + +class CallbacksOnActionAndConditionTest < ActiveRecord::TestCase + self.use_transactional_tests = false + + class TopicWithCallbacksOnActionAndCondition < ActiveRecord::Base + self.table_name = :topics + + after_commit(on: [:create, :update], if: :run_callback?) { |record| record.history << :create_or_update } + + def clear_history + @history = [] + end + + def history + @history ||= [] + end + + def run_callback? + self.history << :run_callback? + true + end + + attr_accessor :save_before_commit_history, :update_title + end + + def test_callback_on_action_with_condition + topic = TopicWithCallbacksOnActionAndCondition.new + topic.save + assert_equal [:run_callback?, :create_or_update], topic.history + + topic.clear_history + topic.approved = true + topic.save + assert_equal [:run_callback?, :create_or_update], topic.history + + topic.clear_history + topic.destroy + assert_equal [], topic.history + end +end diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb index bc5af36a28..1a609e13c3 100644 --- a/activerecord/test/support/connection.rb +++ b/activerecord/test/support/connection.rb @@ -10,7 +10,10 @@ module ARTest end def self.connection_config - config["connections"][connection_name] + config.fetch("connections").fetch(connection_name) do + puts "Connection #{connection_name.inspect} not found. Available connections: #{config['connections'].keys.join(', ')}" + exit 1 + end end def self.connect diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 0f43b1256f..2999820c42 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,10 +1,54 @@ +* Update `DateTime#change` to support `:usec` and `:nsec` options. + + Adding support for these options now allows us to update the `DateTime#end_of` + methods to match the equivalent `Time#end_of` methods, e.g: + + datetime = DateTime.now.end_of_day + datetime.nsec == 999999999 # => true + + Fixes #21424. + + *Dan Moore*, *Andrew White* + +* Add `ActiveSupport::Duration#before` and `#after` as aliases for `#until` and `#since` + + These read more like English and require less mental gymnastics to read and write. + + Before: + + 2.weeks.since(customer_start_date) + 5.days.until(today) + + After: + + 2.weeks.after(customer_start_date) + 5.days.before(today) + + *Nick Johnstone* + +* Soft-deprecated the top-level `HashWithIndifferentAccess` constant. + `ActiveSupport::HashWithIndifferentAccess` should be used instead. + + *Robin Dupret* (#28157) + +* In Core Extensions, make `MarshalWithAutoloading#load` pass through the second, optional + argument for `Marshal#load( source [, proc] )`. This way we don't have to do + `Marshal.method(:load).super_method.call(source, proc)` just to be able to pass a proc. + + *Jeff Latz* + +* `ActiveSupport::Gzip.decompress` now checks checksum and length in footer. + + *Dylan Thacker-Smith* + + ## Rails 5.1.0.beta1 (February 23, 2017) ## * Cache `ActiveSupport::TimeWithZone#to_datetime` before freezing. *Adam Rice* -* Deprecate `.halt_callback_chains_on_return_false`. +* Deprecate `ActiveSupport.halt_callback_chains_on_return_false`. *Rafael Mendonça França* @@ -67,7 +111,7 @@ duration's numeric value isn't used in calculations, only parts are used. Methods on `Numeric` like `2.days` now use these predefined durations - to avoid duplicating of duration constants through the codebase and + to avoid duplication of duration constants through the codebase and eliminate creation of intermediate durations. *Andrey Novikov, Andrew White* diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 70d5c9af8e..7a9eb8c266 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -47,13 +47,23 @@ class DateTime # DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => DateTime.new(1981, 8, 1, 22, 35, 0) # DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => DateTime.new(1981, 8, 29, 0, 0, 0) def change(options) + if new_nsec = options[:nsec] + raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec] + new_fraction = Rational(new_nsec, 1000000000) + else + new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + new_fraction = Rational(new_usec, 1000000) + end + + raise ArgumentError, "argument out of range" if new_fraction >= 1 + ::DateTime.civil( options.fetch(:year, year), options.fetch(:month, month), options.fetch(:day, day), options.fetch(:hour, hour), options.fetch(:min, options[:hour] ? 0 : min), - options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec + sec_fraction), + options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_fraction, options.fetch(:offset, offset), options.fetch(:start, start) ) @@ -122,7 +132,7 @@ class DateTime # Returns a new DateTime representing the end of the day (23:59:59). def end_of_day - change(hour: 23, min: 59, sec: 59) + change(hour: 23, min: 59, sec: 59, usec: Rational(999999999, 1000)) end alias :at_end_of_day :end_of_day @@ -134,7 +144,7 @@ class DateTime # Returns a new DateTime representing the end of the hour (hh:59:59). def end_of_hour - change(min: 59, sec: 59) + change(min: 59, sec: 59, usec: Rational(999999999, 1000)) end alias :at_end_of_hour :end_of_hour @@ -146,7 +156,7 @@ class DateTime # Returns a new DateTime representing the end of the minute (hh:mm:59). def end_of_minute - change(sec: 59) + change(sec: 59, usec: Rational(999999999, 1000)) end alias :at_end_of_minute :end_of_minute diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb index edfc8296fe..bba2b3be2e 100644 --- a/activesupport/lib/active_support/core_ext/marshal.rb +++ b/activesupport/lib/active_support/core_ext/marshal.rb @@ -1,7 +1,7 @@ module ActiveSupport module MarshalWithAutoloading # :nodoc: - def load(source) - super(source) + def load(source, proc = nil) + super(source, proc) rescue ArgumentError, NameError => exc if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|) # try loading the class/module diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 003f6203ef..70cf78519d 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -180,6 +180,7 @@ module ActiveSupport sum(1, time) end alias :from_now :since + alias :after :since # Calculates a new Time or Date that is as far in the past # as this Duration represents. @@ -187,6 +188,7 @@ module ActiveSupport sum(-1, time) end alias :until :ago + alias :before :ago def inspect #:nodoc: parts. diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb index 84eef6a623..95a86889ec 100644 --- a/activesupport/lib/active_support/gzip.rb +++ b/activesupport/lib/active_support/gzip.rb @@ -21,7 +21,7 @@ module ActiveSupport # Decompresses a gzipped string. def self.decompress(source) - Zlib::GzipReader.new(StringIO.new(source)).read + Zlib::GzipReader.wrap(StringIO.new(source), &:read) end # Compresses a string using gzip. diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 79e7feaf47..1927cddf34 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -270,7 +270,7 @@ module ActiveSupport end def compact - dup.compact! + dup.tap(&:compact!) end # Convert to a regular hash with string keys. @@ -316,4 +316,6 @@ module ActiveSupport end end +# :stopdoc: + HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 0671469788..69109d2005 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -61,7 +61,7 @@ module ActiveSupport sign_secret = signature_key_or_options.first @secret = secret @sign_secret = sign_secret - @cipher = options[:cipher] || "aes-256-cbc" + @cipher = options[:cipher] || DEFAULT_CIPHER @digest = options[:digest] || "SHA1" unless aead_mode? @verifier = resolve_verifier @serializer = options[:serializer] || Marshal diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index e3b31c10f5..36f0ee22b8 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -4,8 +4,8 @@ require "core_ext/date_and_time_behavior" require "time_zone_test_helpers" class DateTimeExtCalculationsTest < ActiveSupport::TestCase - def date_time_init(year, month, day, hour, minute, second, *args) - DateTime.civil(year, month, day, hour, minute, second) + def date_time_init(year, month, day, hour, minute, second, usec = 0) + DateTime.civil(year, month, day, hour, minute, second + (usec / 1000000)) end include DateAndTimeBehavior @@ -113,7 +113,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_end_of_day - assert_equal DateTime.civil(2005, 2, 4, 23, 59, 59), DateTime.civil(2005, 2, 4, 10, 10, 10).end_of_day + assert_equal DateTime.civil(2005, 2, 4, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 10, 10, 10).end_of_day end def test_beginning_of_hour @@ -121,7 +121,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_end_of_hour - assert_equal DateTime.civil(2005, 2, 4, 19, 59, 59), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_hour + assert_equal DateTime.civil(2005, 2, 4, 19, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_hour end def test_beginning_of_minute @@ -129,13 +129,13 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_end_of_minute - assert_equal DateTime.civil(2005, 2, 4, 19, 30, 59), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_minute + assert_equal DateTime.civil(2005, 2, 4, 19, 30, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_minute end def test_end_of_month - assert_equal DateTime.civil(2005, 3, 31, 23, 59, 59), DateTime.civil(2005, 3, 20, 10, 10, 10).end_of_month - assert_equal DateTime.civil(2005, 2, 28, 23, 59, 59), DateTime.civil(2005, 2, 20, 10, 10, 10).end_of_month - assert_equal DateTime.civil(2005, 4, 30, 23, 59, 59), DateTime.civil(2005, 4, 20, 10, 10, 10).end_of_month + assert_equal DateTime.civil(2005, 3, 31, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 3, 20, 10, 10, 10).end_of_month + assert_equal DateTime.civil(2005, 2, 28, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 20, 10, 10, 10).end_of_month + assert_equal DateTime.civil(2005, 4, 30, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 4, 20, 10, 10, 10).end_of_month end def test_last_year @@ -162,12 +162,19 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2006, 2, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(year: 2006) assert_equal DateTime.civil(2005, 6, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(month: 6) assert_equal DateTime.civil(2012, 9, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(year: 2012, month: 9) - assert_equal DateTime.civil(2005, 2, 22, 16), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16) - assert_equal DateTime.civil(2005, 2, 22, 16, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45) - assert_equal DateTime.civil(2005, 2, 22, 15, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(min: 45) + assert_equal DateTime.civil(2005, 2, 22, 16), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16) + assert_equal DateTime.civil(2005, 2, 22, 16, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45) + assert_equal DateTime.civil(2005, 2, 22, 15, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(min: 45) # datetime with fractions of a second assert_equal DateTime.civil(2005, 2, 1, 15, 15, 10.7), DateTime.civil(2005, 2, 22, 15, 15, 10.7).change(day: 1) + assert_equal DateTime.civil(2005, 1, 2, 11, 22, Rational(33000008, 1000000)), DateTime.civil(2005, 1, 2, 11, 22, 33).change(usec: 8) + assert_equal DateTime.civil(2005, 1, 2, 11, 22, Rational(33000008, 1000000)), DateTime.civil(2005, 1, 2, 11, 22, 33).change(nsec: 8000) + assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 1, nsec: 1) } + assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 1000000) } + assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(nsec: 1000000000) } + assert_nothing_raised { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 999999) } + assert_nothing_raised { DateTime.civil(2005, 1, 2, 11, 22, 0).change(nsec: 999999999) } end def test_advance diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 6a275d1d5b..6facb04f1f 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -179,6 +179,19 @@ class DurationTest < ActiveSupport::TestCase Time.zone = nil end + def test_before_and_afer + t = Time.local(2000) + assert_equal t + 1, 1.second.after(t) + assert_equal t - 1, 1.second.before(t) + end + + def test_before_and_after_without_argument + Time.stub(:now, Time.local(2000)) do + assert_equal Time.now - 1.second, 1.second.before + assert_equal Time.now + 1.second, 1.second.after + end + end + def test_adding_hours_across_dst_boundary with_env_tz "CET" do assert_equal Time.local(2009, 3, 29, 0, 0, 0) + 24.hours, Time.local(2009, 3, 30, 1, 0, 0) diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 05813ad388..525ea08abd 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -8,6 +8,8 @@ require "active_support/core_ext/object/deep_dup" require "active_support/inflections" class HashExtTest < ActiveSupport::TestCase + HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess + class IndifferentHash < ActiveSupport::HashWithIndifferentAccess end @@ -597,6 +599,16 @@ class HashExtTest < ActiveSupport::TestCase assert_equal(@strings, compacted_hash) assert_equal(hash_contain_nil_value, hash) assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash + + empty_hash = ActiveSupport::HashWithIndifferentAccess.new + compacted_hash = empty_hash.compact + + assert_equal compacted_hash, empty_hash + + non_empty_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: :bar) + compacted_hash = non_empty_hash.compact + + assert_equal compacted_hash, non_empty_hash end def test_indifferent_to_hash @@ -1078,6 +1090,30 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 1, hash[:a] assert_equal 3, hash[:b] end + + def test_inheriting_from_top_level_hash_with_indifferent_access_preserves_ancestors_chain + klass = Class.new(::HashWithIndifferentAccess) + assert_equal ActiveSupport::HashWithIndifferentAccess, klass.ancestors[1] + end + + def test_inheriting_from_hash_with_indifferent_access_properly_dumps_ivars + klass = Class.new(::HashWithIndifferentAccess) do + def initialize(*) + @foo = "bar" + super + end + end + + yaml_output = klass.new.to_yaml + + # `hash-with-ivars` was introduced in 2.0.9 (https://git.io/vyUQW) + if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("2.0.9") + assert_includes yaml_output, "hash-with-ivars" + assert_includes yaml_output, "@foo: bar" + else + assert_includes yaml_output, "hash" + end + end end class IWriteMyOwnXML @@ -1123,6 +1159,8 @@ class HashExtToParamTests < ActiveSupport::TestCase end class HashToXmlTest < ActiveSupport::TestCase + HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess + def setup @xml_options = { root: :person, skip_instruct: true, indent: 0 } end diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb index a899f98705..cabeed2fae 100644 --- a/activesupport/test/core_ext/marshal_test.rb +++ b/activesupport/test/core_ext/marshal_test.rb @@ -19,6 +19,19 @@ class MarshalTest < ActiveSupport::TestCase end end + test "that Marshal#load still works when passed a proc" do + example_string = "test" + + example_proc = Proc.new do |o| + if o.is_a?(String) + o.capitalize! + end + end + + dumped = Marshal.dump(example_string) + assert_equal Marshal.load(dumped, example_proc), "Test" + end + test "that a missing class is autoloaded from string" do dumped = nil with_autoloading_fixtures do diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb index f51d3cdf65..33e0cd2a04 100644 --- a/activesupport/test/gzip_test.rb +++ b/activesupport/test/gzip_test.rb @@ -30,4 +30,14 @@ class GzipTest < ActiveSupport::TestCase assert_equal true, (gzipped_by_best_compression.bytesize < gzipped_by_speed.bytesize) end + + def test_decompress_checks_crc + compressed = ActiveSupport::Gzip.compress("Hello World") + first_crc_byte_index = compressed.bytesize - 8 + compressed.setbyte(first_crc_byte_index, compressed.getbyte(first_crc_byte_index) ^ 0xff) + + assert_raises(Zlib::GzipFile::CRCError) do + ActiveSupport::Gzip.decompress(compressed) + end + end end diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb index 7644f6fe4a..486c7243ad 100644 --- a/guides/bug_report_templates/action_controller_master.rb +++ b/guides/bug_report_templates/action_controller_master.rb @@ -8,7 +8,6 @@ end gemfile(true) do source "https://rubygems.org" gem "rails", github: "rails/rails" - gem "arel", github: "rails/arel" end require "action_controller/railtie" diff --git a/guides/source/i18n.md b/guides/source/i18n.md index ed8cf8a344..6c8706bc13 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -707,6 +707,7 @@ The `:count` interpolation variable has a special role in that it both is interp ```ruby I18n.backend.store_translations :en, inbox: { + zero: 'no messages', # optional one: 'one message', other: '%{count} messages' } @@ -715,15 +716,20 @@ I18n.translate :inbox, count: 2 I18n.translate :inbox, count: 1 # => 'one message' + +I18n.translate :inbox, count: 0 +# => 'no messages' ``` The algorithm for pluralizations in `:en` is as simple as: ```ruby -entry[count == 1 ? 0 : 1] +lookup_key = :zero if count == 0 && entry.has_key?(:zero) +lookup_key ||= count == 1 ? :one : :other +entry[lookup_key] ``` -I.e. the translation denoted as `:one` is regarded as singular, the other is used as plural (including the count being zero). +The translation denoted as `:one` is regarded as singular, and the `:other` is used as plural. If the count is zero, and a `:zero` entry is present, then it will be used instead of `:other`. If the lookup for the key does not return a Hash suitable for pluralization, an `I18n::InvalidPluralizationData` exception is raised. diff --git a/guides/source/testing.md b/guides/source/testing.md index 652030a733..4231500729 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -1435,6 +1435,10 @@ variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect. The helper `read_fixture` is used to read in the content from this file. +NOTE: `email.body.to_s` is present when there's only one (HTML or text) part present. +If the mailer provides both, you can test your fixture against specific parts +with `email.text_part.body.to_s` or `email.html_part.body.to_s`. + Here's the content of the `invite` fixture: ``` diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 8ba00a2b10..6005298127 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -65,6 +65,25 @@ Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh] Don't forget to review the difference, to see if there were any unexpected changes. +Upgrading from Rails 5.0 to Rails 5.1 +------------------------------------- + +For more information on changes made to Rails 5.1 please see the [release notes](5_1_release_notes.html). + +### Top-level `HashWithIndifferentAccess` is soft-deprecated + +If your application uses the the top-level `HashWithIndifferentAccess` class, you +should slowly move your code to use the `ActiveSupport::HashWithIndifferentAccess` +one. + +It is only soft-deprecated, which means that your code will not break at the +moment and no deprecation warning will be displayed but this constant will be +removed in the future. + +Also, if you have pretty old YAML documents containg dumps of such objects, +you may need to load and dump them again to make sure that they reference +the right constant and that loading them won't break in the future. + Upgrading from Rails 4.2 to Rails 5.0 ------------------------------------- diff --git a/guides/w3c_validator.rb b/guides/w3c_validator.rb index c0a32c6b91..4671e040ca 100644 --- a/guides/w3c_validator.rb +++ b/guides/w3c_validator.rb @@ -32,7 +32,8 @@ include W3CValidators module RailsGuides class Validator def validate - validator = MarkupValidator.new + # https://github.com/w3c-validators/w3c_validators/issues/25 + validator = NuValidator.new STDOUT.sync = true errors_on_guides = {} @@ -44,11 +45,11 @@ module RailsGuides next end - if results.validity - print "." - else + if results.errors.length > 0 print "E" errors_on_guides[f] = results.errors + else + print "." end end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 3afadc8cba..54bf0ec65e 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,19 @@ ## Rails 5.1.0.beta1 (February 23, 2017) ## +* Add encrypted secrets in `config/secrets.yml.enc`. + + Allow storing production secrets straight in the revision control system by + encrypting them. + + Use `bin/rails secrets:setup` to opt-in by generating `config/secrets.yml.enc` + for the secrets themselves and `config/secrets.yml.key` for the encryption key. + + Edit secrets with `bin/rails secrets:edit`. + + See `bin/rails secrets:setup --help` for more. + + *Kasper Timm Hansen* + * Fix running multiple tests in one `rake` command e.g. `bin/rake test:models test:controllers` diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc index ef9bbf3d7e..654c7bae57 100644 --- a/railties/RDOC_MAIN.rdoc +++ b/railties/RDOC_MAIN.rdoc @@ -57,7 +57,7 @@ can read more about Action Pack in its {README}[link:files/actionpack/README_rdo * The \README file created within your application. * {Getting Started with \Rails}[http://guides.rubyonrails.org/getting_started.html]. -* {Ruby on \Rails Tutorial}[http://www.railstutorial.org/book]. +* {Ruby on \Rails Tutorial}[https://www.railstutorial.org/book]. * {Ruby on \Rails Guides}[http://guides.rubyonrails.org]. * {The API Documentation}[http://api.rubyonrails.org]. diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb index d8549db62e..0d4e6dc5a1 100644 --- a/railties/lib/rails/command.rb +++ b/railties/lib/rails/command.rb @@ -36,8 +36,8 @@ module Rails command_name = namespace end - command_name = "help" if command_name.blank? || HELP_MAPPINGS.include?(command_name) - namespace = "version" if %w( -v --version ).include?(command_name) + command_name, namespace = "help", "help" if command_name.blank? || HELP_MAPPINGS.include?(command_name) + command_name, namespace = "version", "version" if %w( -v --version ).include?(command_name) command = find_by_namespace(namespace, command_name) if command && command.all_commands[command_name] diff --git a/railties/lib/rails/commands/generate/generate_command.rb b/railties/lib/rails/commands/generate/generate_command.rb index aa8dab71b0..2718b453a8 100644 --- a/railties/lib/rails/commands/generate/generate_command.rb +++ b/railties/lib/rails/commands/generate/generate_command.rb @@ -4,6 +4,9 @@ module Rails module Command class GenerateCommand < Base # :nodoc: def help + require_application_and_environment! + load_generators + Rails::Generators.help self.class.command_name end diff --git a/railties/lib/rails/commands/secrets/secrets_command.rb b/railties/lib/rails/commands/secrets/secrets_command.rb index 3ba8c0c85b..b9ae5d8b3b 100644 --- a/railties/lib/rails/commands/secrets/secrets_command.rb +++ b/railties/lib/rails/commands/secrets/secrets_command.rb @@ -18,16 +18,27 @@ module Rails end def edit + if ENV["EDITOR"].to_s.empty? + say "No $EDITOR to open decrypted secrets in. Assign one like this:" + say "" + say %(EDITOR="mate --wait" bin/rails secrets:edit) + say "" + say "For editors that fork and exit immediately, it's important to pass a wait flag," + say "otherwise the secrets will be saved immediately with no chance to edit." + + return + end + require_application_and_environment! Rails::Secrets.read_for_editing do |tmp_path| - puts "Waiting for secrets file to be saved. Abort with Ctrl-C." + say "Waiting for secrets file to be saved. Abort with Ctrl-C." system("\$EDITOR #{tmp_path}") end - puts "New secrets encrypted and saved." + say "New secrets encrypted and saved." rescue Interrupt - puts "Aborted changing encrypted secrets: nothing saved." + say "Aborted changing encrypted secrets: nothing saved." rescue Rails::Secrets::MissingKeyError => error say error.message end diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index d58721f648..7e8c86fb49 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -99,8 +99,9 @@ module Rails class_option :port, aliases: "-p", type: :numeric, desc: "Runs Rails on the specified port.", banner: :port, default: 3000 - class_option :binding, aliases: "-b", type: :string, default: "localhost", - desc: "Binds Rails to the specified IP.", banner: :IP + class_option :binding, aliases: "-b", type: :string, + desc: "Binds Rails to the specified IP - defaults to 'localhost' in development and '0.0.0.0' in other environments'.", + banner: :IP class_option :config, aliases: "-c", type: :string, default: "config.ru", desc: "Uses a custom rackup configuration.", banner: :file class_option :daemon, aliases: "-d", type: :boolean, default: false, @@ -133,28 +134,64 @@ module Rails no_commands do def server_options { - server: @server, - log_stdout: @log_stdout, - Port: port, - Host: host, - DoNotReverseLookup: true, - config: options[:config], - environment: environment, - daemonize: options[:daemon], - pid: pid, - caching: options["dev-caching"], - restart_cmd: restart_command + user_supplied_options: user_supplied_options, + server: @server, + log_stdout: @log_stdout, + Port: port, + Host: host, + DoNotReverseLookup: true, + config: options[:config], + environment: environment, + daemonize: options[:daemon], + pid: pid, + caching: options["dev-caching"], + restart_cmd: restart_command } end end private + def user_supplied_options + @user_supplied_options ||= begin + # Convert incoming options array to a hash of flags + # ["-p", "3001", "-c", "foo"] # => {"-p" => true, "-c" => true} + user_flag = {} + @original_options.each_with_index { |command, i| user_flag[command] = true if i.even? } + + # Collect all options that the user has explicitly defined so we can + # differentiate them from defaults + user_supplied_options = [] + self.class.class_options.select do |key, option| + if option.aliases.any? { |name| user_flag[name] } || user_flag["--#{option.name}"] + name = option.name.to_sym + case name + when :port + name = :Port + when :binding + name = :Host + when :"dev-caching" + name = :caching + when :daemonize + name = :daemon + end + user_supplied_options << name + end + end + user_supplied_options << :Host if ENV["HOST"] + user_supplied_options << :Port if ENV["PORT"] + user_supplied_options.uniq + end + end + def port ENV.fetch("PORT", options[:port]).to_i end def host - ENV.fetch("HOST", options[:binding]) + unless (default_host = options[:binding]) + default_host = environment == "development" ? "localhost" : "0.0.0.0" + end + ENV.fetch("HOST", default_host) end def environment diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 3f1bf6a5bb..8ec805370b 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -63,7 +63,7 @@ module Rails stylesheet_engine: :css, scaffold_stylesheet: true, system_tests: nil, - test_framework: false, + test_framework: nil, template_engine: :erb } } diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 04f6341471..56e286f259 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -322,7 +322,7 @@ module Rails return [] unless options[:webpack] comment = "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker" - GemfileEntry.github "webpacker", "rails/webpacker", nil, comment + GemfileEntry.new "webpacker", nil, comment end def jbuilder_gemfile_entry diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 86326d98ed..442258c9d1 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -32,6 +32,14 @@ module Rails # This allows you to override entire operations, like the creation of the # Gemfile, README, or JavaScript files, without needing to know exactly # what those operations do so you can create another template action. + # + # class CustomAppBuilder < Rails::AppBuilder + # def test + # @generator.gem "rspec-rails", group: [:development, :test] + # run "bundle install" + # generate "rspec:install" + # end + # end class AppBuilder def rakefile template "Rakefile" diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index be211e016d..ca48919f9a 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -92,6 +92,7 @@ task default: :test opts[:api] = options.api? opts[:skip_listen] = true opts[:skip_git] = true + opts[:skip_turbolinks] = true invoke Rails::Generators::AppGenerator, [ File.expand_path(dummy_path, destination_root) ], opts @@ -432,7 +433,7 @@ end end def inside_application? - rails_app_path && app_path =~ /^#{rails_app_path}/ + rails_app_path && destination_root.start_with?(rails_app_path.to_s) end def relative_path diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 09931c108a..8e290239bd 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -11,7 +11,7 @@ require "rails/generators/test_case" require "active_support/testing/autorun" -if defined?(Capbyara) +if defined?(Capybara) && defined?(Puma) require "action_dispatch/system_test_case" end @@ -49,7 +49,7 @@ class ActionDispatch::IntegrationTest end end -if defined? Capybara +if defined?(Capybara) && defined?(Puma) class ActionDispatch::SystemTestCase def before_setup # :nodoc: @routes = Rails.application.routes diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index d2ce14f594..ee0d697599 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -184,5 +184,12 @@ module ApplicationTests Rails::Command.send(:remove_const, "APP_PATH") end + + test "help does not show hidden namespaces" do + FileUtils.cd(rails_root) do + output = `bin/rails generate --help` + assert_no_match "active_record:migration", output + end + end end end diff --git a/railties/test/application/help_test.rb b/railties/test/application/help_test.rb new file mode 100644 index 0000000000..0c3fe8bfa3 --- /dev/null +++ b/railties/test/application/help_test.rb @@ -0,0 +1,23 @@ +require "isolation/abstract_unit" + +class HelpTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "command works" do + output = Dir.chdir(app_path) { `bin/rails help` } + assert_match "The most common rails commands are", output + end + + test "short-cut alias works" do + output = Dir.chdir(app_path) { `bin/rails -h` } + assert_match "The most common rails commands are", output + end +end diff --git a/railties/test/application/version_test.rb b/railties/test/application/version_test.rb new file mode 100644 index 0000000000..6b419ae7ae --- /dev/null +++ b/railties/test/application/version_test.rb @@ -0,0 +1,24 @@ +require "isolation/abstract_unit" +require "rails/gem_version" + +class VersionTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "command works" do + output = Dir.chdir(app_path) { `bin/rails version` } + assert_equal "Rails #{Rails.gem_version}\n", output + end + + test "short-cut alias works" do + output = Dir.chdir(app_path) { `bin/rails -v` } + assert_equal "Rails #{Rails.gem_version}\n", output + end +end diff --git a/railties/test/commands/secrets_test.rb b/railties/test/commands/secrets_test.rb new file mode 100644 index 0000000000..13fcf6c8a4 --- /dev/null +++ b/railties/test/commands/secrets_test.rb @@ -0,0 +1,24 @@ +require "isolation/abstract_unit" +require "rails/command" +require "rails/commands/secrets/secrets_command" + +class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "edit without editor gives hint" do + assert_match "No $EDITOR to open decrypted secrets in", run_edit_command(editor: "") + end + + private + def run_edit_command(editor: "cat") + Dir.chdir(app_path) { `EDITOR="#{editor}" bin/rails secrets:edit` } + end +end diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb index e3dfc3e82b..d21a80982b 100644 --- a/railties/test/commands/server_test.rb +++ b/railties/test/commands/server_test.rb @@ -121,6 +121,32 @@ class Rails::ServerTest < ActiveSupport::TestCase end end + def test_host + with_rails_env "development" do + options = parse_arguments([]) + assert_equal "localhost", options[:Host] + end + + with_rails_env "production" do + options = parse_arguments([]) + assert_equal "0.0.0.0", options[:Host] + end + + with_rails_env "development" do + args = ["-b", "127.0.0.1"] + options = parse_arguments(args) + assert_equal "127.0.0.1", options[:Host] + end + end + + def test_records_user_supplied_options + server_options = parse_arguments(["-p", 3001]) + assert_equal [:Port], server_options[:user_supplied_options] + + server_options = parse_arguments(["--port", 3001]) + assert_equal [:Port], server_options[:user_supplied_options] + end + def test_default_options server = Rails::Server.new old_default_options = server.default_options diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index eaf1199601..8ec096e5c6 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -536,6 +536,21 @@ class PluginGeneratorTest < Rails::Generators::TestCase FileUtils.rm gemfile_path end + def test_creating_plugin_only_specify_plugin_name_in_app_directory_adds_gemfile_entry + # simulate application existence + gemfile_path = "#{Rails.root}/Gemfile" + Object.const_set("APP_PATH", Rails.root) + FileUtils.touch gemfile_path + + FileUtils.cd(destination_root) + run_generator ["bukkits"] + + assert_file gemfile_path, /gem 'bukkits', path: 'bukkits'/ + ensure + Object.send(:remove_const, "APP_PATH") + FileUtils.rm gemfile_path + end + def test_skipping_gemfile_entry # simulate application existence gemfile_path = "#{Rails.root}/Gemfile" diff --git a/tasks/release.rb b/tasks/release.rb index d394b181c3..8fb151ceb4 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -186,7 +186,7 @@ task :announce do raise "Only valid for patch releases" end - sums = "$ shasum *-#{version}.gem\n" + `shasum *-#{version}.gem` + sums = "$ shasum -a 256 *-#{version}.gem\n" + `shasum -a 256 *-#{version}.gem` puts "Hi everyone," puts @@ -232,7 +232,7 @@ GitHub](https://github.com/rails/rails/compare/v#{previous_version}...v#{version ## SHA-1 If you'd like to verify that your gem is the same as the one I've uploaded, -please use these SHA-1 hashes. +please use these SHA-256 hashes. Here are the checksums for #{version}: |