diff options
87 files changed, 702 insertions, 502 deletions
diff --git a/.travis.yml b/.travis.yml index 95a9099901..6c7380f050 100644 --- a/.travis.yml +++ b/.travis.yml @@ -114,7 +114,8 @@ notifications: on_success: change on_failure: always channels: - - "irc.freenode.org#rails-contrib" + # "irc.freenode.org#rails-contrib" + - secure: "QFKSOK7xQiWWqTzYfYm0XWoW7idzuxT57MBW9i9EASyRLEPuDwZEubKRP40Y7wPx7ylQd9lp6kJheeLnrDvvTjFbW3sWv9GDRl4WlOU8sG/Kv7MXAASXlDqzyJxxXTtzLeXz2iwY296kOBuKxKxl923eTvEGeocwH02QGo14LpQ=" campfire: on_success: change on_failure: always @@ -40,7 +40,7 @@ gem "rubocop", ">= 0.47", require: false gem "rb-inotify", github: "matthewd/rb-inotify", branch: "close-handling", require: false group :doc do - gem "sdoc", "> 1.0.0.rc1", "< 2.0" + gem "sdoc", github: "robin850/sdoc", branch: "upgrade" gem "redcarpet", "~> 3.2.3", platforms: :ruby gem "w3c_validators" gem "kindlerb", "~> 1.2.0" diff --git a/Gemfile.lock b/Gemfile.lock index 2096465a8b..516b4155c1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,6 +41,14 @@ GIT sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) +GIT + remote: https://github.com/robin850/sdoc.git + revision: 0e340352f3ab2f196c8a8743f83c2ee286e4f71c + branch: upgrade + specs: + sdoc (1.0.0.rc2) + rdoc (~> 5.0) + PATH remote: . specs: @@ -135,7 +143,7 @@ GEM bcrypt (3.1.11-x86-mingw32) beaneater (1.0.0) benchmark-ips (2.7.2) - blade (0.7.0) + blade (0.7.1) activesupport (>= 3.0.0) blade-qunit_adapter (~> 2.0.1) coffee-script @@ -145,7 +153,7 @@ GEM faye sprockets (>= 3.0) thin (>= 1.6.0) - thor (~> 0.19.1) + thor (>= 0.19.1) useragent (~> 0.16.7) blade-qunit_adapter (2.0.1) blade-sauce_labs_plugin (0.7.2) @@ -389,8 +397,6 @@ GEM sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sdoc (1.0.0.rc2) - rdoc (~> 5.0) selenium-webdriver (3.5.1) childprocess (~> 0.5) rubyzip (~> 1.0) @@ -437,7 +443,7 @@ GEM daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) - thor (0.19.4) + thor (0.20.0) thread (0.1.7) thread_safe (0.3.6) tilt (2.0.8) @@ -521,7 +527,7 @@ DEPENDENCIES resque-scheduler rubocop (>= 0.47) sass-rails! - sdoc (> 1.0.0.rc1, < 2.0) + sdoc! sequel sidekiq sneakers diff --git a/actioncable/lib/action_cable/remote_connections.rb b/actioncable/lib/action_cable/remote_connections.rb index a92b5e90b4..a07a517092 100644 --- a/actioncable/lib/action_cable/remote_connections.rb +++ b/actioncable/lib/action_cable/remote_connections.rb @@ -51,9 +51,10 @@ module ActionCable server.connection_identifiers end - private + protected attr_reader :server + private def set_identifier_instance_vars(ids) raise InvalidIdentifiersError unless valid_identifiers?(ids) ids.each { |k, v| instance_variable_set("@#{k}", v) } diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index c64f55f5b7..facea944ff 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -10,7 +10,7 @@ module ActionCable class Redis < Base # :nodoc: prepend ChannelPrefix - # Overwrite this factory method for redis connections if you want to use a different Redis library than Redis. + # Overwrite this factory method for Redis connections if you want to use a different Redis library than the redis gem. # This is needed, for example, when using Makara proxies for distributed Redis. cattr_accessor :redis_connector, default: ->(config) do ::Redis.new(config.slice(:url, :host, :port, :db, :password)) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 5a55ee13ee..c0913715ac 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -360,7 +360,11 @@ module ActionDispatch @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; " end - def handle_options(options) #:nodoc: + def handle_options(options) # :nodoc: + if options[:expires].respond_to?(:from_now) + options[:expires] = options[:expires].from_now + end + options[:path] ||= "/" if options[:domain] == :all || options[:domain] == "all" @@ -488,6 +492,14 @@ module ActionDispatch def request; @parent_jar.request; end private + def expiry_options(options) + if options[:expires].respond_to?(:from_now) + { expires_in: options[:expires] } + else + { expires_at: options[:expires] } + end + end + def parse(name, data); data; end def commit(options); end end @@ -569,7 +581,7 @@ module ActionDispatch end def commit(options) - options[:value] = @verifier.generate(serialize(options[:value])) + options[:value] = @verifier.generate(serialize(options[:value]), expiry_options(options)) raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE end @@ -609,7 +621,7 @@ module ActionDispatch end def commit(options) - options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value])) + options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), expiry_options(options)) raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 28809d7e67..57a5bc681e 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1851,7 +1851,7 @@ module ActionDispatch path_types.fetch(String, []).each do |_path| route_options = options.dup if _path && option_path - raise ArgumentError, "Ambiguous route definition. Both :path and the route path where specified as strings." + raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings." end to = get_to_from_path(_path, to, route_options[:action]) decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints) diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb index 4eef15b6c2..dbd27ffd32 100644 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb @@ -21,7 +21,7 @@ module ActionDispatch # This is the default value if the +CI+ environment variables # is defined. # * [+artifact+] display the screenshot in the terminal, using the terminal - # artifact format (http://buildkite.github.io/terminal/inline-images/). + # artifact format (https://buildkite.github.io/terminal/inline-images/). def take_screenshot save_image puts display_image diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index a551fb00a8..cb225c0f62 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -278,6 +278,11 @@ class CookiesTest < ActionController::TestCase def encrypted_cookie cookies.encrypted["foo"] end + + def cookie_expires_in_two_hours + cookies[:user_name] = { value: "assain", expires: 2.hours } + head :ok + end end tests TestController @@ -1235,6 +1240,33 @@ class CookiesTest < ActionController::TestCase assert_equal "bar", @controller.encrypted_cookie end + def test_signed_cookie_with_expires_set_relatively + cookies.signed[:user_name] = { value: "assain", expires: 2.hours } + + travel 1.hour + assert_equal "assain", cookies.signed[:user_name] + + travel 2.hours + assert_nil cookies.signed[:user_name] + end + + def test_encrypted_cookie_with_expires_set_relatively + cookies.encrypted[:user_name] = { value: "assain", expires: 2.hours } + + travel 1.hour + assert_equal "assain", cookies.encrypted[:user_name] + + travel 2.hours + assert_nil cookies.encrypted[:user_name] + end + + def test_vanilla_cookie_with_expires_set_relatively + travel_to Time.utc(2017, 8, 15) do + get :cookie_expires_in_two_hours + assert_cookie_header "user_name=assain; path=/; expires=Tue, 15 Aug 2017 02:00:00 -0000" + end + end + private def assert_cookie_header(expected) header = @response.headers["Set-Cookie"] diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index 26a63c9f7d..6517cf4c99 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -26,6 +26,11 @@ class CookieStoreTest < ActionDispatch::IntegrationTest render plain: Rack::Utils.escape(Verifier.generate(session.to_hash)) end + def set_session_value_expires_in_five_hours + session[:foo] = "bar" + render plain: Rack::Utils.escape(Verifier.generate(session.to_hash, expires_in: 5.hours)) + end + def get_session_value render plain: "foo: #{session[:foo].inspect}" end @@ -283,7 +288,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest cookies[SessionKey] = SignedBar - get "/set_session_value" + get "/set_session_value_expires_in_five_hours" assert_response :success cookie_body = response.body @@ -299,7 +304,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest get "/no_session_access" assert_response :success - assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", + assert_equal "_myapp_session=#{cookies[SessionKey]}; path=/; expires=#{expected_expiry}; HttpOnly", headers["Set-Cookie"] end end diff --git a/actionview/test/ujs/public/vendor/qunit.js b/actionview/test/ujs/public/vendor/qunit.js index 0e279fde17..50a9e6455d 100644 --- a/actionview/test/ujs/public/vendor/qunit.js +++ b/actionview/test/ujs/public/vendor/qunit.js @@ -42,7 +42,7 @@ var QUnit, * with IE 7 (and prior) where Error.prototype.toString is * not properly implemented * - * Based on http://es5.github.com/#x15.11.4.4 + * Based on https://es5.github.io/#x15.11.4.4 * * @param {String|Error} error * @return {String} error message diff --git a/activejob/lib/active_job/queue_adapters/async_adapter.rb b/activejob/lib/active_job/queue_adapters/async_adapter.rb index eec9ae08bb..ebf6f384e3 100644 --- a/activejob/lib/active_job/queue_adapters/async_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/async_adapter.rb @@ -31,7 +31,7 @@ module ActiveJob # jobs. Since jobs share a single thread pool, long-running jobs will block # short-lived jobs. Fine for dev/test; bad for production. class AsyncAdapter - # See {Concurrent::ThreadPoolExecutor}[http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options. + # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options. def initialize(**executor_options) @scheduler = Scheduler.new(**executor_options) end diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb index 9629876a6d..f02a32a38e 100644 --- a/activejob/test/support/integration/test_case_helpers.rb +++ b/activejob/test/support/integration/test_case_helpers.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/string/inflections" require "support/integration/jobs_manager" module TestCaseHelpers @@ -30,8 +29,7 @@ module TestCaseHelpers end def adapter_is?(*adapter_class_symbols) - adapter = ActiveJob::Base.queue_adapter.class.name.demodulize.chomp("Adapter").underscore - adapter_class_symbols.map(&:to_s).include? adapter + adapter_class_symbols.map(&:to_s).include? ActiveJob::Base.queue_adapter_name end def wait_for_jobs_to_finish_for(seconds = 60) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index c24ae4e430..971bdd08b1 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -398,21 +398,19 @@ module ActiveModel type = options.delete(:message) if options[:message].is_a?(Symbol) if @base.class.respond_to?(:i18n_scope) - defaults = @base.class.lookup_ancestors.map do |klass| - [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}", - :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ] + i18n_scope = @base.class.i18n_scope.to_s + defaults = @base.class.lookup_ancestors.flat_map do |klass| + [ :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}", + :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ] end + defaults << :"#{i18n_scope}.errors.messages.#{type}" else defaults = [] end - defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope) defaults << :"errors.attributes.#{attribute}.#{type}" defaults << :"errors.messages.#{type}" - defaults.compact! - defaults.flatten! - key = defaults.shift defaults = options.delete(:message) if options[:message] value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 5febb5a59f..4078345abf 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -10,6 +10,11 @@ module ActiveRecord # Converts an arel AST to SQL def to_sql(arel_or_sql_string, binds = []) + sql, _ = to_sql_and_binds(arel_or_sql_string, binds) + sql + end + + def to_sql_and_binds(arel_or_sql_string, binds = []) # :nodoc: if arel_or_sql_string.respond_to?(:ast) unless binds.empty? raise "Passing bind parameters with an arel AST is forbidden. " \ @@ -21,6 +26,7 @@ module ActiveRecord [arel_or_sql_string.dup.freeze, binds] end end + private :to_sql_and_binds # This is used in the StatementCache object. It returns an object that # can be used to query the database repeatedly. @@ -39,7 +45,7 @@ module ActiveRecord # Returns an ActiveRecord::Result instance. def select_all(arel, name = nil, binds = [], preparable: nil) arel = arel_from_relation(arel) - sql, binds = to_sql(arel, binds) + sql, binds = to_sql_and_binds(arel, binds) if !prepared_statements || (arel.is_a?(String) && preparable.nil?) preparable = false else @@ -138,22 +144,22 @@ module ActiveRecord # # If the next id was calculated in advance (as in Oracle), it should be # passed in as +id_value+. - def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil) - sql, binds = to_sql(arel) + def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) value = exec_insert(sql, name, binds, pk, sequence_name) id_value || last_inserted_id(value) end alias create insert # Executes the update statement and returns the number of rows affected. - def update(arel, name = nil) - sql, binds = to_sql(arel) + def update(arel, name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) exec_update(sql, name, binds) end # Executes the delete statement and returns the number of rows affected. - def delete(arel, name = nil) - sql, binds = to_sql(arel) + def delete(arel, name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) exec_delete(sql, name, binds) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 41d93c4322..25622e34c8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -95,7 +95,7 @@ module ActiveRecord def select_all(arel, name = nil, binds = [], preparable: nil) if @query_cache_enabled && !locked?(arel) arel = arel_from_relation(arel) - sql, binds = to_sql(arel, binds) + sql, binds = to_sql_and_binds(arel, binds) cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) } else super diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index 9be26254b2..f17f1d35e2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/core_ext/hash/compact" + module ActiveRecord module ConnectionAdapters # :nodoc: # The goal of this module is to move Adapter specific column @@ -20,38 +22,6 @@ module ActiveRecord spec end - # This can be overridden on an Adapter level basis to support other - # extended datatypes (Example: Adding an array option in the - # PostgreSQL::ColumnDumper) - def prepare_column_options(column) - spec = {} - - if limit = schema_limit(column) - spec[:limit] = limit - end - - if precision = schema_precision(column) - spec[:precision] = precision - end - - if scale = schema_scale(column) - spec[:scale] = scale - end - - default = schema_default(column) if column.has_default? - spec[:default] = default unless default.nil? - - spec[:null] = "false" unless column.null - - if collation = schema_collation(column) - spec[:collation] = collation - end - - spec[:comment] = column.comment.inspect if column.comment.present? - - spec - end - # Lists the valid migration options def migration_keys # :nodoc: column_options_keys @@ -59,6 +29,18 @@ module ActiveRecord deprecate :migration_keys private + def prepare_column_options(column) + spec = {} + spec[:limit] = schema_limit(column) + spec[:precision] = schema_precision(column) + spec[:scale] = schema_scale(column) + spec[:default] = schema_default(column) + spec[:null] = "false" unless column.null + spec[:collation] = schema_collation(column) + spec[:comment] = column.comment.inspect if column.comment.present? + spec.compact! + spec + end def default_primary_key?(column) schema_type(column) == :bigint @@ -98,6 +80,7 @@ module ActiveRecord end def schema_default(column) + return unless column.has_default? type = lookup_cast_type_from_column(column) default = type.deserialize(column.default) if default.nil? 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 8a9c497918..1b4688a904 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -20,10 +20,6 @@ module ActiveRecord include MySQL::ColumnDumper include MySQL::SchemaStatements - def update_table_definition(table_name, base) # :nodoc: - MySQL::Table.new(table_name, base) - end - ## # :singleton-method: # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt> @@ -177,8 +173,7 @@ module ActiveRecord #++ def explain(arel, binds = []) - sql, binds = to_sql(arel, binds) - sql = "EXPLAIN #{sql}" + sql = "EXPLAIN #{to_sql(arel, binds)}" start = Time.now result = exec_query(sql, "EXPLAIN", binds) elapsed = Time.now - start diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb index fbe3596dda..81f7dce562 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -4,24 +4,23 @@ module ActiveRecord module ConnectionAdapters module MySQL module ColumnDumper # :nodoc: - def prepare_column_options(column) - spec = super - spec[:unsigned] = "true" if column.unsigned? - - if supports_virtual_columns? && column.virtual? - spec[:as] = extract_expression_for_virtual_column(column) - spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra) - spec = { type: schema_type(column).inspect }.merge!(spec) - end - - spec - end - def migration_keys super + [:unsigned] end private + def prepare_column_options(column) + spec = super + spec[:unsigned] = "true" if column.unsigned? + + if supports_virtual_columns? && column.virtual? + spec[:as] = extract_expression_for_virtual_column(column) + spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra) + spec = { type: schema_type(column).inspect }.merge!(spec) + end + + spec + end def default_primary_key?(column) super && column.auto_increment? && !column.unsigned? diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb index 1d87d60ba9..7bebe82065 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -62,6 +62,10 @@ module ActiveRecord end end + def update_table_definition(table_name, base) + MySQL::Table.new(table_name, base) + end + private CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"] diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 0dd4aac463..8db2a645af 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -5,8 +5,7 @@ module ActiveRecord module PostgreSQL module DatabaseStatements def explain(arel, binds = []) - sql, binds = to_sql(arel, binds) - sql = "EXPLAIN #{sql}" + sql = "EXPLAIN #{to_sql(arel, binds)}" PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds)) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb index 12c6603081..5031605c2a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -4,19 +4,17 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module ColumnDumper # :nodoc: - # Adds +:array+ option to the default set - def prepare_column_options(column) - spec = super - spec[:array] = "true" if column.array? - spec - end - # Adds +:array+ as a valid migration key def migration_keys super + [:array] end private + def prepare_column_options(column) + spec = super + spec[:array] = "true" if column.array? + spec + end def default_primary_key?(column) schema_type(column) == :bigserial 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 780e642f21..f258203a43 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -586,6 +586,10 @@ module ActiveRecord [super, *order_columns].join(", ") end + def update_table_definition(table_name, base) # :nodoc: + PostgreSQL::Table.new(table_name, base) + end + private def schema_creation PostgreSQL::SchemaCreation.new(self) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3b4439fc46..878c9c8ccd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -76,7 +76,7 @@ module ActiveRecord primary_key: "bigserial primary key", string: { name: "character varying" }, text: { name: "text" }, - integer: { name: "integer" }, + integer: { name: "integer", limit: 4 }, float: { name: "float" }, decimal: { name: "decimal" }, datetime: { name: "timestamp" }, @@ -368,10 +368,6 @@ module ActiveRecord @use_insert_returning end - def update_table_definition(table_name, base) #:nodoc: - PostgreSQL::Table.new(table_name, base) - end - def column_name_for_operation(operation, node) # :nodoc: OPERATION_ALIASES.fetch(operation) { operation.downcase } end @@ -439,9 +435,9 @@ module ActiveRecord end def initialize_type_map(m = type_map) - register_class_with_limit m, "int2", Type::Integer - register_class_with_limit m, "int4", Type::Integer - register_class_with_limit m, "int8", Type::Integer + m.register_type "int2", Type::Integer.new(limit: 2) + m.register_type "int4", Type::Integer.new(limit: 4) + m.register_type "int8", Type::Integer.new(limit: 8) m.register_type "oid", OID::Oid.new m.register_type "float4", Type::Float.new m.alias_type "float8", "float4" @@ -508,17 +504,6 @@ module ActiveRecord load_additional_types end - def extract_limit(sql_type) - case sql_type - when /^bigint/i, /^int8/i - 8 - when /^smallint/i - 2 - else - super - end - end - # Extracts the value from a PostgreSQL column default definition. def extract_value_from_default(default) case default @@ -703,18 +688,20 @@ module ActiveRecord # Use standard-conforming strings so we don't have to do the E'...' dance. set_standard_conforming_strings + variables = @config.fetch(:variables, {}).stringify_keys + # If using Active Record's time zone support configure the connection to return # TIMESTAMP WITH ZONE types in UTC. - # (SET TIME ZONE does not use an equals sign like other SET variables) - if ActiveRecord::Base.default_timezone == :utc - execute("SET time zone 'UTC'", "SCHEMA") - elsif @local_tz - execute("SET time zone '#{@local_tz}'", "SCHEMA") + unless variables["timezone"] + if ActiveRecord::Base.default_timezone == :utc + variables["timezone"] = "UTC" + elsif @local_tz + variables["timezone"] = @local_tz + end end # SET statements from :variables config hash # https://www.postgresql.org/docs/current/static/sql-set.html - variables = @config[:variables] || {} variables.map do |k, v| if v == ":default" || v == :default # Sets the value to the global or compile default diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb index c155e7f1ac..ee84646214 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb @@ -39,6 +39,10 @@ module ActiveRecord end end + def update_table_definition(table_name, base) + SQLite3::Table.new(table_name, base) + end + private def schema_creation SQLite3::SchemaCreation.new(self) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 8c12cb09bd..327ca34e77 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -99,10 +99,6 @@ module ActiveRecord end end - def update_table_definition(table_name, base) # :nodoc: - SQLite3::Table.new(table_name, base) - end - def initialize(connection, logger, connection_options, config) super(connection, logger, config) @@ -203,8 +199,7 @@ module ActiveRecord #++ def explain(arel, binds = []) - sql, binds = to_sql(arel, binds) - sql = "EXPLAIN QUERY PLAN #{sql}" + sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", [])) end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index caabad6055..d319978930 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -571,8 +571,7 @@ module ActiveRecord conn = klass.connection conn.unprepared_statement { - sql, _ = conn.to_sql(relation.arel) - sql + conn.to_sql(relation.arel) } end end diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index f166c27fd9..1e121f2a09 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -39,7 +39,7 @@ module ActiveRecord # The +info+ hash is optional, and if given is used to define metadata # about the current schema (currently, only the schema's version): # - # ActiveRecord::Schema.define(version: 20380119000001) do + # ActiveRecord::Schema.define(version: 2038_01_19_000001) do # ... # end def self.define(info = {}, &block) diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 27a1c89bd1..fd644225ca 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -243,7 +243,9 @@ HEADER end def remove_prefix_and_suffix(table) - table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2") + prefix = Regexp.escape(@options[:table_name_prefix].to_s) + suffix = Regexp.escape(@options[:table_name_suffix].to_s) + table.sub(/\A#{prefix}(.+)#{suffix}\z/, "\\1") end def ignored?(table_name) diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 6e04578576..9aaa2852d0 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -227,6 +227,38 @@ module ActiveRecord result = @connection.select_all("SELECT * FROM posts WHERE id = #{Arel::Nodes::BindParam.new(nil).to_sql}", nil, [[nil, post.id]]) assert_equal expected.to_hash, result.to_hash end + + def test_insert_update_delete_with_legacy_binds + binds = [[nil, 1]] + bind_param = Arel::Nodes::BindParam.new(nil) + + id = @connection.insert("INSERT INTO events(id) VALUES (#{bind_param.to_sql})", nil, nil, nil, nil, binds) + assert_equal 1, id + + @connection.update("UPDATE events SET title = 'foo' WHERE id = #{bind_param.to_sql}", nil, binds) + result = @connection.select_all("SELECT * FROM events WHERE id = #{bind_param.to_sql}", nil, binds) + assert_equal({ "id" => 1, "title" => "foo" }, result.first) + + @connection.delete("DELETE FROM events WHERE id = #{bind_param.to_sql}", nil, binds) + result = @connection.select_all("SELECT * FROM events WHERE id = #{bind_param.to_sql}", nil, binds) + assert_nil result.first + end + + def test_insert_update_delete_with_binds + binds = [Relation::QueryAttribute.new("id", 1, Type.default_value)] + bind_param = Arel::Nodes::BindParam.new(nil) + + id = @connection.insert("INSERT INTO events(id) VALUES (#{bind_param.to_sql})", nil, nil, nil, nil, binds) + assert_equal 1, id + + @connection.update("UPDATE events SET title = 'foo' WHERE id = #{bind_param.to_sql}", nil, binds) + result = @connection.select_all("SELECT * FROM events WHERE id = #{bind_param.to_sql}", nil, binds) + assert_equal({ "id" => 1, "title" => "foo" }, result.first) + + @connection.delete("DELETE FROM events WHERE id = #{bind_param.to_sql}", nil, binds) + result = @connection.select_all("SELECT * FROM events WHERE id = #{bind_param.to_sql}", nil, binds) + assert_nil result.first + end end def test_select_methods_passing_a_association_relation @@ -256,11 +288,11 @@ module ActiveRecord def test_log_invalid_encoding error = assert_raises RuntimeError do @connection.send :log, "SELECT 'ы' FROM DUAL" do - raise "ы".force_encoding(Encoding::ASCII_8BIT) + raise "ы".dup.force_encoding(Encoding::ASCII_8BIT) end end - assert_not_nil error.message + assert_equal "ы", error.message end end end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 24875c6678..2bb217a8b1 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -220,6 +220,13 @@ module ActiveRecord end end + def test_set_session_timezone + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { timezone: "America/New_York" })) + assert_equal "America/New_York", ActiveRecord::Base.connection.query_value("SHOW TIME ZONE") + end + end + def test_get_and_release_advisory_lock lock_id = 5295901941911233559 list_advisory_locks = <<-SQL diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 383c7314e1..6bd11a5d81 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -576,8 +576,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_taking_with_a_number + klass = Class.new(Author) do + has_many :posts, -> { order(:id) } + + def self.name + "Author" + end + end + # taking from unloaded Relation - bob = Author.find(authors(:bob).id) + bob = klass.find(authors(:bob).id) new_post = bob.posts.build assert_not bob.posts.loaded? assert_equal [posts(:misc_by_bob)], bob.posts.take(1) diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb index 9200d32caf..be6dc2acb1 100644 --- a/activerecord/test/cases/migration/column_attributes_test.rb +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -45,11 +45,11 @@ module ActiveRecord assert_nil TestModel.columns_hash["description"].limit end - if current_adapter?(:Mysql2Adapter) + if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) def test_unabstracted_database_dependent_types - add_column :test_models, :intelligence_quotient, :tinyint + add_column :test_models, :intelligence_quotient, :smallint TestModel.reset_column_information - assert_match(/tinyint/, TestModel.columns_hash["intelligence_quotient"].sql_type) + assert_match(/smallint/, TestModel.columns_hash["intelligence_quotient"].sql_type) end end @@ -99,7 +99,21 @@ module ActiveRecord assert_equal 7, wealth_column.scale end + # Test SQLite3 adapter specifically for decimal types with precision and scale + # attributes, since these need to be maintained in schema but aren't actually + # used in SQLite3 itself if current_adapter?(:SQLite3Adapter) + def test_change_column_with_new_precision_and_scale + connection.add_column "test_models", "wealth", :decimal, precision: 9, scale: 7 + + connection.change_column "test_models", "wealth", :decimal, precision: 12, scale: 8 + TestModel.reset_column_information + + wealth_column = TestModel.columns_hash["wealth"] + assert_equal 12, wealth_column.precision + assert_equal 8, wealth_column.scale + end + def test_change_column_preserve_other_column_precision_and_scale connection.add_column "test_models", "last_name", :string connection.add_column "test_models", "wealth", :decimal, precision: 9, scale: 7 diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index c887f54560..170fd02b6f 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -78,6 +78,16 @@ class PersistenceTest < ActiveRecord::TestCase assert_equal "2 updated", Topic.find(2).content end + def test_class_level_update_is_affected_by_scoping + topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } } + + assert_raise(ActiveRecord::RecordNotFound) do + Topic.where("1=0").scoping { Topic.update(topic_data.keys, topic_data.values) } + end + assert_not_equal "1 updated", Topic.find(1).content + assert_not_equal "2 updated", Topic.find(2).content + end + def test_delete_all assert Topic.count > 0 @@ -912,13 +922,33 @@ class PersistenceTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) } end + def test_class_level_destroy_is_affected_by_scoping + should_not_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") + Topic.find(1).replies << should_not_be_destroyed_reply + + assert_raise(ActiveRecord::RecordNotFound) do + Topic.where("1=0").scoping { Topic.destroy(1) } + end + assert_nothing_raised { Topic.find(1) } + assert_nothing_raised { Reply.find(should_not_be_destroyed_reply.id) } + end + def test_class_level_delete - should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") - Topic.find(1).replies << should_be_destroyed_reply + should_not_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") + Topic.find(1).replies << should_not_be_destroyed_reply Topic.delete(1) assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) } - assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) } + assert_nothing_raised { Reply.find(should_not_be_destroyed_reply.id) } + end + + def test_class_level_delete_is_affected_by_scoping + should_not_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world") + Topic.find(1).replies << should_not_be_destroyed_reply + + Topic.where("1=0").scoping { Topic.delete(1) } + assert_nothing_raised { Topic.find(1) } + assert_nothing_raised { Reply.find(should_not_be_destroyed_reply.id) } end def test_create_with_custom_timestamps diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index 2fe2a67b1c..d95a54a2fe 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -109,6 +109,15 @@ module ActiveRecord assert_equal expected.to_sql, actual.to_sql end + def test_polymorphic_shallow_where_not + treasure = treasures(:sapphire) + + expected = [price_estimates(:diamond), price_estimates(:honda)] + actual = PriceEstimate.where.not(estimate_of: treasure) + + assert_equal expected.sort_by(&:id), actual.sort_by(&:id) + end + def test_polymorphic_nested_array_where treasure = Treasure.new treasure.id = 1 @@ -121,6 +130,16 @@ module ActiveRecord assert_equal expected.to_sql, actual.to_sql end + def test_polymorphic_nested_array_where_not + treasure = treasures(:diamond) + car = cars(:honda) + + expected = [price_estimates(:sapphire_1), price_estimates(:sapphire_2)] + actual = PriceEstimate.where.not(estimate_of: [treasure, car]) + + assert_equal expected.sort_by(&:id), actual.sort_by(&:id) + end + def test_polymorphic_array_where_multiple_types treasure_1 = treasures(:diamond) treasure_2 = treasures(:sapphire) diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 01ec3e06ad..eb9b257da9 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -400,6 +400,31 @@ class SchemaDumperTest < ActiveRecord::TestCase $stdout = original end + def test_schema_dump_with_table_name_prefix_and_suffix_regexp_escape + original, $stdout = $stdout, StringIO.new + ActiveRecord::Base.table_name_prefix = "foo$" + ActiveRecord::Base.table_name_suffix = "$bar" + + migration = CreateDogMigration.new + migration.migrate(:up) + + output = perform_schema_dump + assert_no_match %r{create_table "foo\$.+\$bar"}, output + assert_no_match %r{add_index "foo\$.+\$bar"}, output + assert_no_match %r{create_table "schema_migrations"}, output + assert_no_match %r{create_table "ar_internal_metadata"}, output + + if ActiveRecord::Base.connection.supports_foreign_keys? + assert_no_match %r{add_foreign_key "foo\$.+\$bar"}, output + assert_no_match %r{add_foreign_key "[^"]+", "foo\$.+\$bar"}, output + end + ensure + migration.migrate(:down) + + ActiveRecord::Base.table_name_suffix = ActiveRecord::Base.table_name_prefix = "" + $stdout = original + end + def test_schema_dump_with_table_name_prefix_and_ignoring_tables original, $stdout = $stdout, StringIO.new diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index ba0bc6c45d..5d3e2a175c 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -446,17 +446,6 @@ class TimestampTest < ActiveRecord::TestCase toy = Toy.first assert_equal ["created_at", "updated_at"], toy.send(:all_timestamp_attributes_in_model) end - - def test_index_is_created_for_both_timestamps - ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| - t.timestamps null: true, index: true - end - - indexes = ActiveRecord::Base.connection.indexes("foos") - assert_equal ["created_at", "updated_at"], indexes.flat_map(&:columns).sort - ensure - ActiveRecord::Base.connection.drop_table(:foos) - end end class TimestampsWithoutTransactionTest < ActiveRecord::TestCase @@ -475,4 +464,15 @@ class TimestampsWithoutTransactionTest < ActiveRecord::TestCase assert_nil post.updated_at end end + + def test_index_is_created_for_both_timestamps + ActiveRecord::Base.connection.create_table(:foos, force: true) do |t| + t.timestamps null: true, index: true + end + + indexes = ActiveRecord::Base.connection.indexes("foos") + assert_equal ["created_at", "updated_at"], indexes.flat_map(&:columns).sort + ensure + ActiveRecord::Base.connection.drop_table(:foos) + end end diff --git a/activestorage/.codeclimate.yml b/activestorage/.codeclimate.yml deleted file mode 100644 index 18f3dbb7b5..0000000000 --- a/activestorage/.codeclimate.yml +++ /dev/null @@ -1,7 +0,0 @@ -engines: - rubocop: - enabled: true - -ratings: - paths: - - "**.rb" diff --git a/activestorage/app/controllers/active_storage/disk_controller.rb b/activestorage/app/controllers/active_storage/disk_controller.rb index b10d4e2cac..41e6d61bff 100644 --- a/activestorage/app/controllers/active_storage/disk_controller.rb +++ b/activestorage/app/controllers/active_storage/disk_controller.rb @@ -8,7 +8,7 @@ class ActiveStorage::DiskController < ActionController::Base def show if key = decode_verified_key send_data disk_service.download(key), - filename: params[:filename], disposition: disposition_param, content_type: params[:content_type] + disposition: disposition_param, content_type: params[:content_type] else head :not_found end @@ -39,7 +39,7 @@ class ActiveStorage::DiskController < ActionController::Base end def disposition_param - params[:disposition].presence_in(%w( inline attachment )) || "inline" + params[:disposition].presence || "inline" end diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb index 9f2ed1e5ac..2e627ef118 100644 --- a/activestorage/app/models/active_storage/blob.rb +++ b/activestorage/app/models/active_storage/blob.rb @@ -127,7 +127,7 @@ class ActiveStorage::Blob < ActiveRecord::Base # Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And # it allows permanent URLs that redirect to the +service_url+ to be cached in the view. def service_url(expires_in: 5.minutes, disposition: :inline) - service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type + service.url key, expires_in: expires_in, disposition: "#{disposition}; filename=\"#{filename}\"", filename: filename, content_type: content_type end # Returns a URL that can be used to directly upload a file for this blob on the service. This URL is intended to be diff --git a/activestorage/app/models/active_storage/filename.rb b/activestorage/app/models/active_storage/filename.rb index 6a9889addf..df21078718 100644 --- a/activestorage/app/models/active_storage/filename.rb +++ b/activestorage/app/models/active_storage/filename.rb @@ -9,21 +9,23 @@ class ActiveStorage::Filename @filename = filename end - # Filename.new("racecar.jpg").extname # => ".jpg" - def extname - File.extname(@filename) + # Filename.new("racecar.jpg").base # => "racecar" + def base + File.basename @filename, extension_with_delimiter end - # Filename.new("racecar.jpg").extension # => "jpg" - def extension - extname.from(1) + # Filename.new("racecar.jpg").extension_with_delimiter # => ".jpg" + def extension_with_delimiter + File.extname @filename end - # Filename.new("racecar.jpg").base # => "racecar" - def base - File.basename(@filename, extname) + # Filename.new("racecar.jpg").extension_without_delimiter # => "jpg" + def extension_without_delimiter + extension_with_delimiter.from(1).to_s end + alias_method :extension, :extension_without_delimiter + # Filename.new("foo:bar.jpg").sanitized # => "foo-bar.jpg" # Filename.new("foo/bar.jpg").sanitized # => "foo-bar.jpg" # diff --git a/activestorage/bin/test b/activestorage/bin/test new file mode 100755 index 0000000000..c53377cc97 --- /dev/null +++ b/activestorage/bin/test @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +COMPONENT_ROOT = File.expand_path("..", __dir__) +require_relative "../../tools/test" diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb index d0ee2273c5..895cc9c2f1 100644 --- a/activestorage/lib/active_storage/service/azure_storage_service.rb +++ b/activestorage/lib/active_storage/service/azure_storage_service.rb @@ -59,12 +59,16 @@ module ActiveStorage end end - def url(key, expires_in:, disposition:, filename:, content_type:) + def url(key, expires_in:, filename:, disposition:, content_type:) instrument :url, key do |payload| base_url = url_for(key) - generated_url = signer.signed_uri(URI(base_url), false, permissions: "r", - expiry: format_expiry(expires_in), content_type: content_type, - content_disposition: "#{disposition}; filename=\"#{filename}\"").to_s + generated_url = signer.signed_uri( + URI(base_url), false, + permissions: "r", + expiry: format_expiry(expires_in), + content_disposition: disposition, + content_type: content_type + ).to_s payload[:url] = generated_url diff --git a/activestorage/lib/active_storage/service/disk_service.rb b/activestorage/lib/active_storage/service/disk_service.rb index 1d121dcb6c..5aa8d74a5a 100644 --- a/activestorage/lib/active_storage/service/disk_service.rb +++ b/activestorage/lib/active_storage/service/disk_service.rb @@ -56,7 +56,7 @@ module ActiveStorage end end - def url(key, expires_in:, disposition:, filename:, content_type:) + def url(key, expires_in:, filename:, disposition:, content_type:) instrument :url, key do |payload| verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in, purpose: :blob_key) @@ -64,7 +64,7 @@ module ActiveStorage if defined?(Rails.application) Rails.application.routes.url_helpers.rails_disk_service_path \ verified_key_with_expiration, - disposition: disposition, filename: filename, content_type: content_type + filename: filename, disposition: disposition, content_type: content_type else "/rails/active_storage/disk/#{verified_key_with_expiration}/#{filename}?disposition=#{disposition}&content_type=#{content_type}" end diff --git a/activestorage/lib/active_storage/service/gcs_service.rb b/activestorage/lib/active_storage/service/gcs_service.rb index e656f2c69f..ebf24a36d7 100644 --- a/activestorage/lib/active_storage/service/gcs_service.rb +++ b/activestorage/lib/active_storage/service/gcs_service.rb @@ -47,10 +47,10 @@ module ActiveStorage end end - def url(key, expires_in:, disposition:, filename:, content_type:) + def url(key, expires_in:, filename:, content_type:, disposition:) instrument :url, key do |payload| generated_url = file_for(key).signed_url expires: expires_in, query: { - "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"", + "response-content-disposition" => disposition, "response-content-type" => content_type } diff --git a/activestorage/lib/active_storage/service/s3_service.rb b/activestorage/lib/active_storage/service/s3_service.rb index bef2ecbea9..e5d1e56e05 100644 --- a/activestorage/lib/active_storage/service/s3_service.rb +++ b/activestorage/lib/active_storage/service/s3_service.rb @@ -52,10 +52,10 @@ module ActiveStorage end end - def url(key, expires_in:, disposition:, filename:, content_type:) + def url(key, expires_in:, filename:, disposition:, content_type:) instrument :url, key do |payload| generated_url = object_for(key).presigned_url :get, expires_in: expires_in, - response_content_disposition: "#{disposition}; filename=\"#{filename}\"", + response_content_disposition: disposition, response_content_type: content_type payload[:url] = generated_url diff --git a/activestorage/test/controllers/disk_controller_test.rb b/activestorage/test/controllers/disk_controller_test.rb index 53a086f214..eacb9bfc11 100644 --- a/activestorage/test/controllers/disk_controller_test.rb +++ b/activestorage/test/controllers/disk_controller_test.rb @@ -8,7 +8,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest blob = create_blob get blob.service_url - assert_equal "inline; filename=\"#{blob.filename.base}\"", @response.headers["Content-Disposition"] + assert_equal "inline; filename=\"hello.txt\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end @@ -16,7 +16,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest blob = create_blob get blob.service_url(disposition: :attachment) - assert_equal "attachment; filename=\"#{blob.filename.base}\"", @response.headers["Content-Disposition"] + assert_equal "attachment; filename=\"hello.txt\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end diff --git a/activestorage/test/models/blob_test.rb b/activestorage/test/models/blob_test.rb index 1c50d18994..03bf72c0de 100644 --- a/activestorage/test/models/blob_test.rb +++ b/activestorage/test/models/blob_test.rb @@ -41,9 +41,16 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase end end + test "purge removes from external service" do + blob = create_blob + + blob.purge + assert_not ActiveStorage::Blob.service.exist?(blob.key) + end + private def expected_url_for(blob, disposition: :inline) - query_string = { content_type: blob.content_type, disposition: disposition }.to_param + query_string = { content_type: blob.content_type, disposition: "#{disposition}; filename=\"#{blob.filename}\"" }.to_param "/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key)}/#{blob.filename}?#{query_string}" end end diff --git a/activestorage/test/models/filename_test.rb b/activestorage/test/models/filename_test.rb index f1e4a467ba..88405e41c0 100644 --- a/activestorage/test/models/filename_test.rb +++ b/activestorage/test/models/filename_test.rb @@ -3,6 +3,24 @@ require "test_helper" class ActiveStorage::FilenameTest < ActiveSupport::TestCase + test "base" do + assert_equal "racecar", ActiveStorage::Filename.new("racecar.jpg").base + assert_equal "race.car", ActiveStorage::Filename.new("race.car.jpg").base + assert_equal "racecar", ActiveStorage::Filename.new("racecar").base + end + + test "extension with delimiter" do + assert_equal ".jpg", ActiveStorage::Filename.new("racecar.jpg").extension_with_delimiter + assert_equal ".jpg", ActiveStorage::Filename.new("race.car.jpg").extension_with_delimiter + assert_equal "", ActiveStorage::Filename.new("racecar").extension_with_delimiter + end + + test "extension without delimiter" do + assert_equal "jpg", ActiveStorage::Filename.new("racecar.jpg").extension_without_delimiter + assert_equal "jpg", ActiveStorage::Filename.new("race.car.jpg").extension_without_delimiter + assert_equal "", ActiveStorage::Filename.new("racecar").extension_without_delimiter + end + test "sanitize" do "%$|:;/\t\r\n\\".each_char do |character| filename = ActiveStorage::Filename.new("foo#{character}bar.pdf") diff --git a/activestorage/test/service/azure_storage_service_test.rb b/activestorage/test/service/azure_storage_service_test.rb index dac154cf11..4729bdfbc5 100644 --- a/activestorage/test/service/azure_storage_service_test.rb +++ b/activestorage/test/service/azure_storage_service_test.rb @@ -11,9 +11,9 @@ if SERVICE_CONFIGURATIONS[:azure] test "signed URL generation" do url = @service.url(FIXTURE_KEY, expires_in: 5.minutes, - disposition: :inline, filename: "avatar.png", content_type: "image/png") + disposition: "inline; filename=\"avatar.png\"", filename: "avatar.png", content_type: "image/png") - assert_match(/(\S+)&rsct=image%2Fpng&rscd=inline%3B\+filename%3D%22avatar.png/, url) + assert_match(/(\S+)&rscd=inline%3B\+filename%3D%22avatar.png%22&rsct=image%2Fpng/, url) assert_match SERVICE_CONFIGURATIONS[:azure][:container], url end end diff --git a/activestorage/test/service/disk_service_test.rb b/activestorage/test/service/disk_service_test.rb index 835a3a2971..f57e44536a 100644 --- a/activestorage/test/service/disk_service_test.rb +++ b/activestorage/test/service/disk_service_test.rb @@ -9,6 +9,6 @@ class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase test "url generation" do assert_match(/rails\/active_storage\/disk\/.*\/avatar\.png\?.+disposition=inline/, - @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png", content_type: "image/png")) + @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: "inline; filename=\"avatar.png\"", filename: "avatar.png", content_type: "image/png")) end end diff --git a/activestorage/test/service/gcs_service_test.rb b/activestorage/test/service/gcs_service_test.rb index 5f53f61baf..f664cee90b 100644 --- a/activestorage/test/service/gcs_service_test.rb +++ b/activestorage/test/service/gcs_service_test.rb @@ -37,7 +37,7 @@ if SERVICE_CONFIGURATIONS[:gcs] "&response-content-disposition=inline%3B+filename%3D%22test.txt%22" + "&response-content-type=text%2Fplain" - assert_equal url, @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt", content_type: "text/plain") + assert_equal url, @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: "inline; filename=\"test.txt\"", filename: "test.txt", content_type: "text/plain") end end end diff --git a/activestorage/test/service/s3_service_test.rb b/activestorage/test/service/s3_service_test.rb index 98f520741d..c07d6396b1 100644 --- a/activestorage/test/service/s3_service_test.rb +++ b/activestorage/test/service/s3_service_test.rb @@ -33,7 +33,7 @@ if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].pr test "signed URL generation" do url = @service.url(FIXTURE_KEY, expires_in: 5.minutes, - disposition: :inline, filename: "avatar.png", content_type: "image/png") + disposition: "inline; filename=\"avatar.png\"", filename: "avatar.png", content_type: "image/png") assert_match(/s3\.(\S+)?amazonaws.com.*response-content-disposition=inline.*avatar\.png.*response-content-type=image%2Fpng/, url) assert_match SERVICE_CONFIGURATIONS[:s3][:bucket], url diff --git a/activestorage/test/test_helper.rb b/activestorage/test/test_helper.rb index 0bb3a925b8..2a969fa326 100644 --- a/activestorage/test/test_helper.rb +++ b/activestorage/test/test_helper.rb @@ -32,7 +32,7 @@ end require "tmpdir" ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: Dir.mktmpdir("active_storage_tests")) -ActiveStorage::Service.logger = ActiveSupport::Logger.new(STDOUT) +ActiveStorage::Service.logger = ActiveSupport::Logger.new(nil) ActiveStorage.verifier = ActiveSupport::MessageVerifier.new("Testing") diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb index f92a2f1c2a..9bc50b7bc6 100644 --- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb @@ -21,20 +21,14 @@ class Hash # Same as +deep_merge+, but modifies +self+. def deep_merge!(other_hash, &block) - other_hash.each_pair do |current_key, other_value| - this_value = self[current_key] - - self[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash) - this_value.deep_merge(other_value, &block) + merge!(other_hash) do |key, this_val, other_val| + if this_val.is_a?(Hash) && other_val.is_a?(Hash) + this_val.deep_merge(other_val, &block) + elsif block_given? + block.call(key, this_val, other_val) else - if block_given? && key?(current_key) - block.call(current_key, this_value, other_value) - else - other_value - end + other_val end end - - self end end diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb index c124416595..dc8080c469 100644 --- a/activesupport/lib/active_support/lazy_load_hooks.rb +++ b/activesupport/lib/active_support/lazy_load_hooks.rb @@ -40,7 +40,7 @@ module ActiveSupport # * <tt>:run_once</tt> - Given +block+ will run only once. def on_load(name, options = {}, &block) @loaded[name].each do |base| - execute_hook(base, options, block) + execute_hook(name, base, options, block) end @load_hooks[name] << [block, options] @@ -49,7 +49,7 @@ module ActiveSupport def run_load_hooks(name, base = Object) @loaded[name] << base @load_hooks[name].each do |hook, options| - execute_hook(base, options, hook) + execute_hook(name, base, options, hook) end end @@ -63,7 +63,7 @@ module ActiveSupport end end - def execute_hook(base, options, block) + def execute_hook(name, base, options, block) with_execution_control(name, block, options[:run_once]) do if options[:yield] block.call(base) diff --git a/activesupport/test/lazy_load_hooks_test.rb b/activesupport/test/lazy_load_hooks_test.rb index c161005100..721d44d0c1 100644 --- a/activesupport/test/lazy_load_hooks_test.rb +++ b/activesupport/test/lazy_load_hooks_test.rb @@ -22,14 +22,19 @@ class LazyLoadHooksTest < ActiveSupport::TestCase def test_basic_hook_with_two_registrations_only_once i = 0 - ActiveSupport.on_load(:basic_hook_with_two_once, run_once: true) do + block = proc { i += incr } + ActiveSupport.on_load(:basic_hook_with_two_once, run_once: true, &block) + ActiveSupport.on_load(:basic_hook_with_two_once) do i += incr end - assert_equal 0, i - ActiveSupport.run_load_hooks(:basic_hook_with_two_once, FakeContext.new(2)) + + ActiveSupport.on_load(:different_hook, run_once: true, &block) + ActiveSupport.run_load_hooks(:different_hook, FakeContext.new(2)) assert_equal 2, i + ActiveSupport.run_load_hooks(:basic_hook_with_two_once, FakeContext.new(2)) + assert_equal 6, i ActiveSupport.run_load_hooks(:basic_hook_with_two_once, FakeContext.new(5)) - assert_equal 2, i + assert_equal 11, i end def test_hook_registered_after_run diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index 6976848e95..3f5a3c7ade 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -10,7 +10,7 @@ Rails 2.3 delivers a variety of new and improved features, including pervasive R Application Architecture ------------------------ -There are two major changes in the architecture of Rails applications: complete integration of the [Rack](http://rack.github.io/) modular web server interface, and renewed support for Rails Engines. +There are two major changes in the architecture of Rails applications: complete integration of the [Rack](https://rack.github.io/) modular web server interface, and renewed support for Rails Engines. ### Rack Integration diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 5f4be07351..6d53e1c2b4 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -55,7 +55,7 @@ information. ### API Applications Rails can now be used to create slimmed down API only applications. -This is useful for creating and serving APIs similar to [Twitter](https://dev.twitter.com) or [GitHub](http://developer.github.com) API, +This is useful for creating and serving APIs similar to [Twitter](https://dev.twitter.com) or [GitHub](https://developer.github.com) API, that can be used to serve public facing, as well as, for custom applications. You can generate a new api Rails app using: @@ -74,11 +74,11 @@ This will do three main things: Controller modules that provide functionalities primarily used by browser applications. - Configure the generators to skip generating views, helpers and assets when - you generate a new resource. + you generate a new resource. -The application provides a base for APIs, +The application provides a base for APIs, that can then be [configured to pull in functionality](api_app.html) as suitable for the application's needs. - + See the [Using Rails for API-only Applications](api_app.html) guide for more information. diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index a086363cf1..e9157f3db1 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -892,7 +892,7 @@ Conditional Validation Sometimes it will make sense to validate an object only when a given predicate is satisfied. You can do that by using the `:if` and `:unless` options, which -can take a symbol, a string, a `Proc` or an `Array`. You may use the `:if` +can take a symbol, a `Proc` or an `Array`. You may use the `:if` option when you want to specify when the validation **should** happen. If you want to specify when the validation **should not** happen, then you may use the `:unless` option. diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 64200ec242..5ec79de8a0 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -18,7 +18,7 @@ What is an API Application? Traditionally, when people said that they used Rails as an "API", they meant providing a programmatically accessible API alongside their web application. -For example, GitHub provides [an API](http://developer.github.com) that you +For example, GitHub provides [an API](https://developer.github.com) that you can use from your own custom clients. With the advent of client-side frameworks, more developers are using Rails to @@ -94,7 +94,7 @@ Handled at the Action Pack layer: means not having to spend time thinking about how to model your API in terms of HTTP. - URL Generation: The flip side of routing is URL generation. A good API based - on HTTP includes URLs (see [the GitHub Gist API](http://developer.github.com/v3/gists/) + on HTTP includes URLs (see [the GitHub Gist API](https://developer.github.com/v3/gists/) for an example). - Header and Redirection Responses: `head :no_content` and `redirect_to user_url(current_user)` come in handy. Sure, you could manually diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 383e972f74..f148cef24f 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -1069,7 +1069,7 @@ Customizing the Pipeline ### CSS Compression One of the options for compressing CSS is YUI. The [YUI CSS -compressor](http://yui.github.io/yuicompressor/css.html) provides +compressor](https://yui.github.io/yuicompressor/css.html) provides minification. The following line enables YUI compression, and requires the `yui-compressor` diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md index 05743ee4ce..c62194faf4 100644 --- a/guides/source/autoloading_and_reloading_constants.md +++ b/guides/source/autoloading_and_reloading_constants.md @@ -475,12 +475,21 @@ it is (edited): ``` $ bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths' .../app/assets +.../app/channels .../app/controllers +.../app/controllers/concerns .../app/helpers +.../app/jobs .../app/mailers .../app/models -.../app/controllers/concerns .../app/models/concerns +.../activestorage/app/assets +.../activestorage/app/controllers +.../activestorage/app/javascript +.../activestorage/app/jobs +.../activestorage/app/models +.../actioncable/app/assets +.../actionview/app/assets .../test/mailers/previews ``` diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 8ae01286e4..2cd8e02a77 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -645,13 +645,16 @@ $ cat config/database.yml # Configure Using Gemfile # gem 'pg' # -development: +default: &default adapter: postgresql encoding: unicode + # For details on connection pooling, see Rails configuration guide + # http://guides.rubyonrails.org/configuring.html#database-pooling + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + +development: + <<: *default database: gitapp_development - pool: 5 - username: gitapp - password: ... ... ``` diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 2f2962a3e6..7424818757 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -13,7 +13,7 @@ After reading this guide, you will know: * How to contribute to the Ruby on Rails documentation. * How to contribute to the Ruby on Rails code. -Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. +Ruby on Rails is not "someone else's framework." Over the years, thousands of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. As mentioned in [Rails' README](https://github.com/rails/rails/blob/master/README.md), 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/). @@ -92,7 +92,7 @@ For starters, it helps just to verify bug reports. Can you reproduce the reporte If an issue is very vague, can you help narrow it down to something more specific? Maybe you can provide additional information to help reproduce a bug, or help by eliminating needless steps that aren't required to demonstrate the problem. -If you find a bug report without a test, it's very useful to contribute a failing test. This is also a great way to get started exploring the source code: looking at the existing test files will teach you how to write more tests. New tests are best contributed in the form of a patch, as explained later on in the "Contributing to the Rails Code" section. +If you find a bug report without a test, it's very useful to contribute a failing test. This is also a great way to get started exploring the source code: looking at the existing test files will teach you how to write more tests. New tests are best contributed in the form of a patch, as explained later on in the "[Contributing to the Rails Code](#contributing-to-the-rails-code)" section. Anything you can do to make bug reports more succinct or easier to reproduce helps folks trying to write code to fix those bugs - whether you end up writing the code yourself or not. @@ -164,11 +164,11 @@ NOTE: The instructions are for Rails > 4. The Redcarpet Gem doesn't work with JR Translation efforts we know about (various versions): * **Italian**: [https://github.com/rixlabs/docrails](https://github.com/rixlabs/docrails) -* **Spanish**: [http://wiki.github.com/gramos/docrails](http://wiki.github.com/gramos/docrails) -* **Polish**: [https://github.com/apohllo/docrails/tree/master](https://github.com/apohllo/docrails/tree/master) +* **Spanish**: [https://github.com/gramos/docrails/wiki](https://github.com/gramos/docrails/wiki) +* **Polish**: [https://github.com/apohllo/docrails](https://github.com/apohllo/docrails) * **French** : [https://github.com/railsfrance/docrails](https://github.com/railsfrance/docrails) * **Czech** : [https://github.com/rubyonrails-cz/docrails/tree/czech](https://github.com/rubyonrails-cz/docrails/tree/czech) -* **Turkish** : [https://github.com/ujk/docrails/tree/master](https://github.com/ujk/docrails/tree/master) +* **Turkish** : [https://github.com/ujk/docrails](https://github.com/ujk/docrails) * **Korean** : [https://github.com/rorlakr/rails-guides](https://github.com/rorlakr/rails-guides) * **Simplified Chinese** : [https://github.com/ruby-china/guides](https://github.com/ruby-china/guides) * **Traditional Chinese** : [https://github.com/docrails-tw/guides](https://github.com/docrails-tw/guides) @@ -184,7 +184,7 @@ To move on from submitting bugs to helping resolve existing issues or contributi #### The Easy Way -The easiest and recommended way to get a development environment ready to hack is to use the [Rails development box](https://github.com/rails/rails-dev-box). +The easiest and recommended way to get a development environment ready to hack is to use the [rails-dev-box](https://github.com/rails/rails-dev-box). #### The Hard Way @@ -324,7 +324,7 @@ file. #### Testing Active Record -First, create the databases you'll need. You can find a list of the required +First, create the databases you'll need. You can find a list of the required table names, usernames, and passwords in `activerecord/test/config.example.yml`. For MySQL and PostgreSQL, running the SQL statements `create database @@ -480,7 +480,7 @@ Navigate to the Rails [GitHub repository](https://github.com/rails/rails) and pr Add the new remote to your local repository on your local machine: ```bash -$ git remote add mine https://github.com:<your user name>/rails.git +$ git remote add mine https://github.com/<your user name>/rails.git ``` Push to your remote: @@ -554,7 +554,7 @@ is the open source life. If it's been over a week, and you haven't heard anything, you might want to try and nudge things along. You can use the [rubyonrails-core mailing -list](http://groups.google.com/group/rubyonrails-core/) for this. You can also +list](https://groups.google.com/forum/#!forum/rubyonrails-core) for this. You can also leave another comment on the pull request. While you're waiting for feedback on your pull request, open up a few other diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 7ea3646c30..99bc7c5fb5 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -186,21 +186,17 @@ end Here's an example of the log generated when this controller action is executed: ``` -Processing ArticlesController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST] - Session ID: BAh7BzoMY3NyZl9pZCIlMDY5MWU1M2I1ZDRjODBlMzkyMWI1OTg2NWQyNzViZjYiCmZsYXNoSUM6J0FjdGl -vbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7AAY6CkB1c2VkewA=--b18cd92fba90eacf8137e5f6b3b06c4d724596a4 - Parameters: {"commit"=>"Create", "article"=>{"title"=>"Debugging Rails", - "body"=>"I'm learning how to print in logs!!!", "published"=>"0"}, - "authenticity_token"=>"2059c1286e93402e389127b1153204e0d1e275dd", "action"=>"create", "controller"=>"articles"} -New article: {"updated_at"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", - "published"=>false, "created_at"=>nil} +Started POST "/articles" for 127.0.0.1 at 2017-08-20 20:53:10 +0900 +Processing by ArticlesController#create as HTML + Parameters: {"utf8"=>"✓", "authenticity_token"=>"xhuIbSBFytHCE1agHgvrlKnSVIOGD6jltW2tO+P6a/ACjQ3igjpV4OdbsZjIhC98QizWH9YdKokrqxBCJrtoqQ==", "article"=>{"title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>"0"}, "commit"=>"Create Article"} +New article: {"id"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>false, "created_at"=>nil, "updated_at"=>nil} Article should be valid: true - Article Create (0.000443) INSERT INTO "articles" ("updated_at", "title", "body", "published", - "created_at") VALUES('2008-09-08 14:52:54', 'Debugging Rails', - 'I''m learning how to print in logs!!!', 'f', '2008-09-08 14:52:54') + (0.1ms) BEGIN + SQL (0.4ms) INSERT INTO "articles" ("title", "body", "published", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["title", "Debugging Rails"], ["body", "I'm learning how to print in logs!!!"], ["published", "f"], ["created_at", "2017-08-20 11:53:10.010435"], ["updated_at", "2017-08-20 11:53:10.010435"]] + (0.3ms) COMMIT The article was saved and now the user is going to be redirected... -Redirected to # Article:0x20af760> -Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/articles] +Redirected to http://localhost:3000/articles/1 +Completed 302 Found in 4ms (ActiveRecord: 0.8ms) ``` Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels to avoid filling your production logs with useless trivia. diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index c57efd6362..5a9729bb0f 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -21,24 +21,25 @@ The easiest and recommended way to get a development environment ready to hack i The Hard Way ------------ -In case you can't use the Rails development box, see section below, these are the steps to manually build a development box for Ruby on Rails core development. +In case you can't use the Rails development box, see the steps below to manually +build a development box for Ruby on Rails core development. ### Install Git -Ruby on Rails uses Git for source code control. The [Git homepage](http://git-scm.com/) has installation instructions. There are a variety of resources on the net that will help you get familiar with Git: +Ruby on Rails uses Git for source code control. The [Git homepage](https://git-scm.com/) has installation instructions. There are a variety of resources on the net that will help you get familiar with Git: -* [Try Git course](http://try.github.io/) is an interactive course that will teach you the basics. -* The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git. -* [Everyday Git](http://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by. -* [GitHub](http://help.github.com) offers links to a variety of Git resources. -* [Pro Git](http://git-scm.com/book) is an entire book about Git with a Creative Commons license. +* [Try Git course](https://try.github.io/) is an interactive course that will teach you the basics. +* The [official Documentation](https://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git. +* [Everyday Git](https://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by. +* [GitHub](https://help.github.com/) offers links to a variety of Git resources. +* [Pro Git](https://git-scm.com/book) is an entire book about Git with a Creative Commons license. ### Clone the Ruby on Rails Repository Navigate to the folder where you want the Ruby on Rails source code (it will create its own `rails` subdirectory) and run: ```bash -$ git clone git://github.com/rails/rails.git +$ git clone https://github.com/rails/rails.git $ cd rails ``` @@ -62,7 +63,7 @@ $ sudo apt-get install sqlite3 libsqlite3-dev If you are on Fedora or CentOS, you're done with ```bash -$ sudo yum install libsqlite3x libsqlite3x-devel +$ sudo yum install libsqlite3x libsqlite3x-devel ``` If you are on Arch Linux, you will need to run: @@ -96,7 +97,7 @@ This command will install all dependencies except the MySQL and PostgreSQL Ruby NOTE: If you would like to run the tests that use memcached, you need to ensure that you have it installed and running. -You can use [Homebrew](http://brew.sh/) to install memcached on OS X: +You can use [Homebrew](https://brew.sh/) to install memcached on macOS: ```bash $ brew install memcached @@ -181,7 +182,7 @@ The Active Record test suite requires a custom config file: `activerecord/test/c To be able to run the suite for MySQL and PostgreSQL we need their gems. Install first the servers, their client libraries, and their development files. -On OS X, you can run: +On macOS, you can run: ```bash $ brew install mysql @@ -190,7 +191,7 @@ $ brew install postgresql Follow the instructions given by Homebrew to start these. -In Ubuntu just run: +On Ubuntu, just run: ```bash $ sudo apt-get install mysql-server libmysqlclient-dev @@ -260,34 +261,34 @@ with your development account, on Linux or BSD, you just have to run: $ sudo -u postgres createuser --superuser $USER ``` -and for OS X: +and for macOS: ```bash $ createuser --superuser $USER ``` -Then you need to create the test databases with +Then, you need to create the test databases with: ```bash $ cd activerecord $ bundle exec rake db:postgresql:build ``` -It is possible to build databases for both PostgreSQL and MySQL with +It is possible to build databases for both PostgreSQL and MySQL with: ```bash $ cd activerecord $ bundle exec rake db:create ``` -You can cleanup the databases using +You can cleanup the databases using: ```bash $ cd activerecord $ bundle exec rake db:drop ``` -NOTE: Using the rake task to create the test databases ensures they have the correct character set and collation. +NOTE: Using the Rake task to create the test databases ensures they have the correct character set and collation. NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator". @@ -299,11 +300,11 @@ Action Cable uses Redis as its default subscriptions adapter ([read more](action #### Install Redis From Source -Redis' documentation discourage installations with package managers as those are usually outdated. Installing from source and bringing the server up is straight forward and well documented on [Redis' documentation](http://redis.io/download#installation). +Redis' documentation discourage installations with package managers as those are usually outdated. Installing from source and bringing the server up is straight forward and well documented on [Redis' documentation](https://redis.io/download#installation). #### Install Redis From Package Manager -On OS X, you can run: +On macOS, you can run: ```bash $ brew install redis @@ -311,7 +312,7 @@ $ brew install redis Follow the instructions given by Homebrew to start these. -In Ubuntu just run: +On Ubuntu, just run: ```bash $ sudo apt-get install redis-server @@ -323,7 +324,7 @@ On Fedora or CentOS (requires EPEL enabled), just run: $ sudo yum install redis ``` -If you are running Arch Linux just run: +If you are running Arch Linux, just run: ```bash $ sudo pacman -S redis @@ -335,3 +336,43 @@ FreeBSD users will have to run the following: ```bash # portmaster databases/redis ``` + +### Active Storage Setup + +When working on Active Storage, it is important to note that you need to +install its JavaScript dependencies while working on that section of the +codebase. In order to install these dependencies, it is necessary to +have Yarn, a Node.js package manager, available on your system. A +prerequisite for installing this package manager is that +[Node.js](https://nodejs.org) is installed. + + +On macOS, you can run: + +```bash +brew install yarn +``` + +On Ubuntu, you can run: + +```bash +curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list + +sudo apt-get update && sudo apt-get install yarn +``` + +On Fedora or CentOS, just run: + +```bash +sudo wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo + +sudo yum install yarn +``` + +Finally, after installing Yarn, you will need to run the following +command inside of the `activestorage` directory to install the dependencies: + +```bash +yarn install +``` diff --git a/guides/source/generators.md b/guides/source/generators.md index 37af7a1310..b7b8262e4a 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -90,13 +90,15 @@ $ bin/rails generate generator initializer create lib/generators/initializer/initializer_generator.rb create lib/generators/initializer/USAGE create lib/generators/initializer/templates + invoke test_unit + create test/lib/generators/initializer_generator_test.rb ``` This is the generator just created: ```ruby class InitializerGenerator < Rails::Generators::NamedBase - source_root File.expand_path("templates", __dir__) + source_root File.expand_path('templates', __dir__) end ``` @@ -122,7 +124,7 @@ And now let's change the generator to copy this template when invoked: ```ruby class InitializerGenerator < Rails::Generators::NamedBase - source_root File.expand_path("templates", __dir__) + source_root File.expand_path('templates', __dir__) def copy_initializer_file copy_file "initializer.rb", "config/initializers/#{file_name}.rb" @@ -241,6 +243,8 @@ $ bin/rails generate generator rails/my_helper create lib/generators/rails/my_helper/my_helper_generator.rb create lib/generators/rails/my_helper/USAGE create lib/generators/rails/my_helper/templates + invoke test_unit + create test/lib/generators/rails/my_helper_generator_test.rb ``` After that, we can delete both the `templates` directory and the `source_root` @@ -510,13 +514,13 @@ Available options are: Any additional options passed to this method are put on the end of the line: ```ruby -gem "devise", git: "git://github.com/plataformatec/devise", branch: "master" +gem "devise", git: "https://github.com/plataformatec/devise.git", branch: "master" ``` The above code will put the following line into `Gemfile`: ```ruby -gem "devise", git: "git://github.com/plataformatec/devise", branch: "master" +gem "devise", git: "https://github.com/plataformatec/devise.git", branch: "master" ``` ### `gem_group` diff --git a/guides/source/initialization.md b/guides/source/initialization.md index ccad10f07d..0cfabe2a66 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -99,13 +99,15 @@ configure the load path for your Gemfile's dependencies. A standard Rails application depends on several gems, specifically: +* actioncable * actionmailer * actionpack * actionview +* activejob * activemodel * activerecord +* activestorage * activesupport -* activejob * arel * builder * bundler @@ -131,7 +133,7 @@ Once `config/boot.rb` has finished, the next file that is required is `ARGV` array simply contains `server` which will be passed over: ```ruby -require "rails/command" +require_relative "command" aliases = { "g" => "generate", @@ -170,7 +172,7 @@ module Rails::Command namespace = namespace.to_s namespace = "help" if namespace.blank? || HELP_MAPPINGS.include?(namespace) namespace = "version" if %w( -v --version ).include? namespace - + if command = find_by_namespace(namespace) command.perform(namespace, args, config) else @@ -189,7 +191,7 @@ module Rails class ServerCommand < Base # :nodoc: def perform set_application_directory! - + Rails::Server.new.tap do |server| # Require application after server sets environment to propagate # the --environment option. @@ -311,7 +313,7 @@ def parse!(args) args, options = args.dup, {} option_parser(options).parse! args - + options[:log_stdout] = options[:daemonize].blank? && (options[:environment] || Rails.env) == "development" options[:server] = args.shift options @@ -366,11 +368,11 @@ private def log_to_stdout wrapped_app # touch the app so the logger is set up - + console = ActiveSupport::Logger.new(STDOUT) console.formatter = Rails.logger.formatter console.level = Rails.logger.level - + unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT) Rails.logger.extend(ActiveSupport::Logger.broadcast(console)) end @@ -537,6 +539,7 @@ require "rails" action_mailer/railtie active_job/railtie action_cable/engine + active_storage/engine rails/test_unit/railtie sprockets/railtie ).each do |railtie| diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index cef8450ee4..aa1476ecc0 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -125,7 +125,7 @@ use ActionDispatch::Flash use Rack::Head use Rack::ConditionalGet use Rack::ETag -run MyApp.application.routes +run MyApp::Application.routes ``` The default middlewares shown here (and some others) are each summarized in the [Internal Middlewares](#internal-middleware-stack) section, below. @@ -302,7 +302,7 @@ Resources ### Learning Rack -* [Official Rack Website](http://rack.github.io) +* [Official Rack Website](https://rack.github.io) * [Introducing Rack](http://chneukirchen.org/blog/archive/2007/02/introducing-rack.html) ### Understanding Middlewares diff --git a/guides/source/security.md b/guides/source/security.md index 56969c7aea..7736a4b224 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -1025,7 +1025,7 @@ Here is a list of common headers: * **X-Content-Type-Options:** _'nosniff' in Rails by default_ - stops the browser from guessing the MIME type of a file. * **X-Content-Security-Policy:** [A powerful mechanism for controlling which sites certain content types can be loaded from](http://w3c.github.io/webappsec/specs/content-security-policy/csp-specification.dev.html) * **Access-Control-Allow-Origin:** Used to control which sites are allowed to bypass same origin policies and send cross-origin requests. -* **Strict-Transport-Security:** [Used to control if the browser is allowed to only access a site over a secure connection](http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) +* **Strict-Transport-Security:** [Used to control if the browser is allowed to only access a site over a secure connection](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) Environmental Security ---------------------- @@ -1060,7 +1060,7 @@ Additional Resources The security landscape shifts and it is important to keep up to date, because missing a new vulnerability can be catastrophic. You can find additional resources about (Rails) security here: -* Subscribe to the Rails security [mailing list](http://groups.google.com/group/rubyonrails-security). -* [Brakeman - Rails Security Scanner](http://brakemanscanner.org/) - To perform static security analysis for Rails applications. +* Subscribe to the Rails security [mailing list](https://groups.google.com/forum/#!forum/rubyonrails-security). +* [Brakeman - Rails Security Scanner](https://brakemanscanner.org/) - To perform static security analysis for Rails applications. * [Keep up to date on the other application layers](http://secunia.com/) (they have a weekly newsletter, too). * A [good security blog](https://www.owasp.org) including the [Cross-Site scripting Cheat Sheet](https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet). diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 49a0584d51..651b86275a 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -397,6 +397,15 @@ When using Ruby 2.4, you can preserve the timezone of the receiver when calling ActiveSupport.to_time_preserves_timezone = false +### Changes with JSON/JSONB serialization + +In Rails 5.0, how JSON/JSONB attributes are serialized and deserialized changed. Now, if +you set a column equal to a `String`, Active Record will no longer turn that string +into a `Hash`, and will instead only return the string. This is not limited to code +interacting with models, but also affects `:default` column settings in `db/schema.rb`. +It is recommended that you do not set columns equal to a `String`, but pass a `Hash` +instead, which will be converted to and from a JSON string automatically. + Upgrading from Rails 4.1 to Rails 4.2 ------------------------------------- diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index 88e5698714..785265d766 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -126,6 +126,7 @@ module Rails desc: "Specifies the PID file." class_option "dev-caching", aliases: "-C", type: :boolean, default: nil, desc: "Specifies whether to perform caching in development." + class_option "restart", type: :boolean, default: nil, hide: true def initialize(args = [], local_options = {}, config = {}) @original_options = local_options @@ -136,6 +137,7 @@ module Rails def perform set_application_directory! + prepare_restart Rails::Server.new(server_options).tap do |server| # Require application after server sets environment to propagate # the --environment option. @@ -222,7 +224,7 @@ module Rails end def restart_command - "bin/rails server #{@server} #{@original_options.join(" ")}" + "bin/rails server #{@server} #{@original_options.join(" ")} --restart" end def pid @@ -232,6 +234,10 @@ module Rails def self.banner(*) "rails server [puma, thin etc] [options]" end + + def prepare_restart + FileUtils.rm_f(options[:pid]) if options[:restart] + end end end end diff --git a/railties/lib/rails/dev_caching.rb b/railties/lib/rails/dev_caching.rb index efb523de4c..ff629b2527 100644 --- a/railties/lib/rails/dev_caching.rb +++ b/railties/lib/rails/dev_caching.rb @@ -19,7 +19,6 @@ module Rails end FileUtils.touch "tmp/restart.txt" - FileUtils.rm_f("tmp/pids/server.pid") end def enable_by_argument(caching) diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml index 8bc8735a8e..2a67bdca25 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml @@ -7,7 +7,7 @@ # gem 'activerecord-jdbcmysql-adapter' # # And be sure to use new-style password hashing: -# http://dev.mysql.com/doc/refman/5.7/en/old-client.html +# https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html # default: &default adapter: mysql diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml index 269af1470d..04afaa0596 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml @@ -7,7 +7,7 @@ # gem 'mysql2' # # And be sure to use new-style password hashing: -# http://dev.mysql.com/doc/refman/5.7/en/old-client.html +# https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html # default: &default adapter: mysql2 diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore b/railties/lib/rails/generators/rails/plugin/templates/gitignore index 8c7cad74ed..e15863d860 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/gitignore +++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore @@ -2,8 +2,10 @@ log/*.log pkg/ <% if with_dummy_app? -%> +<% if sqlite3? -%> <%= dummy_path %>/db/*.sqlite3 <%= dummy_path %>/db/*.sqlite3-journal +<% end -%> <%= dummy_path %>/log/*.log <% unless options[:skip_yarn] -%> <%= dummy_path %>/node_modules/ diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb index a0bab22764..2fa05a9146 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb @@ -17,6 +17,7 @@ Minitest.backtrace_filter = Minitest::BacktraceFilter.new Rails::TestUnitReporter.executable = 'bin/test' <% end -%> +<% unless options[:skip_active_record] -%> # Load fixtures from the engine if ActiveSupport::TestCase.respond_to?(:fixture_path=) ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__) @@ -24,3 +25,4 @@ if ActiveSupport::TestCase.respond_to?(:fixture_path=) ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files" ActiveSupport::TestCase.fixtures :all end +<% end -%> diff --git a/railties/lib/rails/tasks/restart.rake b/railties/lib/rails/tasks/restart.rake index 5b6bb8ddd6..074e3e89a1 100644 --- a/railties/lib/rails/tasks/restart.rake +++ b/railties/lib/rails/tasks/restart.rake @@ -5,6 +5,5 @@ task :restart do verbose(false) do mkdir_p "tmp" touch "tmp/restart.txt" - rm_f "tmp/pids/server.pid" end end diff --git a/railties/lib/rails/test_unit/line_filtering.rb b/railties/lib/rails/test_unit/line_filtering.rb index e8b891d07b..f8ca77fe4a 100644 --- a/railties/lib/rails/test_unit/line_filtering.rb +++ b/railties/lib/rails/test_unit/line_filtering.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "method_source" require "rails/test_unit/runner" module Rails diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb index d56f315455..b25593ee1c 100644 --- a/railties/test/application/rake/dev_test.rb +++ b/railties/test/application/rake/dev_test.rb @@ -32,12 +32,16 @@ module ApplicationTests end end - test "dev:cache removes server.pid also" do + test "dev:cache touches tmp/restart.txt" do Dir.chdir(app_path) do - FileUtils.mkdir_p("tmp/pids") - FileUtils.touch("tmp/pids/server.pid") `rails dev:cache` - assert_not File.exist?("tmp/pids/server.pid") + assert File.exist?("tmp/restart.txt") + + prev_mtime = File.mtime("tmp/restart.txt") + sleep(1) + `rails dev:cache` + curr_mtime = File.mtime("tmp/restart.txt") + assert_not_equal prev_mtime, curr_mtime end end end diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb index bf06f4c522..ed96dcb6b1 100644 --- a/railties/test/application/rake/restart_test.rb +++ b/railties/test/application/rake/restart_test.rb @@ -35,15 +35,6 @@ module ApplicationTests assert File.exist?("tmp/restart.txt") end end - - test "rails restart removes server.pid also" do - Dir.chdir(app_path) do - FileUtils.mkdir_p("tmp/pids") - FileUtils.touch("tmp/pids/server.pid") - `bin/rails restart` - assert_not File.exist?("tmp/pids/server.pid") - end - end end end end diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb index 97b3b4f6b3..6db9a3b9e8 100644 --- a/railties/test/application/server_test.rb +++ b/railties/test/application/server_test.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true require "isolation/abstract_unit" +require "console_helpers" require "rails/command" require "rails/commands/server/server_command" module ApplicationTests class ServerTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation + include ConsoleHelpers def setup build_app @@ -29,5 +31,31 @@ module ApplicationTests log = File.read(Rails.application.config.paths["log"].first) assert_match(/DEPRECATION WARNING: Use `Rails::Application` subclass to start the server is deprecated/, log) end + + test "restart rails server with custom pid file path" do + skip "PTY unavailable" unless available_pty? + + master, slave = PTY.open + pid = nil + + begin + pid = Process.spawn("#{app_path}/bin/rails server -P tmp/dummy.pid", in: slave, out: slave, err: slave) + assert_output("Listening", master) + + Dir.chdir(app_path) { system("bin/rails restart") } + + assert_output("Restarting", master) + assert_output("Inherited", master) + ensure + kill(pid) if pid + end + end + + private + def kill(pid) + Process.kill("TERM", pid) + Process.wait(pid) + rescue Errno::ESRCH + end end end diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb index eb739db9d1..556c2289e7 100644 --- a/railties/test/commands/server_test.rb +++ b/railties/test/commands/server_test.rb @@ -190,7 +190,7 @@ class Rails::ServerTest < ActiveSupport::TestCase ARGV.replace args options = parse_arguments(args) - expected = "bin/rails server -p 4567 -b 127.0.0.1 -c dummy_config.ru -d -e test -P tmp/server.pid -C" + expected = "bin/rails server -p 4567 -b 127.0.0.1 -c dummy_config.ru -d -e test -P tmp/server.pid -C --restart" assert_equal expected, options[:restart_cmd] ensure diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index b50b9364d0..f64ebf5f1f 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -381,31 +381,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generator_without_skips - run_generator - assert_file "config/application.rb", /\s+require\s+["']rails\/all["']/ - assert_file "config/environments/development.rb" do |content| - assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content) - end - assert_file "config/environments/test.rb" do |content| - assert_match(/config\.action_mailer\.delivery_method = :test/, content) - end - assert_file "config/environments/production.rb" do |content| - assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content) - assert_match(/^ config\.read_encrypted_secrets = true/, content) - end - end - - def test_default_frameworks_are_required_when_others_are_removed - run_generator [destination_root, "--skip-active-record", "--skip-action-mailer", "--skip-action-cable", "--skip-sprockets", "--skip-test"] - assert_file "config/application.rb", /require\s+["']rails["']/ - assert_file "config/application.rb", /require\s+["']active_model\/railtie["']/ - assert_file "config/application.rb", /require\s+["']active_job\/railtie["']/ - assert_file "config/application.rb", /require\s+["']action_controller\/railtie["']/ - assert_file "config/application.rb", /require\s+["']action_view\/railtie["']/ - assert_file "config/application.rb", /require\s+["']active_storage\/engine["']/ - end - def test_generator_defaults_to_puma_version run_generator [destination_root] assert_gem "puma", "'~> 3.7'" @@ -419,39 +394,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generator_if_skip_active_record_is_given - run_generator [destination_root, "--skip-active-record"] - assert_no_directory "db/" - assert_no_file "config/database.yml" - assert_no_file "app/models/application_record.rb" - assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ - assert_file "test/test_helper.rb" do |helper_content| - assert_no_match(/fixtures :all/, helper_content) - end - assert_file "bin/setup" do |setup_content| - assert_no_match(/db:setup/, setup_content) - end - assert_file "bin/update" do |update_content| - assert_no_match(/db:migrate/, update_content) - end - end - - def test_generator_if_skip_action_mailer_is_given - run_generator [destination_root, "--skip-action-mailer"] - assert_file "config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/ - assert_file "config/environments/development.rb" do |content| - assert_no_match(/config\.action_mailer/, content) - end - assert_file "config/environments/test.rb" do |content| - assert_no_match(/config\.action_mailer/, content) - end - assert_file "config/environments/production.rb" do |content| - assert_no_match(/config\.action_mailer/, content) - end - assert_no_directory "app/mailers" - assert_no_directory "test/mailers" - end - def test_generator_has_assets_gems run_generator @@ -459,43 +401,6 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_gem "uglifier" end - def test_generator_if_skip_sprockets_is_given - run_generator [destination_root, "--skip-sprockets"] - - assert_no_file "config/initializers/assets.rb" - - assert_file "config/application.rb", /#\s+require\s+["']sprockets\/railtie["']/ - - assert_file "Gemfile" do |content| - assert_no_match(/sass-rails/, content) - assert_no_match(/uglifier/, content) - assert_no_match(/coffee-rails/, content) - end - - assert_file "config/environments/development.rb" do |content| - assert_no_match(/config\.assets\.debug/, content) - end - - assert_file "config/environments/production.rb" do |content| - assert_no_match(/config\.assets\.digest/, content) - assert_no_match(/config\.assets\.js_compressor/, content) - assert_no_match(/config\.assets\.css_compressor/, content) - assert_no_match(/config\.assets\.compile/, content) - end - end - - def test_generator_if_skip_action_cable_is_given - run_generator [destination_root, "--skip-action-cable"] - assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/ - assert_no_file "config/cable.yml" - assert_no_file "app/assets/javascripts/cable.js" - assert_no_directory "app/assets/javascripts/channels" - assert_no_directory "app/channels" - assert_file "Gemfile" do |content| - assert_no_match(/redis/, content) - end - end - def test_action_cable_redis_gems run_generator assert_file "Gemfile", /^# gem 'redis'/ @@ -770,30 +675,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_gitignore_when_sqlite3 - run_generator - - assert_file ".gitignore" do |content| - assert_match(/sqlite3/, content) - end - end - - def test_gitignore_when_no_active_record - run_generator [destination_root, "--skip-active-record"] - - assert_file ".gitignore" do |content| - assert_no_match(/sqlite/i, content) - end - end - - def test_gitignore_when_non_sqlite3_db - run_generator([destination_root, "-d", "mysql"]) - - assert_file ".gitignore" do |content| - assert_no_match(/sqlite/i, content) - end - end - def test_inclusion_of_ruby_version run_generator diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index e94b1ac8fe..38130ceb68 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -129,31 +129,6 @@ class PluginGeneratorTest < Rails::Generators::TestCase end end - def test_generating_adds_dummy_app_without_sprockets - run_generator [destination_root, "--skip-sprockets"] - - assert_no_file "test/dummy/config/initializers/assets.rb" - - assert_file "test/dummy/config/application.rb", /#\s+require\s+["']sprockets\/railtie["']/ - - assert_file "Gemfile" do |content| - assert_no_match(/sass-rails/, content) - assert_no_match(/uglifier/, content) - assert_no_match(/coffee-rails/, content) - end - - assert_file "test/dummy/config/environments/development.rb" do |content| - assert_no_match(/config\.assets\.debug/, content) - end - - assert_file "test/dummy/config/environments/production.rb" do |content| - assert_no_match(/config\.assets\.digest/, content) - assert_no_match(/config\.assets\.js_compressor/, content) - assert_no_match(/config\.assets\.css_compressor/, content) - assert_no_match(/config\.assets\.compile/, content) - end - end - def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files run_generator [destination_root, "-T", "--mountable", "--dummy-path", "my_dummy_app"] assert_file "Rakefile", /APP_RAKEFILE/ @@ -202,69 +177,13 @@ class PluginGeneratorTest < Rails::Generators::TestCase end end - def test_app_generator_without_skips - run_generator - assert_file "test/dummy/config/application.rb", /\s+require\s+["']rails\/all["']/ - assert_file "test/dummy/config/environments/development.rb" do |content| - assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content) - end - assert_file "test/dummy/config/environments/test.rb" do |content| - assert_match(/config\.action_mailer\.delivery_method = :test/, content) - end - assert_file "test/dummy/config/environments/production.rb" do |content| - assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content) - assert_match(/^ config\.read_encrypted_secrets = true/, content) - end - end - - def test_default_frameworks_are_required_when_others_are_removed - run_generator [destination_root, "--skip-active-record", "--skip-action-mailer", "--skip-action-cable", "--skip-sprockets"] - assert_file "test/dummy/config/application.rb", /require\s+["']rails["']/ - assert_file "test/dummy/config/application.rb", /require\s+["']active_model\/railtie["']/ - assert_file "test/dummy/config/application.rb", /require\s+["']active_job\/railtie["']/ - assert_file "test/dummy/config/application.rb", /require\s+["']action_controller\/railtie["']/ - assert_file "test/dummy/config/application.rb", /require\s+["']action_view\/railtie["']/ - end - - def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given - run_generator [destination_root, "--skip-active-record"] - assert_file "test/dummy/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ - end - def test_ensure_that_skip_active_record_option_is_passed_to_app_generator run_generator [destination_root, "--skip_active_record"] - assert_no_file "test/dummy/config/database.yml" assert_file "test/test_helper.rb" do |contents| assert_no_match(/ActiveRecord/, contents) end end - def test_action_mailer_is_removed_from_frameworks_if_skip_action_mailer_is_given - run_generator [destination_root, "--skip-action-mailer"] - assert_file "test/dummy/config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/ - assert_file "test/dummy/config/environments/development.rb" do |content| - assert_no_match(/config\.action_mailer/, content) - end - assert_file "test/dummy/config/environments/test.rb" do |content| - assert_no_match(/config\.action_mailer/, content) - end - assert_file "test/dummy/config/environments/production.rb" do |content| - assert_no_match(/config\.action_mailer/, content) - end - assert_no_directory "test/dummy/app/mailers" - end - - def test_action_cable_is_removed_from_frameworks_if_skip_action_cable_is_given - run_generator [destination_root, "--skip-action-cable"] - assert_file "test/dummy/config/application.rb", /#\s+require\s+["']action_cable\/engine["']/ - assert_no_file "test/dummy/config/cable.yml" - assert_no_file "test/dummy/app/assets/javascripts/cable.js" - assert_no_directory "test/dummy/app/channels" - assert_file "Gemfile" do |content| - assert_no_match(/redis/, content) - end - end - def test_ensure_that_database_option_is_passed_to_app_generator run_generator [destination_root, "--database", "postgresql"] assert_file "test/dummy/config/database.yml", /postgres/ diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index 6c0775b50e..56c9b37e1b 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -128,6 +128,120 @@ module SharedGeneratorTests assert_no_file("app/models/concerns/.keep") end + def test_default_frameworks_are_required_when_others_are_removed + run_generator [destination_root, "--skip-active-record", "--skip-action-mailer", "--skip-action-cable", "--skip-sprockets"] + assert_file "#{application_path}/config/application.rb", /require\s+["']rails["']/ + assert_file "#{application_path}/config/application.rb", /require\s+["']active_model\/railtie["']/ + assert_file "#{application_path}/config/application.rb", /require\s+["']active_job\/railtie["']/ + assert_file "#{application_path}/config/application.rb", /require\s+["']action_controller\/railtie["']/ + assert_file "#{application_path}/config/application.rb", /require\s+["']action_view\/railtie["']/ + assert_file "#{application_path}/config/application.rb", /require\s+["']active_storage\/engine["']/ + end + + def test_generator_without_skips + run_generator + assert_file "#{application_path}/config/application.rb", /\s+require\s+["']rails\/all["']/ + assert_file "#{application_path}/config/environments/development.rb" do |content| + assert_match(/config\.action_mailer\.raise_delivery_errors = false/, content) + end + assert_file "#{application_path}/config/environments/test.rb" do |content| + assert_match(/config\.action_mailer\.delivery_method = :test/, content) + end + assert_file "#{application_path}/config/environments/production.rb" do |content| + assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content) + assert_match(/^ config\.read_encrypted_secrets = true/, content) + end + end + + def test_gitignore_when_sqlite3 + run_generator + + assert_file ".gitignore" do |content| + assert_match(/sqlite3/, content) + end + end + + def test_gitignore_when_non_sqlite3_db + run_generator([destination_root, "-d", "mysql"]) + + assert_file ".gitignore" do |content| + assert_no_match(/sqlite/i, content) + end + end + + def test_generator_if_skip_active_record_is_given + run_generator [destination_root, "--skip-active-record"] + assert_no_directory "#{application_path}/db/" + assert_no_file "#{application_path}/config/database.yml" + assert_no_file "#{application_path}/app/models/application_record.rb" + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ + assert_file "test/test_helper.rb" do |helper_content| + assert_no_match(/fixtures :all/, helper_content) + end + assert_file "#{application_path}/bin/setup" do |setup_content| + assert_no_match(/db:setup/, setup_content) + end + assert_file "#{application_path}/bin/update" do |update_content| + assert_no_match(/db:migrate/, update_content) + end + assert_file ".gitignore" do |content| + assert_no_match(/sqlite/i, content) + end + end + + def test_generator_if_skip_action_mailer_is_given + run_generator [destination_root, "--skip-action-mailer"] + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/ + assert_file "#{application_path}/config/environments/development.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "#{application_path}/config/environments/test.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "#{application_path}/config/environments/production.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_no_directory "#{application_path}/app/mailers" + assert_no_directory "#{application_path}/test/mailers" + end + + def test_generator_if_skip_action_cable_is_given + run_generator [destination_root, "--skip-action-cable"] + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_cable\/engine["']/ + assert_no_file "#{application_path}/config/cable.yml" + assert_no_file "#{application_path}/app/assets/javascripts/cable.js" + assert_no_directory "#{application_path}/app/assets/javascripts/channels" + assert_no_directory "#{application_path}/app/channels" + assert_file "Gemfile" do |content| + assert_no_match(/redis/, content) + end + end + + def test_generator_if_skip_sprockets_is_given + run_generator [destination_root, "--skip-sprockets"] + + assert_no_file "#{application_path}/config/initializers/assets.rb" + + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']sprockets\/railtie["']/ + + assert_file "Gemfile" do |content| + assert_no_match(/sass-rails/, content) + assert_no_match(/uglifier/, content) + assert_no_match(/coffee-rails/, content) + end + + assert_file "#{application_path}/config/environments/development.rb" do |content| + assert_no_match(/config\.assets\.debug/, content) + end + + assert_file "#{application_path}/config/environments/production.rb" do |content| + assert_no_match(/config\.assets\.digest/, content) + assert_no_match(/config\.assets\.js_compressor/, content) + assert_no_match(/config\.assets\.css_compressor/, content) + assert_no_match(/config\.assets\.compile/, content) + end + end + def test_generator_for_yarn run_generator assert_file "#{application_path}/package.json", /dependencies/ |