diff options
79 files changed, 757 insertions, 216 deletions
diff --git a/.travis.yml b/.travis.yml index bd1a320de5..ef85107515 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,13 @@ rvm: - 2.3.0 - ruby-head matrix: + include: + # Latest compiled version in http://rubies.travis-ci.org + - rvm: jruby-9.0.5.0 + jdk: oraclejdk8 + env: + - "JRUBY_OPTS='--dev -J-Xmx1024M'" + - "GEM='ap'" allow_failures: - rvm: ruby-head fast_finish: true @@ -3,7 +3,7 @@ source 'https://rubygems.org' gemspec # We need a newish Rake since Active Job sets its test tasks' descriptions. -gem 'rake', '>= 10.3' +gem 'rake', '>= 11.1' # This needs to be with require false to ensure correct loading order, as it has to # be loaded after loading the test library. diff --git a/Gemfile.lock b/Gemfile.lock index c76c4d7c2f..4c64cf9cac 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -197,7 +197,7 @@ GEM rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - rake (10.5.0) + rake (11.1.1) rb-fsevent (0.9.7) rb-inotify (0.9.5) ffi (>= 0.5.0) diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index fb9e498e49..1ea862b389 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,3 +1,7 @@ +* Add ActiveSupport::Notifications to ActionCable::Channel. + + *Matthew Wear* + * Allow channel identifiers with no backslahes/escaping to be accepted by the subscription storer. diff --git a/actioncable/app/assets/javascripts/action_cable/connection.coffee b/actioncable/app/assets/javascripts/action_cable/connection.coffee index 92272cc5b8..3a139acf3a 100644 --- a/actioncable/app/assets/javascripts/action_cable/connection.coffee +++ b/actioncable/app/assets/javascripts/action_cable/connection.coffee @@ -20,7 +20,7 @@ class ActionCable.Connection open: => if @isActive() - ActionCable.log("Attemped to open WebSocket, but existing socket is #{@getState()}") + ActionCable.log("Attempted to open WebSocket, but existing socket is #{@getState()}") throw new Error("Existing connection must be closed before opening") else ActionCable.log("Opening WebSocket, current state is #{@getState()}") diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb index 714d9887d4..464d0581dd 100644 --- a/actioncable/lib/action_cable/channel/base.rb +++ b/actioncable/lib/action_cable/channel/base.rb @@ -160,7 +160,10 @@ module ActionCable action = extract_action(data) if processable_action?(action) - dispatch_action(action, data) + payload = { channel_class: self.class.name, action: action, data: data } + ActiveSupport::Notifications.instrument("perform_action.action_cable", payload) do + dispatch_action(action, data) + end else logger.error "Unable to process #{action_signature(action, data)}" end @@ -192,7 +195,11 @@ module ActionCable # the proper channel identifier marked as the recipient. def transmit(data, via: nil) logger.info "#{self.class.name} transmitting #{data.inspect.truncate(300)}".tap { |m| m << " (via #{via})" if via } - connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, message: data) + + payload = { channel_class: self.class.name, data: data, via: via } + ActiveSupport::Notifications.instrument("transmit.action_cable", payload) do + connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, message: data) + end end def defer_subscription_confirmation! @@ -265,8 +272,11 @@ module ActionCable def transmit_subscription_confirmation unless subscription_confirmation_sent? logger.info "#{self.class.name} is transmitting the subscription confirmation" - connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation]) - @subscription_confirmation_sent = true + + ActiveSupport::Notifications.instrument("transmit_subscription_confirmation.action_cable", channel_class: self.class.name) do + connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation]) + @subscription_confirmation_sent = true + end end end @@ -277,7 +287,10 @@ module ActionCable def transmit_subscription_rejection logger.info "#{self.class.name} is transmitting the subscription rejection" - connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection]) + + ActiveSupport::Notifications.instrument("transmit_subscription_rejection.action_cable", channel_class: self.class.name) do + connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection]) + end end end end diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb index 9e4dbcd6e6..7d6de78582 100644 --- a/actioncable/lib/action_cable/connection/client_socket.rb +++ b/actioncable/lib/action_cable/connection/client_socket.rb @@ -71,6 +71,8 @@ module ActionCable def write(data) @stream.write(data) + rescue => e + emit_error e.message end def transmit(message) diff --git a/actioncable/lib/action_cable/connection/faye_client_socket.rb b/actioncable/lib/action_cable/connection/faye_client_socket.rb index c9139b6858..47d09a9e14 100644 --- a/actioncable/lib/action_cable/connection/faye_client_socket.rb +++ b/actioncable/lib/action_cable/connection/faye_client_socket.rb @@ -36,6 +36,7 @@ module ActionCable @faye.on(:open) { |event| @event_target.on_open } @faye.on(:message) { |event| @event_target.on_message(event.data) } @faye.on(:close) { |event| @event_target.on_close(event.reason, event.code) } + @faye.on(:error) { |event| @event_target.on_error(event.message) } end end end diff --git a/actioncable/lib/action_cable/connection/faye_event_loop.rb b/actioncable/lib/action_cable/connection/faye_event_loop.rb index 8b70f3d84e..9c44b38bc3 100644 --- a/actioncable/lib/action_cable/connection/faye_event_loop.rb +++ b/actioncable/lib/action_cable/connection/faye_event_loop.rb @@ -36,7 +36,7 @@ module ActionCable end def shutdown - inner.cancel + @inner.cancel end end end diff --git a/actioncable/lib/action_cable/connection/stream.rb b/actioncable/lib/action_cable/connection/stream.rb index 2d97b28c09..0cf59091bc 100644 --- a/actioncable/lib/action_cable/connection/stream.rb +++ b/actioncable/lib/action_cable/connection/stream.rb @@ -29,7 +29,7 @@ module ActionCable def write(data) return @rack_hijack_io.write(data) if @rack_hijack_io return @stream_send.call(data) if @stream_send - rescue EOFError + rescue EOFError, Errno::ECONNRESET @socket_object.client_gone end diff --git a/actioncable/test/channel/base_test.rb b/actioncable/test/channel/base_test.rb index d41bf3064b..bed54eb6b3 100644 --- a/actioncable/test/channel/base_test.rb +++ b/actioncable/test/channel/base_test.rb @@ -166,6 +166,81 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase end end + test "notification for perform_action" do + begin + events = [] + ActiveSupport::Notifications.subscribe "perform_action.action_cable" do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + + data = {'action' => :speak, 'content' => 'hello'} + @channel.perform_action data + + assert_equal 1, events.length + assert_equal 'perform_action.action_cable', events[0].name + assert_equal 'ActionCable::Channel::BaseTest::ChatChannel', events[0].payload[:channel_class] + assert_equal :speak, events[0].payload[:action] + assert_equal data, events[0].payload[:data] + ensure + ActiveSupport::Notifications.unsubscribe "perform_action.action_cable" + end + end + + test "notification for transmit" do + begin + events = [] + ActiveSupport::Notifications.subscribe 'transmit.action_cable' do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + + @channel.perform_action 'action' => :get_latest + expected_data = {data: 'latest'} + + assert_equal 1, events.length + assert_equal 'transmit.action_cable', events[0].name + assert_equal 'ActionCable::Channel::BaseTest::ChatChannel', events[0].payload[:channel_class] + assert_equal expected_data, events[0].payload[:data] + assert_nil events[0].payload[:via] + ensure + ActiveSupport::Notifications.unsubscribe 'transmit.action_cable' + end + end + + test "notification for transmit_subscription_confirmation" do + begin + events = [] + ActiveSupport::Notifications.subscribe 'transmit_subscription_confirmation.action_cable' do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + + @channel.stubs(:subscription_confirmation_sent?).returns(false) + @channel.send(:transmit_subscription_confirmation) + + assert_equal 1, events.length + assert_equal 'transmit_subscription_confirmation.action_cable', events[0].name + assert_equal 'ActionCable::Channel::BaseTest::ChatChannel', events[0].payload[:channel_class] + ensure + ActiveSupport::Notifications.unsubscribe 'transmit_subscription_confirmation.action_cable' + end + end + + test "notification for transmit_subscription_rejection" do + begin + events = [] + ActiveSupport::Notifications.subscribe 'transmit_subscription_rejection.action_cable' do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + + @channel.send(:transmit_subscription_rejection) + + assert_equal 1, events.length + assert_equal 'transmit_subscription_rejection.action_cable', events[0].name + assert_equal 'ActionCable::Channel::BaseTest::ChatChannel', events[0].payload[:channel_class] + ensure + ActiveSupport::Notifications.unsubscribe 'transmit_subscription_rejection.action_cable' + end + end + private def assert_logged(message) old_logger = @connection.logger diff --git a/actioncable/test/connection/client_socket_test.rb b/actioncable/test/connection/client_socket_test.rb new file mode 100644 index 0000000000..dd730e348f --- /dev/null +++ b/actioncable/test/connection/client_socket_test.rb @@ -0,0 +1,65 @@ +require 'test_helper' +require 'stubs/test_server' + +class ActionCable::Connection::StreamTest < ActionCable::TestCase + class Connection < ActionCable::Connection::Base + attr_reader :websocket, :subscriptions, :message_buffer, :connected + attr_reader :errors + + def initialize(*) + super + @errors = [] + end + + def connect + @connected = true + end + + def disconnect + @connected = false + end + + def send_async(method, *args) + send method, *args + end + + def on_error(message) + @errors << message + end + end + + setup do + @server = TestServer.new + @server.config.allowed_request_origins = %w( http://rubyonrails.com ) + end + + test 'delegate socket errors to on_error handler' do + skip if ENV['FAYE'].present? + + run_in_eventmachine do + connection = open_connection + + # Internal hax = :( + client = connection.websocket.send(:websocket) + client.instance_variable_get('@stream').expects(:write).raises('foo') + client.expects(:client_gone).never + + client.write('boo') + assert_equal %w[ foo ], connection.errors + end + end + + private + def open_connection + env = Rack::MockRequest.env_for '/test', + 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', + 'HTTP_HOST' => 'localhost', 'HTTP_ORIGIN' => 'http://rubyonrails.com' + env['rack.hijack'] = -> { env['rack.hijack_io'] = StringIO.new } + + Connection.new(@server, env).tap do |connection| + connection.process + connection.send :handle_open + assert connection.connected + end + end +end diff --git a/actioncable/test/connection/stream_test.rb b/actioncable/test/connection/stream_test.rb new file mode 100644 index 0000000000..d5aad63648 --- /dev/null +++ b/actioncable/test/connection/stream_test.rb @@ -0,0 +1,67 @@ +require 'test_helper' +require 'stubs/test_server' + +class ActionCable::Connection::StreamTest < ActionCable::TestCase + class Connection < ActionCable::Connection::Base + attr_reader :websocket, :subscriptions, :message_buffer, :connected + attr_reader :errors + + def initialize(*) + super + @errors = [] + end + + def connect + @connected = true + end + + def disconnect + @connected = false + end + + def send_async(method, *args) + send method, *args + end + + def on_error(message) + @errors << message + end + end + + setup do + @server = TestServer.new + @server.config.allowed_request_origins = %w( http://rubyonrails.com ) + end + + [ EOFError, Errno::ECONNRESET ].each do |closed_exception| + test "closes socket on #{closed_exception}" do + skip if ENV['FAYE'].present? + + run_in_eventmachine do + connection = open_connection + + # Internal hax = :( + client = connection.websocket.send(:websocket) + client.instance_variable_get('@stream').instance_variable_get('@rack_hijack_io').expects(:write).raises(closed_exception, 'foo') + client.expects(:client_gone) + + client.write('boo') + assert_equal [], connection.errors + end + end + end + + private + def open_connection + env = Rack::MockRequest.env_for '/test', + 'HTTP_CONNECTION' => 'upgrade', 'HTTP_UPGRADE' => 'websocket', + 'HTTP_HOST' => 'localhost', 'HTTP_ORIGIN' => 'http://rubyonrails.com' + env['rack.hijack'] = -> { env['rack.hijack_io'] = StringIO.new } + + Connection.new(@server, env).tap do |connection| + connection.process + connection.send :handle_open + assert connection.connected + end + end +end diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb index 70333e51bd..6d20e6ed78 100644 --- a/actioncable/test/subscription_adapter/evented_redis_test.rb +++ b/actioncable/test/subscription_adapter/evented_redis_test.rb @@ -4,6 +4,17 @@ require_relative './common' class EventedRedisAdapterTest < ActionCable::TestCase include CommonSubscriptionAdapterTest + def setup + super + + # em-hiredis is warning-rich + @previous_verbose, $VERBOSE = $VERBOSE, nil + end + + def teardown + $VERBOSE = @previous_verbose + end + def cable_config { adapter: 'evented_redis', url: 'redis://127.0.0.1:6379/12' } end diff --git a/actioncable/test/subscription_adapter/postgresql_test.rb b/actioncable/test/subscription_adapter/postgresql_test.rb index 3b4fadbb2a..214352a0b2 100644 --- a/actioncable/test/subscription_adapter/postgresql_test.rb +++ b/actioncable/test/subscription_adapter/postgresql_test.rb @@ -21,6 +21,7 @@ class PostgresqlAdapterTest < ActionCable::TestCase begin ActiveRecord::Base.connection rescue + @rx_adapter = @tx_adapter = nil skip "Couldn't connect to PostgreSQL: #{database_config.inspect}" end diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 604e332dad..51d85b95f2 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,8 +1,6 @@ ## Rails 5.0.0.beta3 (February 24, 2016) ## -* Add support to fragment cache in Action Mailer. - - Now you can use fragment caching in your mailers views. +* Add support for fragment caching in Action Mailer views. *Stan Lo* diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb index f86d43be61..01bdfb0685 100644 --- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb +++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb @@ -11,8 +11,8 @@ module Rails template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}_mailer.rb") in_root do - if self.behavior == :invoke && !File.exist?('app/mailers/application_mailer.rb') - template 'application_mailer.rb', 'app/mailers/application_mailer.rb' + if self.behavior == :invoke && !File.exist?(application_mailer_file_name) + template 'application_mailer.rb', application_mailer_file_name end end end @@ -23,6 +23,15 @@ module Rails def file_name @_file_name ||= super.gsub(/_mailer/i, '') end + + private + def application_mailer_file_name + @_application_mailer_file_name ||= if mountable_engine? + "app/mailers/#{namespaced_path}/application_mailer.rb" + else + "app/mailers/application_mailer.rb" + end + end end end end diff --git a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb index 286b2239d1..f23e575fe5 100644 --- a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb +++ b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb @@ -1,4 +1,6 @@ +<% module_namespacing do %> class ApplicationMailer < ActionMailer::Base default from: 'from@example.com' layout 'mailer' end +<% end %> diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 61c608972c..4f5b81d04a 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,7 +1,12 @@ +* `ActionDispatch::ParamsParser` is deprecated and was removed from the middleware + stack. To configure the parameter parsers use `ActionDispatch::Request.parameter_parsers=`. + + *tenderlove* + * When a `respond_to` collector with a block doesn't have a response, then a `:no_content` response should be rendered. This brings the default rendering behavior introduced by https://github.com/rails/rails/issues/19036 - to controller methods employing `respond_to` + to controller methods employing `respond_to`. *Justin Coyne* @@ -30,19 +35,19 @@ First, if a template exists for the controller action, it is rendered. This template lookup takes into account the action name, locales, format, - variant, template handlers, etc. (see +render+ for details). + variant, template handlers, etc. (see `render` for details). Second, if other templates exist for the controller action but is not in - the right format (or variant, etc.), an <tt>ActionController::UnknownFormat</tt> + the right format (or variant, etc.), an `ActionController::UnknownFormat` is raised. The list of available templates is assumed to be a complete enumeration of all the possible formats (or variants, etc.); that is, having only HTML and JSON templates indicate that the controller action is not meant to handle XML requests. Third, if the current request is an "interactive" browser request (the user - navigated here by entering the URL in the address bar, submiting a form, + navigated here by entering the URL in the address bar, submitting a form, clicking on a link, etc. as opposed to an XHR or non-browser API request), - <tt>ActionView::UnknownFormat</tt> is raised to display a helpful error + `ActionView::UnknownFormat` is raised to display a helpful error message. Finally, it falls back to the same "204 No Content" behavior as API controllers. @@ -51,7 +56,7 @@ ## Rails 5.0.0.beta3 (February 24, 2016) ## -* Add application/gzip as a default mime type. +* Add "application/gzip" as a default mime type. *Mehmet Emin İNAÇ* @@ -102,7 +107,7 @@ *Kasper Timm Hansen* -* Add image/svg+xml as a default mime type. +* Add "image/svg+xml" as a default mime type. *DHH* @@ -115,7 +120,7 @@ See #18902. - *Anton Davydov* & *Vipul A M* + *Anton Davydov*, *Vipul A M* * Response etags to always be weak: Prefixes 'W/' to value returned by `ActionDispatch::Http::Cache::Response#etag=`, such that etags set in @@ -147,11 +152,7 @@ * Add option for per-form CSRF tokens. - *Greg Ose & Ben Toews* - -* Add tests and documentation for `ActionController::Renderers::use_renderers`. - - *Benjamin Fleischer* + *Greg Ose*, *Ben Toews* * Fix `ActionController::Parameters#convert_parameters_to_hashes` to return filtered or unfiltered values based on from where it is called, `to_h` or `to_unsafe_h` diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index 3a6f784507..6192fc0f9c 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -1,33 +1,31 @@ require 'active_support/core_ext/string/strip' module ActionController - # Handles implicit rendering for a controller action when it did not - # explicitly indicate an appropiate response via methods such as +render+, - # +respond_to+, +redirect+ or +head+. + # Handles implicit rendering for a controller action that does not + # explicitly respond with +render+, +respond_to+, +redirect+, or +head+. # - # For API controllers, the implicit render always renders "204 No Content" - # and does not account for any templates. + # For API controllers, the implicit response is always 204 No Content. # - # For other controllers, the following conditions are checked: + # For all other controllers, we use these heuristics to decide whether to + # render a template, raise an error for a missing template, or respond with + # 204 No Content: # - # First, if a template exists for the controller action, it is rendered. - # This template lookup takes into account the action name, locales, format, - # variant, template handlers, etc. (see +render+ for details). + # First, if we DO find a template, it's rendered. Template lookup accounts + # for the action name, locales, format, variant, template handlers, and more + # (see +render+ for details). # - # Second, if other templates exist for the controller action but is not in - # the right format (or variant, etc.), an <tt>ActionController::UnknownFormat</tt> - # is raised. The list of available templates is assumed to be a complete - # enumeration of all the possible formats (or variants, etc.); that is, - # having only HTML and JSON templates indicate that the controller action is - # not meant to handle XML requests. + # Second, if we DON'T find a template but the controller action does have + # templates for other formats, variants, etc., then we trust that you meant + # to provide a template for this response, too, and we raise + # <tt>ActionController::UnknownFormat</tt> with an explanation. # - # Third, if the current request is an "interactive" browser request (the user - # navigated here by entering the URL in the address bar, submiting a form, - # clicking on a link, etc. as opposed to an XHR or non-browser API request), - # <tt>ActionView::UnknownFormat</tt> is raised to display a helpful error - # message. + # Third, if we DON'T find a template AND the request is a page load in a web + # browser (technically, a non-XHR GET request for an HTML response) where + # you reasonably expect to have rendered a template, then we raise + # <tt>ActionView::UnknownFormat</tt> with an explanation. # - # Finally, it falls back to the same "204 No Content" behavior as API controllers. + # Finally, if we DON'T find a template AND the request isn't a browser page + # load, then we implicitly respond with 204 No Content. module ImplicitRender # :stopdoc: @@ -37,39 +35,23 @@ module ActionController if template_exists?(action_name.to_s, _prefixes, variants: request.variant) render(*args) elsif any_templates?(action_name.to_s, _prefixes) - message = "#{self.class.name}\##{action_name} does not know how to respond " \ - "to this request. There are other templates available for this controller " \ - "action but none of them were suitable for this request.\n\n" \ - "This usually happens when the client requested an unsupported format " \ - "(e.g. requesting HTML content from a JSON endpoint or vice versa), but " \ - "it might also be failing due to other constraints, such as locales or " \ - "variants.\n" - - if request.formats.any? - message << "\nRequested format(s): #{request.formats.join(", ")}" - end - - if request.variant.any? - message << "\nRequested variant(s): #{request.variant.join(", ")}" - end + message = "#{self.class.name}\##{action_name} is missing a template " \ + "for this request format and variant.\n" \ + "\nrequest.formats: #{request.formats.map(&:to_s).inspect}" \ + "\nrequest.variant: #{request.variant.inspect}" raise ActionController::UnknownFormat, message elsif interactive_browser_request? - message = "You did not define any templates for #{self.class.name}\##{action_name}. " \ - "This is not necessarily a problem (e.g. you might be building an API endpoint " \ - "that does not require any templates), and the controller would usually respond " \ - "with `head :no_content` for your convenience.\n\n" \ - "However, you appear to have navigated here from an interactive browser request – " \ - "such as by navigating to this URL directly, clicking on a link or submitting a form. " \ - "Rendering a `head :no_content` in this case could have resulted in unexpected UI " \ - "behavior in the browser.\n\n" \ - "If you expected the `head :no_content` response, you do not need to take any " \ - "actions – requests coming from an XHR (AJAX) request or other non-browser clients " \ - "will receive the \"204 No Content\" response as expected.\n\n" \ - "If you did not expect this behavior, you can resolve this error by adding a " \ - "template for this controller action (usually `#{action_name}.html.erb`) or " \ - "otherwise indicate the appropriate response in the action using `render`, " \ - "`redirect_to`, `head`, etc.\n" + message = "#{self.class.name}\##{action_name} is missing a template " \ + "for this request format and variant.\n\n" \ + "request.formats: #{request.formats.map(&:to_s).inspect}\n" \ + "request.variant: #{request.variant.inspect}\n\n" \ + "NOTE! For XHR/Ajax or API requests, this action would normally " \ + "respond with 204 No Content: an empty white screen. Since you're " \ + "loading it in a web browser, we assume that you expected to " \ + "actually render a template, not… nothing, so we're showing an " \ + "error to be extra-clear. If you expect 204 No Content, carry on. " \ + "That's what you'll get from an XHR or API request. Give it a shot." raise ActionController::UnknownFormat, message else @@ -85,9 +67,8 @@ module ActionController end private - def interactive_browser_request? - request.format == Mime[:html] && !request.xhr? + request.get? && request.format == Mime[:html] && !request.xhr? end end end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index b13ba06962..3c7cc15627 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -84,7 +84,7 @@ module ActionController # redirect_back fallback_location: proc { edit_post_url(@post) } # # All options that can be passed to <tt>redirect_to</tt> are accepted as - # options and the behavior is indetical. + # options and the behavior is identical. def redirect_back(fallback_location:, **args) if referer = request.headers["Referer"] redirect_to referer, **args diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 5c3b7245d6..69a934b7cd 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -5,7 +5,7 @@ module ActionDispatch # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" } # headers = ActionDispatch::Http::Headers.new(env) # headers["Content-Type"] # => "text/plain" - # headers["User-Agent"] # => "curl/7/43/0" + # headers["User-Agent"] # => "curl/7.43.0" # # Also note that when headers are mapped to CGI-like variables by the Rack # server, both dashes and underscores are converted to underscores. This diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 5841c978af..faf3262b8f 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -37,6 +37,7 @@ module ActionDispatch # The +parsers+ argument can take Hash of parsers where key is identifying # content mime type, and value is a lambda that is going to process data. def self.new(app, parsers = {}) + ActiveSupport::Deprecation.warn('ActionDispatch::ParamsParser is deprecated and will be removed in Rails 5.1. Configure the parameter parsing in ActionDispatch::Request.parameter_parsers.') parsers = parsers.transform_keys { |key| key.respond_to?(:symbol) ? key.symbol : key } ActionDispatch::Request.parameter_parsers = ActionDispatch::Request::DEFAULT_PARSERS.merge(parsers) app diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index c15fb4304d..ecc1e764a5 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -44,7 +44,7 @@ * Create a new `ActiveSupport::SafeBuffer` instance when `content_for` is flushed. - Fixes #19890 + Fixes #19890. *Yoong Kang Lim* diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb index c233d06ccb..b32567cd66 100644 --- a/actionview/lib/action_view/template/types.rb +++ b/actionview/lib/action_view/template/types.rb @@ -1,4 +1,3 @@ -require 'set' require 'active_support/core_ext/module/attribute_accessors' module ActionView diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index e06b98736d..3feb82d432 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -193,26 +193,13 @@ module ActiveJob # end # # The block form supports filtering. If the :only option is specified, - # then only the listed job(s) will be performed. + # then only the listed job(s) will not be performed. # - # def test_hello_job - # assert_performed_jobs 1, only: HelloJob do - # HelloJob.perform_later('jeremy') - # LoggingJob.perform_later - # end - # end - # - # An array may also be specified, to support testing multiple jobs. - # - # def test_hello_and_logging_jobs - # assert_nothing_raised do - # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do - # HelloJob.perform_later('jeremy') - # LoggingJob.perform_later('stewie') - # RescueJob.perform_later('david') - # end - # end + # def test_no_logging + # assert_no_performed_jobs only: LoggingJob do + # HelloJob.perform_later('jeremy') # end + # end # # Note: This assertion is simply a shortcut for: # diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 36834bbd36..836201535f 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -110,7 +110,7 @@ module ActiveModel # person.errors.include?(:name) # => true # person.errors.include?(:age) # => false def include?(attribute) - messages[attribute].present? + messages.key?(attribute) && messages[attribute].present? end alias :has_key? :include? alias :key? :include? diff --git a/activemodel/lib/active_model/type/decimal.rb b/activemodel/lib/active_model/type/decimal.rb index d19d8baada..11ea327026 100644 --- a/activemodel/lib/active_model/type/decimal.rb +++ b/activemodel/lib/active_model/type/decimal.rb @@ -29,12 +29,12 @@ module ActiveModel end end - scale ? casted_value.round(scale) : casted_value + apply_scale(casted_value) end def convert_float_to_big_decimal(value) if precision - BigDecimal(value, float_precision) + BigDecimal(apply_scale(value), float_precision) else value.to_d end @@ -47,6 +47,14 @@ module ActiveModel precision.to_i end end + + def apply_scale(value) + if scale + value.round(scale) + else + value + end + end end end end diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb index bad9e4f9a9..d49af603bb 100644 --- a/activemodel/lib/active_model/validations/clusivity.rb +++ b/activemodel/lib/active_model/validations/clusivity.rb @@ -30,14 +30,15 @@ module ActiveModel @delimiter ||= options[:in] || options[:within] end - # In Ruby 1.9 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all + # In Ruby 2.2 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all # possible values in the range for equality, which is slower but more accurate. # <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range - # endpoints, which is fast but is only accurate on Numeric, Time, or DateTime ranges. + # endpoints, which is fast but is only accurate on Numeric, Time, Date, + # or DateTime ranges. def inclusion_method(enumerable) if enumerable.is_a? Range case enumerable.first - when Numeric, Time, DateTime + when Numeric, Time, DateTime, Date :cover? else :include? diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index a5ac055033..26ba6ba4fb 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -128,6 +128,13 @@ class ErrorsTest < ActiveModel::TestCase assert !person.errors.include?(:foo) end + test "include? does not add a key to messages hash" do + person = Person.new + person.errors.include?(:foo) + + assert_not person.errors.messages.key?(:foo) + end + test "adding errors using conditionals with Person#validate!" do person = Person.new person.validate! diff --git a/activemodel/test/cases/type/decimal_test.rb b/activemodel/test/cases/type/decimal_test.rb index 353dbf84ad..1950566c0e 100644 --- a/activemodel/test/cases/type/decimal_test.rb +++ b/activemodel/test/cases/type/decimal_test.rb @@ -52,6 +52,13 @@ module ActiveModel assert_not type.changed?(5.0, 5.0, '5.0') assert_not type.changed?(-5.0, -5.0, '-5.0') end + + def test_scale_is_applied_before_precision_to_prevent_rounding_errors + type = Decimal.new(precision: 5, scale: 3) + + assert_equal BigDecimal("1.250"), type.cast(1.250473853637869) + assert_equal BigDecimal("1.250"), type.cast("1.250473853637869") + end end end end diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb index 6cb96343fa..9bd44175a6 100644 --- a/activemodel/test/cases/validations/inclusion_validation_test.rb +++ b/activemodel/test/cases/validations/inclusion_validation_test.rb @@ -21,24 +21,38 @@ class InclusionValidationTest < ActiveModel::TestCase end def test_validates_inclusion_of_time_range - Topic.validates_inclusion_of(:created_at, in: 1.year.ago..Time.now) + range_begin = 1.year.ago + range_end = Time.now + Topic.validates_inclusion_of(:created_at, in: range_begin..range_end) assert Topic.new(title: 'aaa', created_at: 2.years.ago).invalid? assert Topic.new(title: 'aaa', created_at: 3.months.ago).valid? assert Topic.new(title: 'aaa', created_at: 37.weeks.from_now).invalid? + assert Topic.new(title: 'aaa', created_at: range_begin).valid? + assert Topic.new(title: 'aaa', created_at: range_end).valid? end def test_validates_inclusion_of_date_range - Topic.validates_inclusion_of(:created_at, in: 1.year.until(Date.today)..Date.today) + range_begin = 1.year.until(Date.today) + range_end = Date.today + Topic.validates_inclusion_of(:created_at, in: range_begin..range_end) assert Topic.new(title: 'aaa', created_at: 2.years.until(Date.today)).invalid? assert Topic.new(title: 'aaa', created_at: 3.months.until(Date.today)).valid? assert Topic.new(title: 'aaa', created_at: 37.weeks.since(Date.today)).invalid? + assert Topic.new(title: 'aaa', created_at: 1.year.until(Date.today)).valid? + assert Topic.new(title: 'aaa', created_at: Date.today).valid? + assert Topic.new(title: 'aaa', created_at: range_begin).valid? + assert Topic.new(title: 'aaa', created_at: range_end).valid? end def test_validates_inclusion_of_date_time_range - Topic.validates_inclusion_of(:created_at, in: 1.year.until(DateTime.current)..DateTime.current) + range_begin = 1.year.until(DateTime.current) + range_end = DateTime.current + Topic.validates_inclusion_of(:created_at, in: range_begin..range_end) assert Topic.new(title: 'aaa', created_at: 2.years.until(DateTime.current)).invalid? assert Topic.new(title: 'aaa', created_at: 3.months.until(DateTime.current)).valid? assert Topic.new(title: 'aaa', created_at: 37.weeks.since(DateTime.current)).invalid? + assert Topic.new(title: 'aaa', created_at: range_begin).valid? + assert Topic.new(title: 'aaa', created_at: range_end).valid? end def test_validates_inclusion_of diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f854c106e8..d59101d621 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,6 +1,40 @@ +* Delegate `empty?`, `none?` and `one?`. Now they can be invoked as model class methods. + + Example: + + # When no record is found on the table + Topic.empty? # => true + Topic.none? # => true + + # When only one record is found on the table + Topic.one? # => true + + *Kenta Shirai* + +* The form builder now properly displays values when passing a proc form + default to the attributes API. + + Fixes #24249. + + *Sean Griffin* + +* The schema cache is now cleared after the `db:migrate` task is run. + + Closes #24273. + + *Chris Arcand* + +* MySQL: strict mode respects other SQL modes rather than overwriting them. + Setting `strict: true` adds `STRICT_ALL_TABLES` to `sql_mode`. Setting + `strict: false` removes `STRICT_TRANS_TABLES`, `STRICT_ALL_TABLES`, and + `TRADITIONAL` from `sql_mode`. + + *Ryuta Kamizono* + * Execute default_scope defined by abstract class in the context of subclass. - Fixes #23413 & #10658 + Fixes #23413. + Fixes #10658. *Mehmet Emin İNAÇ* @@ -523,13 +557,13 @@ * Add option to index errors in nested attributes For models which have nested attributes, errors within those models will - now be indexed if :index_errors is specified when defining a + now be indexed if `:index_errors` is specified when defining a has_many relationship, or if its set in the global config. Example: class Guitar < ActiveRecord::Base - has_many :tuning_pegs + has_many :tuning_pegs, index_errors: true accepts_nested_attributes_for :tuning_pegs end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index e13fe33b85..77d17fc975 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -504,7 +504,7 @@ module ActiveRecord # # == Customizing the query # - # \Associations are built from <tt>Relation</tt>s, and you can use the Relation syntax + # \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax # to customize them. For example, to add a condition: # # class Blog < ActiveRecord::Base diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb index 6dbd92ce28..4580813364 100644 --- a/activerecord/lib/active_record/attribute/user_provided_default.rb +++ b/activerecord/lib/active_record/attribute/user_provided_default.rb @@ -4,20 +4,25 @@ module ActiveRecord class Attribute # :nodoc: class UserProvidedDefault < FromUser # :nodoc: def initialize(name, value, type, database_default) + @user_provided_value = value super(name, value, type, database_default) end - def type_cast(value) - if value.is_a?(Proc) - super(value.call) + def value_before_type_cast + if user_provided_value.is_a?(Proc) + @memoized_value_before_type_cast ||= user_provided_value.call else - super + @user_provided_value end end def with_type(type) - self.class.new(name, value_before_type_cast, type, original_attribute) + self.class.new(name, user_provided_value, type, original_attribute) end + + protected + + attr_reader :user_provided_value end end end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index ebaaa54b2b..e160460286 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -104,7 +104,7 @@ module ActiveRecord To silence this deprecation warning, add the following: - config.active_record.time_zone_aware_types << :time + config.active_record.time_zone_aware_types = [:datetime, :time] MESSAGE end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 7ed2fe48be..6a1a27ce41 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -169,7 +169,8 @@ module ActiveRecord #:nodoc: # ActiveRecord::RecordNotFound error if they do not return any records, # like <tt>Person.find_by_last_name!</tt>. # - # It's also possible to use multiple attributes in the same find by separating them with "_and_". + # It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with + # "_and_". # # Person.find_by(user_name: user_name, password: password) # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index a401310ee0..020d9bbdca 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1051,9 +1051,9 @@ module ActiveRecord end # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+. - # Additional options (like <tt>null: false</tt>) are forwarded to #add_column. + # Additional options (like +:null+) are forwarded to #add_column. # - # add_timestamps(:suppliers, null: false) + # add_timestamps(:suppliers, null: true) # def add_timestamps(table_name, options = {}) options[:null] = false if options[:null].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 22b71fbaba..e31d2237eb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -846,9 +846,19 @@ module ActiveRecord # Make MySQL reject illegal values rather than truncating or blanking them, see # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables # If the user has provided another value for sql_mode, don't replace it. - unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict]) - variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : '' + if sql_mode = variables.delete('sql_mode') + sql_mode = quote(sql_mode) + elsif !defaults.include?(strict_mode?) + if strict_mode? + sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')" + else + sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')" + end + sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')" end + sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode # NAMES does not have an equals sign, see # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430 @@ -870,7 +880,7 @@ module ActiveRecord end.compact.join(', ') # ...and send them all in one query - @connection.query "SET #{encoding} #{variable_assignments}" + @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}" end def column_definitions(table_name) # :nodoc: diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 4419a7b1e7..245c05f3e0 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -528,7 +528,7 @@ module ActiveRecord name = "V#{version.tr('.', '_')}" unless Compatibility.const_defined?(name) versions = Compatibility.constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete('V').tr('_', '.').inspect } - raise "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}" + raise ArgumentError, "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}" end Compatibility.const_get(name) end diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 09d55adcd7..a20d7e0820 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -57,7 +57,7 @@ module ActiveRecord def index_exists?(table_name, column_name, options = {}) column_names = Array(column_name).map(&:to_s) options[:name] = - if options.key?(:name).present? + if options[:name].present? options[:name].to_s else index_name(table_name, column: column_names) diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index ee52c3ae02..52eab952e1 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -231,6 +231,18 @@ module ActiveRecord @explicit_sequence_name = true end + # Determines if the primary key values should be selected from their + # corresponding sequence before the insert statement. + def prefetch_primary_key? + connection.prefetch_primary_key?(table_name) + end + + # Returns the next value that will be used as the primary key on + # an insert statment. + def next_sequence_value + connection.next_sequence_value(sequence_name) + end + # Indicates whether the table associated with this class exists def table_exists? connection.schema_cache.data_source_exists?(table_name) diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index de5b42e987..4e32d73001 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -1,6 +1,6 @@ module ActiveRecord module Querying - delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all + delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :empty?, :none?, :one?, to: :all delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 4c074c93ed..98ea425d16 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -65,7 +65,6 @@ module ActiveRecord ActiveSupport.on_load(:active_record) do self.time_zone_aware_attributes = true self.default_timezone = :utc - self.time_zone_aware_types = ActiveRecord::Base.time_zone_aware_types end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 09afdc6c69..777b593812 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -45,8 +45,8 @@ module ActiveRecord k.name == primary_key }] - if !primary_key_value && connection.prefetch_primary_key?(klass.table_name) - primary_key_value = connection.next_sequence_value(klass.sequence_name) + if !primary_key_value && klass.prefetch_primary_key? + primary_key_value = klass.next_sequence_value values[arel_attribute(klass.primary_key)] = primary_key_value end end diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 261a6a264f..8881986f1b 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -159,6 +159,7 @@ module ActiveRecord Migrator.migrate(migrations_paths, version) do |migration| scope.blank? || scope == migration.scope end + ActiveRecord::Base.clear_cache! ensure Migration.verbose = verbose_was end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index a9f1993842..4f389e9249 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -23,6 +23,12 @@ module ActiveRecord end end + def test_create_record_with_pk_as_zero + Book.create(id: 0) + assert_equal 0, Book.find(0).id + assert_nothing_raised { Book.destroy(0) } + end + def test_tables tables = nil ActiveSupport::Deprecation.silence { tables = @connection.tables } diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 575138eb2a..c4715393b3 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -69,40 +69,48 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end def test_mysql_default_in_strict_mode - result = @connection.exec_query "SELECT @@SESSION.sql_mode" - assert_equal [["STRICT_ALL_TABLES"]], result.rows + result = @connection.select_value("SELECT @@SESSION.sql_mode") + assert_match %r(STRICT_ALL_TABLES), result end def test_mysql_strict_mode_disabled run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.merge({:strict => false})) - result = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode" - assert_equal [['']], result.rows + ActiveRecord::Base.establish_connection(orig_connection.merge(strict: false)) + result = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.sql_mode") + assert_no_match %r(STRICT_ALL_TABLES), result + end + end + + def test_mysql_strict_mode_specified_default + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.merge(strict: :default)) + global_sql_mode = ActiveRecord::Base.connection.select_value("SELECT @@GLOBAL.sql_mode") + session_sql_mode = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.sql_mode") + assert_equal global_sql_mode, session_sql_mode + end + end + + def test_mysql_sql_mode_variable_overrides_strict_mode + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' })) + result = ActiveRecord::Base.connection.select_value('SELECT @@SESSION.sql_mode') + assert_no_match %r(STRICT_ALL_TABLES), result end end def test_passing_arbitary_flags_to_adapter - run_without_connection do |orig_connection| + run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.merge({flags: Mysql2::Client::COMPRESS})) assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags] end end - + def test_passing_flags_by_array_to_adapter - run_without_connection do |orig_connection| + run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.merge({flags: ['COMPRESS'] })) assert_equal ["COMPRESS", "FOUND_ROWS"], ActiveRecord::Base.connection.raw_connection.query_options[:flags] end end - - def test_mysql_strict_mode_specified_default - run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.merge({strict: :default})) - global_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.sql_mode" - session_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode" - assert_equal global_sql_mode.rows, session_sql_mode.rows - end - end def test_mysql_set_session_variable run_without_connection do |orig_connection| @@ -112,14 +120,6 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end end - def test_mysql_sql_mode_variable_overrides_strict_mode - run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' })) - result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode' - assert_not_equal [['STRICT_ALL_TABLES']], result.rows - end - end - def test_mysql_set_session_variable_to_default run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}})) diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb index 2991ca8b76..2bebbfa205 100644 --- a/activerecord/test/cases/attributes_test.rb +++ b/activerecord/test/cases/attributes_test.rb @@ -135,6 +135,17 @@ module ActiveRecord assert_equal 2, klass.new.counter end + test "procs are memoized before type casting" do + klass = Class.new(OverloadedType) do + @@counter = 0 + attribute :counter, :integer, default: -> { @@counter += 1 } + end + + model = klass.new + assert_equal 1, model.counter_before_type_cast + assert_equal 1, model.counter_before_type_cast + end + test "user provided defaults are persisted even if unchanged" do model = OverloadedType.create! diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 6a6250eec3..5a6d2ce80c 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1107,4 +1107,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase ActiveRecord::Base.logger = old end + def test_unknown_migration_version_should_raise_an_argument_error + assert_raise(ArgumentError) { ActiveRecord::Migration[1.0] } + end end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index acba97bbb8..96c94eefa0 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -544,4 +544,24 @@ class NamedScopingTest < ActiveRecord::TestCase assert_equal 1, SpecialComment.where(body: 'go crazy').created.count end + def test_model_class_should_respond_to_empty + assert !Topic.empty? + Topic.delete_all + assert Topic.empty? + end + + def test_model_class_should_respond_to_none + assert !Topic.none? + Topic.delete_all + assert Topic.none? + end + + def test_model_class_should_respond_to_one + assert !Topic.one? + Topic.delete_all + assert !Topic.one? + Topic.create! + assert Topic.one? + end + end diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 429aeca1d9..0aac5bad31 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -309,19 +309,30 @@ module ActiveRecord end class DatabaseTasksMigrateTest < ActiveRecord::TestCase + def setup + ActiveRecord::Tasks::DatabaseTasks.migrations_paths = 'custom/path' + end + + def teardown + ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil + end + def test_migrate_receives_correct_env_vars verbose, version = ENV['VERBOSE'], ENV['VERSION'] - ActiveRecord::Tasks::DatabaseTasks.migrations_paths = 'custom/path' ENV['VERBOSE'] = 'false' ENV['VERSION'] = '4' ActiveRecord::Migrator.expects(:migrate).with('custom/path', 4) ActiveRecord::Tasks::DatabaseTasks.migrate ensure - ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil ENV['VERBOSE'], ENV['VERSION'] = verbose, version end + + def test_migrate_clears_schema_cache_afterward + ActiveRecord::Base.expects(:clear_cache!) + ActiveRecord::Tasks::DatabaseTasks.migrate + end end class DatabaseTasksPurgeTest < ActiveRecord::TestCase diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 01dbf2cee6..2bcdc8729e 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -1,10 +1,4 @@ ActiveRecord::Schema.define do - def except(adapter_names_to_exclude) - unless [adapter_names_to_exclude].flatten.include?(adapter_name) - yield - end - end - # ------------------------------------------------------------------- # # # # Please keep these create table statements in alphabetical order # @@ -991,7 +985,7 @@ ActiveRecord::Schema.define do create_table :records, force: true do |t| end - except 'SQLite' do + if supports_foreign_keys? # fk_test_has_fk should be before fk_test_has_pk create_table :fk_test_has_fk, force: true do |t| t.integer :fk_id, null: false diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 15dc9946c5..dd1b347e73 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,7 @@ +* Add `String#upcase_first` method. + + *Glauco Custódio* + * Prevent `Marshal.load` from looping infinitely when trying to autoload a constant which resolves to a different name. @@ -7,7 +11,7 @@ *Yuichiro Kaneko* -* Publish ActiveSupport::Executor and ActiveSupport::Reloader APIs to allow +* Publish `ActiveSupport::Executor` and `ActiveSupport::Reloader` APIs to allow components and libraries to manage, and participate in, the execution of application code, and the application reloading process. @@ -25,7 +29,7 @@ *Tara Scherner de la Fuente* -* Make `benchmark('something', silence: true)` actually work +* Make `benchmark('something', silence: true)` actually work. *DHH* @@ -46,7 +50,7 @@ ## Rails 5.0.0.beta2 (February 01, 2016) ## -* Change number_to_currency behavior for checking negativity. +* Change `number_to_currency` behavior for checking negativity. Used `to_f.negative` instead of using `to_f.phase` for checking negativity of a number in number_to_currency helper. diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 76825862d7..567ac825e9 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -27,7 +27,7 @@ class Module # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>. # # module HairColors - # mattr_writer :hair_colors, instance_reader: false + # mattr_reader :hair_colors, instance_reader: false # end # # class Person @@ -40,7 +40,7 @@ class Module # Also, you can pass a block to set up the attribute with a default value. # # module HairColors - # cattr_reader :hair_colors do + # mattr_reader :hair_colors do # [:brown, :black, :blonde, :red] # end # end diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index cc71b8155d..f301eeac43 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -222,6 +222,13 @@ class String ActiveSupport::Inflector.humanize(self, options) end + # Converts just the first character to uppercase. + # + # 'what a Lovely Day'.upcase_first # => "What a Lovely Day" + def upcase_first + ActiveSupport::Inflector.upcase_first(self) + end + # Creates a foreign key name from a class name. # +separate_class_name_and_id_with_underscore+ sets whether # the method should put '_' between the name and 'id'. diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index f741c0bfb8..3fbc19ddf8 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -140,6 +140,13 @@ module ActiveSupport result end + # Converts just the first character to uppercase. + # + # 'what a Lovely Day'.upcase_first # => "What a Lovely Day" + def upcase_first(string) + string[0].upcase.concat(string[1..-1]) + end + # Capitalizes all the words and replaces some characters in the string to # create a nicer looking title. +titleize+ is meant for creating pretty # output. It is not used in the Rails internals. diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 2e69816364..5c0b60039a 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -76,6 +76,10 @@ class StringInflectionsTest < ActiveSupport::TestCase end end + def test_upcase_first + assert_equal "What a Lovely Day", "what a Lovely Day".upcase_first + end + def test_camelize CamelToUnderscore.each do |camel, underscore| assert_equal(camel, underscore.camelize) diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 8f664d4215..a868c5f856 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -94,6 +94,10 @@ Please refer to the [Changelog][railties] for detailed changes. * Deprecated `config.serve_static_files` in favor of `config.public_file_server.enabled`. ([Pull Request](https://github.com/rails/rails/pull/22173)) +* Deprecated the tasks in the `rails` task namespace in favor of the `app` namespace. + (e.g. `rails:update` and `rails:template` tasks is renamed to `app:update` and `app:template`.) + ([Pull Request](https://github.com/rails/rails/pull/23439)) + ### Notable changes * Added Rails test runner `bin/rails test`. @@ -120,6 +124,23 @@ Please refer to the [Changelog][railties] for detailed changes. ([Pull Request](https://github.com/rails/rails/pull/22457), [Pull Request](https://github.com/rails/rails/pull/22288)) +* New applications are generated with the evented file system monitor enabled + on Linux and Mac OS X. The feature can be opted out by passing + `--skip-listen` to the generator. + ([commit](https://github.com/rails/rails/commit/de6ad5665d2679944a9ee9407826ba88395a1003), + [commit](https://github.com/rails/rails/commit/94dbc48887bf39c241ee2ce1741ee680d773f202)) + +* Generate applications with an option to log to STDOUT in production + using the environment variable `RAILS_LOG_TO_STDOUT`. + ([Pull Request](https://github.com/rails/rails/pull/23734)) + +* Enable HSTS with IncludeSudomains header for new applications. + ([Pull Request](https://github.com/rails/rails/pull/23852)) + +* The application generator writes a new file `config/spring.rb`, which tells + Spring to watch additional common files. + ([commit](https://github.com/rails/rails/commit/b04d07337fd7bc17e88500e9d6bcd361885a45f8)) + Action Pack ----------- @@ -262,6 +283,17 @@ Please refer to the [Changelog][action-pack] for detailed changes. (Pull Request [1](https://github.com/rails/rails/pull/19377), [2](https://github.com/rails/rails/pull/23827)) +* Added an option for per-form CSRF tokens. + ([Pull Request](https://github.com/rails/rails/pull/22275)) + +* Added request encoding and response parsing to integration tests. + ([Pull Request](https://github.com/rails/rails/pull/21671)) + +* Update default rendering policies when the controller action did + not explicitly indicate a response. + ([Pull Request](https://github.com/rails/rails/pull/23827)) + + Action View ------------- @@ -286,8 +318,9 @@ Please refer to the [Changelog][action-view] for detailed changes. * Changed the default template handler from `ERB` to `Raw`. ([commit](https://github.com/rails/rails/commit/4be859f0fdf7b3059a28d03c279f03f5938efc80)) -* Collection rendering automatically caches and fetches multiple partials. - ([Pull Request](https://github.com/rails/rails/pull/18948)) +* Collection rendering can cache and fetches multiple partials. + ([Pull Request](https://github.com/rails/rails/pull/18948), + [commit](https://github.com/rails/rails/commit/e93f0f0f133717f9b06b1eaefd3442bd0ff43985)) * Allow defining explicit collection caching using a `# Template Collection: ...` directive inside templates. @@ -300,6 +333,9 @@ Please refer to the [Changelog][action-view] for detailed changes. button on submit to prevent double submits. ([Pull Request](https://github.com/rails/rails/pull/21135)) +* Collection rendering can cache and fetch multiple partials at once. + ([Pull Request](https://github.com/rails/rails/pull/21135)) + Action Mailer ------------- @@ -333,7 +369,9 @@ Please refer to the [Changelog][action-mailer] for detailed changes. the mailer queue name. ([Pull Request](https://github.com/rails/rails/pull/18587)) -* Added `config.action_mailer.perform_caching` configuration to determine whether your templates should perform caching or not. +* Added support for fragment caching in Action Mailer views. + Added new config option `config.action_mailer.perform_caching` to determine + whether your templates should perform caching or not. ([Pull Request](https://github.com/rails/rails/pull/22825)) @@ -453,6 +491,10 @@ Please refer to the [Changelog][active-record] for detailed changes. `offset` method on relation instead. ([Pull Request](https://github.com/rails/rails/pull/22053)) +* Deprecated `{insert|update|delete}_sql` in `DatabaseStatements`. + Use the `{insert|update|delete}` public methods instead. + ([Pull Request](https://github.com/rails/rails/pull/23086)) + ### Notable changes * Added a `foreign_key` option to `references` while creating the table. @@ -554,6 +596,9 @@ Please refer to the [Changelog][active-record] for detailed changes. model behavior. ([Pull Request](https://github.com/rails/rails/pull/22567)) +* Added ActiveRecord `#second_to_last` and `#third_to_last` methods. + ([Pull Request](https://github.com/rails/rails/pull/23583)) + Active Model ------------ @@ -631,6 +676,10 @@ Please refer to the [Changelog][active-job] for detailed changes. queue jobs to a `concurrent-ruby` thread pool. ([Pull Request](https://github.com/rails/rails/pull/21257)) +* Change the default adapter from inline to async. It's a better default as + tests will then not mistakenly come to rely on behavior happening + synchronously. + ([commit](https://github.com/rails/rails/commit/625baa69d14881ac49ba2e5c7d9cac4b222d7022)) Active Support -------------- @@ -699,6 +748,13 @@ Please refer to the [Changelog][active-support] for detailed changes. Deprecated `ActiveSupport::Cache::LocaleCache#set_cache_value` in favor of `write_cache_value`. ([Pull Request](https://github.com/rails/rails/pull/22215)) +* Deprecated passing arguments to `assert_nothing_raised`. + ([Pull Request](https://github.com/rails/rails/pull/23789)) + +* Deprecated `Module.local_constants` in favor of `Module.constants(false)`. + ([Pull Request](https://github.com/rails/rails/pull/23936)) + + ### Notable changes * Added `#verified` and `#valid_message?` methods to @@ -768,6 +824,17 @@ Please refer to the [Changelog][active-support] for detailed changes. class and module variables that live per-thread. ([Pull Request](https://github.com/rails/rails/pull/22630)) +* Added `Array#second_to_last` and `Array#third_to_last` methods. + ([Pull Request](https://github.com/rails/rails/pull/23583)) + +* Added `#on_weekday?` method to `Date`, `Time`, and `DateTime`. + ([Pull Request](https://github.com/rails/rails/pull/23687)) + +* Publish `ActiveSupport::Executor` and `ActiveSupport::Reloader` APIs to allow + components and libraries to manage, and participate in, the execution of + application code, and the application reloading process. + ([Pull Request](https://github.com/rails/rails/pull/23807)) + Credits ------- diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index af15d4870c..9d349691b4 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -153,9 +153,9 @@ You can pass in a numerical argument to the `take` method to return up to that n ```ruby client = Client.take(2) # => [ - #<Client id: 1, first_name: "Lifo">, - #<Client id: 220, first_name: "Sara"> -] +# #<Client id: 1, first_name: "Lifo">, +# #<Client id: 220, first_name: "Sara"> +# ] ``` The SQL equivalent of the above is: @@ -192,10 +192,10 @@ You can pass in a numerical argument to the `first` method to return up to that ```ruby client = Client.first(3) # => [ - #<Client id: 1, first_name: "Lifo">, - #<Client id: 2, first_name: "Fifo">, - #<Client id: 3, first_name: "Filo"> -] +# #<Client id: 1, first_name: "Lifo">, +# #<Client id: 2, first_name: "Fifo">, +# #<Client id: 3, first_name: "Filo"> +# ] ``` The SQL equivalent of the above is: @@ -243,10 +243,10 @@ You can pass in a numerical argument to the `last` method to return up to that n ```ruby client = Client.last(3) # => [ - #<Client id: 219, first_name: "James">, - #<Client id: 220, first_name: "Sara">, - #<Client id: 221, first_name: "Russel"> -] +# #<Client id: 219, first_name: "James">, +# #<Client id: 220, first_name: "Sara">, +# #<Client id: 221, first_name: "Russel"> +# ] ``` The SQL equivalent of the above is: @@ -1613,7 +1613,7 @@ now want the client named 'Nick': ```ruby nick = Client.find_or_initialize_by(first_name: 'Nick') -# => <Client id: nil, first_name: "Nick", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27"> +# => #<Client id: nil, first_name: "Nick", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27"> nick.persisted? # => false @@ -1645,10 +1645,10 @@ Client.find_by_sql("SELECT * FROM clients INNER JOIN orders ON clients.id = orders.client_id ORDER BY clients.created_at desc") # => [ - #<Client id: 1, first_name: "Lucas" >, - #<Client id: 2, first_name: "Jan" >, - # ... -] +# #<Client id: 1, first_name: "Lucas" >, +# #<Client id: 2, first_name: "Jan" >, +# ... +# ] ``` `find_by_sql` provides you with a simple way of making custom calls to the database and retrieving instantiated objects. @@ -1660,9 +1660,9 @@ Client.find_by_sql("SELECT * FROM clients ```ruby Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'") # => [ - {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, - {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"} -] +# {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, +# {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"} +# ] ``` ### `pluck` diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 10bd201145..baaebd21c8 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -505,6 +505,8 @@ constraints to acceptable values: * `:less_than_or_equal_to` - Specifies the value must be less than or equal to the supplied value. The default error message for this option is _"must be less than or equal to %{count}"_. +* `:other_than` - Specifies the value must be other than the supplied value. + The default error message for this option is _"must be other than %{count}"_. * `:odd` - Specifies the value must be an odd number if set to true. The default error message for this option is _"must be odd"_. * `:even` - Specifies the value must be an even number if set to true. The @@ -830,6 +832,25 @@ class Person < ApplicationRecord end ``` +You can also use `on:` to define custom context. +Custom contexts need to be triggered explicitly +by passing name of the context to `valid?`, `invalid?` or `save`. + +```ruby +class Person < ApplicationRecord + validates :email, uniqueness: true, on: :account_setup + validates :age, numericality: true, on: :account_setup +end + +person = Person.new +``` + +`person.valid?(:account_setup)` executes both the validations +without saving the model. And `person.save(context: :account_setup)` +validates `person` in `account_setup` context before saving. +On explicit triggers, model is validated by +validations of only that context and validations without context. + Strict Validations ------------------ diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 67fd758fe1..4977d4f30e 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -545,12 +545,12 @@ author.books.size # uses the cached copy of books author.books.empty? # uses the cached copy of books ``` -But what if you want to reload the cache, because data might have been changed by some other part of the application? Just pass `true` to the association call: +But what if you want to reload the cache, because data might have been changed by some other part of the application? Just call `reload` on the association: ```ruby author.books # retrieves books from the database author.books.size # uses the cached copy of books -author.books(true).empty? # discards the cached copy of books +author.books.reload.empty? # discards the cached copy of books # and goes back to the database ``` diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 7beb8f72a9..cc24e6f666 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -30,7 +30,6 @@ Ruby on Rails uses Git for source code control. The [Git homepage](http://git-sc * [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. -* The [PeepCode screencast](https://peepcode.com/products/git) on Git is easier to follow. * [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. diff --git a/guides/source/security.md b/guides/source/security.md index f4a9f64669..4883c1abe7 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -160,7 +160,7 @@ The most effective countermeasure is to _issue a new session identifier_ and dec reset_session ``` -If you use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_. +If you use the popular [Devise](https://rubygems.org/gems/devise) gem for user management, it will automatically expire sessions on sign in and sign out for you. If you roll your own, remember to expire the session after your sign in action (when the session is created). This will remove values from the session, therefore _you will have to transfer them to the new session_. Another countermeasure is to _save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way. diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index a4eaf0ab0f..52c2e0743c 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,8 @@ +* Make `rails restart` command work with Puma by passing the restart command + which Puma can use to restart rails server. + + *Prathamesh Sonpatki* + * The application generator writes a new file `config/spring.rb`, which tells Spring to watch additional common files. @@ -42,7 +47,9 @@ *Xavier Noria* -* Add dummy files for apple-touch-icon.png and apple-touch-icon.png. GH#23427 +* Add dummy files for apple-touch-icon.png and apple-touch-icon.png. + + See #23427. *Alexey Zabelin* diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index fc8717c752..7a8f42fe94 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -1,4 +1,5 @@ require 'rails/code_statistics_calculator' +require 'active_support/core_ext/enumerable' class CodeStatistics #:nodoc: diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index 5844e9037c..f9c183ac86 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -2,6 +2,7 @@ require 'optparse' options = { environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup } code_or_file = nil +command = 'bin/rails runner' if ARGV.first.nil? ARGV.push "-h" @@ -34,7 +35,7 @@ ARGV.clone.options do |opts| opts.separator "" opts.separator "You can also use runner as a shebang line for your executables:" opts.separator " -------------------------------------------------------------" - opts.separator " #!/usr/bin/env #{File.expand_path($0)} runner" + opts.separator " #!/usr/bin/env #{File.expand_path(command)}" opts.separator "" opts.separator " Product.all.each { |p| p.price *= 2 ; p.save! }" opts.separator " -------------------------------------------------------------" @@ -52,7 +53,7 @@ Rails.application.require_environment! Rails.application.load_runner if code_or_file.nil? - $stderr.puts "Run '#{$0} -h' for help." + $stderr.puts "Run '#{command} -h' for help." exit 1 elsif File.exist?(code_or_file) $0 = code_or_file @@ -62,7 +63,7 @@ else eval(code_or_file, binding, __FILE__, __LINE__) rescue SyntaxError, NameError $stderr.puts "Please specify a valid ruby command or the path of a script to run." - $stderr.puts "Run '#{$0} -h' for help." + $stderr.puts "Run '#{command} -h' for help." exit 1 end end diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index c2434d62e2..7418dff18b 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -94,7 +94,8 @@ module Rails environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup, daemonize: false, caching: nil, - pid: Options::DEFAULT_PID_PATH + pid: Options::DEFAULT_PID_PATH, + restart_cmd: restart_command }) end @@ -130,5 +131,9 @@ module Rails Rails.logger.extend(ActiveSupport::Logger.broadcast(console)) end end + + def restart_command + "bin/rails server #{ARGV.join(' ')}" + end end end diff --git a/railties/lib/rails/dev_caching.rb b/railties/lib/rails/dev_caching.rb index 4760010851..3c20164f0f 100644 --- a/railties/lib/rails/dev_caching.rb +++ b/railties/lib/rails/dev_caching.rb @@ -15,6 +15,7 @@ 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/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index d6be69e16c..f58e6ba653 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -95,13 +95,10 @@ module Rails action_cable_config_exist = File.exist?('config/cable.yml') ssl_options_exist = File.exist?('config/initializers/ssl_options.rb') rack_cors_config_exist = File.exist?('config/initializers/cors.rb') - development_config_exist = File.exist?('config/environments/development.rb') config - if development_config_exist - gsub_file 'config/environments/development.rb', /^(\s+)config\.file_watcher/, '\1# config.file_watcher' - end + gsub_file 'config/environments/development.rb', /^(\s+)config\.file_watcher/, '\1# config.file_watcher' unless callback_terminator_config_exist remove_file 'config/initializers/callback_terminator.rb' diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup index df88bfd3bc..acae810c1a 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/setup +++ b/railties/lib/rails/generators/rails/app/templates/bin/setup @@ -15,7 +15,7 @@ chdir APP_ROOT do puts '== Installing dependencies ==' system! 'gem install bundler --conservative' - system('bundle check') or system!('bundle install') + system('bundle check') || system!('bundle install') # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update b/railties/lib/rails/generators/rails/app/templates/bin/update index c6ed3ae64b..770a605fed 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/update +++ b/railties/lib/rails/generators/rails/app/templates/bin/update @@ -15,7 +15,7 @@ chdir APP_ROOT do puts '== Installing dependencies ==' system! 'gem install bundler --conservative' - system 'bundle check' or system! 'bundle install' + system('bundle check') || system!('bundle install') puts "\n== Updating database ==" system! 'bin/rails db:migrate' diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb index d71a021bd2..d03b1be878 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb @@ -6,10 +6,12 @@ require 'rails/all' # Pick the frameworks you want: <%= comment_if :skip_active_record %>require "active_record/railtie" require "action_controller/railtie" -<%= comment_if :skip_action_mailer %>require "action_mailer/railtie" require "action_view/railtie" -<%= comment_if :skip_sprockets %>require "sprockets/railtie" +<%= comment_if :skip_action_mailer %>require "action_mailer/railtie" +require "active_job/railtie" +<%= comment_if :skip_action_cable %>require "action_cable/engine" <%= comment_if :skip_test %>require "rails/test_unit/railtie" +<%= comment_if :skip_sprockets %>require "sprockets/railtie" <% end -%> Bundler.require(*Rails.groups) diff --git a/railties/lib/rails/tasks/restart.rake b/railties/lib/rails/tasks/restart.rake index f36c86d81b..7e15bb55a1 100644 --- a/railties/lib/rails/tasks/restart.rake +++ b/railties/lib/rails/tasks/restart.rake @@ -2,4 +2,5 @@ desc "Restart app by touching tmp/restart.txt" task :restart do FileUtils.mkdir_p('tmp') FileUtils.touch('tmp/restart.txt') + FileUtils.rm_f('tmp/pids/server.pid') end diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb index f22139490b..e9195d5b4e 100644 --- a/railties/lib/rails/test_unit/minitest_plugin.rb +++ b/railties/lib/rails/test_unit/minitest_plugin.rb @@ -84,14 +84,18 @@ module Minitest end # Replace progress reporter for colors. - self.reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) } - self.reporter << SuppressedSummaryReporter.new(options[:io], options) - self.reporter << ::Rails::TestUnitReporter.new(options[:io], options) + reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) } + reporter << SuppressedSummaryReporter.new(options[:io], options) + reporter << ::Rails::TestUnitReporter.new(options[:io], options) end mattr_accessor(:run_with_autorun) { false } mattr_accessor(:run_with_rails_extension) { false } end +# Put Rails as the first plugin minitest initializes so other plugins +# can override or replace our default reporter setup. +# Since minitest only loads plugins if its extensions are empty we have +# to call `load_plugins` first. Minitest.load_plugins -Minitest.extensions << 'rails' +Minitest.extensions.unshift 'rails' diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb index 59b46c6e79..deb9bc8dee 100644 --- a/railties/test/application/rake/dev_test.rb +++ b/railties/test/application/rake/dev_test.rb @@ -29,6 +29,15 @@ module ApplicationTests assert_match(/Development mode is no longer being cached/, output) end end + + test 'dev:cache removes server.pid also' do + Dir.chdir(app_path) do + FileUtils.mkdir_p("tmp/pids") + FileUtils.touch("tmp/pids/server.pid") + `rake dev:cache` + assert_not File.exist?("tmp/pids/server.pid") + end + end end end end diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb index 4cae199e6b..30f662a9be 100644 --- a/railties/test/application/rake/restart_test.rb +++ b/railties/test/application/rake/restart_test.rb @@ -34,6 +34,15 @@ module ApplicationTests assert File.exist?('tmp/restart.txt') end end + + test 'rake restart removes server.pid also' do + Dir.chdir(app_path) do + FileUtils.mkdir_p("tmp/pids") + FileUtils.touch("tmp/pids/server.pid") + `rake restart` + assert_not File.exist?("tmp/pids/server.pid") + end + end end end end diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb index 964b9a44de..38a1605d1f 100644 --- a/railties/test/commands/server_test.rb +++ b/railties/test/commands/server_test.rb @@ -118,4 +118,18 @@ class Rails::ServerTest < ActiveSupport::TestCase assert_equal old_default_options, server.default_options end end + + def test_restart_command_contains_customized_options + original_args = ARGV.dup + args = ["-p", "4567"] + ARGV.replace args + + options = Rails::Server::Options.new.parse! args + server = Rails::Server.new options + expected = "bin/rails server -p 4567" + + assert_equal expected, server.default_options[:restart_cmd] + ensure + ARGV.replace original_args + end end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index cf3ed8405d..17a2c6a327 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -656,6 +656,19 @@ class PluginGeneratorTest < Rails::Generators::TestCase end end + def test_generate_application_mailer_when_does_not_exist_in_mountable_engine + run_generator [destination_root, '--mountable'] + FileUtils.rm "#{destination_root}/app/mailers/bukkits/application_mailer.rb" + capture(:stdout) do + `#{destination_root}/bin/rails g mailer User` + end + + assert_file "#{destination_root}/app/mailers/bukkits/application_mailer.rb" do |mailer| + assert_match(/module Bukkits/, mailer) + assert_match(/class ApplicationMailer < ActionMailer::Base/, mailer) + end + end + def test_after_bundle_callback path = 'http://example.org/rails_template' template = %{ after_bundle { run 'echo ran after_bundle' } } |