From 55cba0dd45aa8cc57d4f2a2e5cf3d662ef4a091a Mon Sep 17 00:00:00 2001 From: Aditya Prakash Date: Tue, 13 Oct 2015 17:32:14 +0530 Subject: Improvement in ActiveRecord::Persistence doc [ci skip] --- activerecord/lib/active_record/persistence.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 94316d5249..917c8131a5 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -102,10 +102,10 @@ module ActiveRecord # Saves the model. # - # If the model is new a record gets created in the database, otherwise + # If the model is new, a record gets created in the database, otherwise # the existing record gets updated. # - # By default, save always run validations. If any of them fail the action + # By default, save always runs validations. If any of them fail the action # is cancelled and #save returns +false+. However, if you supply # validate: false, validations are bypassed altogether. See # ActiveRecord::Validations for more information. @@ -132,9 +132,10 @@ module ActiveRecord # If the model is new, a record gets created in the database, otherwise # the existing record gets updated. # - # With #save! validations always run. If any of them fail - # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations - # for more information. + # By default, #save! always runs validations. If any of them fail + # ActiveRecord::RecordInvalid gets raised. However, if you supply + # validate: false, validations are bypassed altogether. See + # ActiveRecord::Validations for more information. # # By default, #save! also sets the +updated_at+/+updated_on+ attributes to # the current time. However, if you supply touch: false, these @@ -246,7 +247,7 @@ module ActiveRecord # This method raises an ActiveRecord::ActiveRecordError if the # attribute is marked as readonly. # - # See also #update_column. + # Also see #update_column. def update_attribute(name, value) name = name.to_s verify_readonly_attribute(name) @@ -347,7 +348,7 @@ module ActiveRecord end # Wrapper around #decrement that saves the record. This method differs from - # its non-bang version in that it passes through the attribute setter. + # its non-bang version in the sense that it passes through the attribute setter. # Saving is not subjected to validation checks. Returns +true+ if the # record could be saved. def decrement!(attribute, by = 1) @@ -364,7 +365,7 @@ module ActiveRecord end # Wrapper around #toggle that saves the record. This method differs from - # its non-bang version in that it passes through the attribute setter. + # its non-bang version in the sense that it passes through the attribute setter. # Saving is not subjected to validation checks. Returns +true+ if the # record could be saved. def toggle!(attribute) -- cgit v1.2.3 From 3f93888808442c014eab31ba31151daeafa76f1f Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Wed, 23 Dec 2015 17:14:12 +0200 Subject: Fix edge case with ActionView::Template::Error reraise When you re-raise an ActionView::Template::Error, the #cause can change. You can see this behaviour with [nack]. Currently, `web-console` doesn't run the console in the proper binding in the case of errors in the views, because when we follow the `#cause` of the exception it is an [`EOFError`][EOFError]. This also affects [pow] as it runs on [nack]. [nack]: https://github.com/josh/nack [pow]: http://pow.cx/ [EOFError]: https://github.com/josh/nack/blob/d523cc870c0a11dcf349388a15adfecba9314f97/lib/nack/server.rb#L108 --- actionview/lib/action_view/template/error.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index b03b197cb5..ccee785d3e 100644 --- a/actionview/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb @@ -59,6 +59,9 @@ module ActionView class Error < ActionViewError #:nodoc: SOURCE_CODE_RADIUS = 3 + # Override to prevent #cause resetting during re-raise. + attr_reader :cause + def initialize(template, original_exception = nil) if original_exception ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \ @@ -67,6 +70,7 @@ module ActionView super($!.message) set_backtrace($!.backtrace) + @cause = $! @template, @sub_templates = template, nil end -- cgit v1.2.3 From 837e40dcac023319a0bfc38240761d4352b73b99 Mon Sep 17 00:00:00 2001 From: Dave Gynn Date: Sat, 26 Dec 2015 22:25:27 -0800 Subject: restore ability to pass extra options to cache stores The `cache` helper methods should pass any extra options to the cache store. For example :expires_in would be a valid option if memcache was the cache store. The change in commit da16745 broke the ability to pass any options other than :skip_digest and :virtual_path. This PR restores that functionality and adds a test for it. --- actionpack/test/controller/caching_test.rb | 12 ++++++++++++ .../functional_caching/fragment_cached_with_options.html.erb | 3 +++ actionview/lib/action_view/helpers/cache_helper.rb | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index d19b3810c2..74c78dfa8e 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -172,6 +172,9 @@ class FunctionalCachingController < CachingController def fragment_cached_without_digest end + + def fragment_cached_with_options + end end class FunctionalFragmentCachingTest < ActionController::TestCase @@ -215,6 +218,15 @@ CACHED assert_equal "

ERB

", @store.read("views/nodigest") end + def test_fragment_caching_with_options + get :fragment_cached_with_options + assert_response :success + expected_body = "\n

ERB

\n\n" + + assert_equal expected_body, @response.body + assert_equal "

ERB

", @store.read("views/with_options") + end + def test_render_inline_before_fragment_caching get :inline_fragment_cached assert_response :success diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb new file mode 100644 index 0000000000..6865df9b7e --- /dev/null +++ b/actionpack/test/fixtures/functional_caching/fragment_cached_with_options.html.erb @@ -0,0 +1,3 @@ + +<%= cache 'with_options', :skip_digest => true, :expires_in => 1.minute do %>

ERB

<% end %> + diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 18b2102d73..2e30578123 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -208,7 +208,7 @@ module ActionView # # The digest will be generated using +virtual_path:+ if it is provided. # - def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil) + def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil, **_options) if skip_digest name else -- cgit v1.2.3 From 52bb2d36d3141dcd8217221065d9b5fa2b12deba Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Sat, 19 Sep 2015 00:11:18 +0200 Subject: Add `as` to encode a request as a specific mime type. Turns ``` post articles_path(format: :json), params: { article: { name: 'Ahoy!' } }.to_json, headers: { 'Content-Type' => 'application/json' } ``` into ``` post articles_path, params: { article: { name: 'Ahoy!' } }, as: :json ``` --- .../lib/action_dispatch/testing/integration.rb | 83 ++++++++++++++++++++-- actionpack/test/controller/integration_test.rb | 43 +++++++++++ 2 files changed, 122 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 711ca10419..cade3bf6b3 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -321,7 +321,9 @@ module ActionDispatch end # Performs the actual request. - def process(method, path, params: nil, headers: nil, env: nil, xhr: false) + def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil) + request_encoder = RequestEncoder.encoder(as) + if path =~ %r{://} location = URI.parse(path) https! URI::HTTPS === location if location.scheme @@ -330,14 +332,17 @@ module ActionDispatch url_host += ":#{location.port}" if default != location.port host! url_host end - path = location.query ? "#{location.path}?#{location.query}" : location.path + path = request_encoder.append_format_to location.path + path = location.query ? "#{path}?#{location.query}" : path + else + path = request_encoder.append_format_to path end hostname, port = host.split(':') request_env = { :method => method, - :params => params, + :params => request_encoder.encode_params(params), "SERVER_NAME" => hostname, "SERVER_PORT" => port || (https? ? "443" : "80"), @@ -347,7 +352,7 @@ module ActionDispatch "REQUEST_URI" => path, "HTTP_HOST" => host, "REMOTE_ADDR" => remote_addr, - "CONTENT_TYPE" => "application/x-www-form-urlencoded", + "CONTENT_TYPE" => request_encoder.content_type, "HTTP_ACCEPT" => accept } @@ -387,6 +392,48 @@ module ActionDispatch def build_full_uri(path, env) "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}" end + + class RequestEncoder # :nodoc: + @encoders = {} + + def initialize(mime_name, param_encoder, url_encoded_form = false) + @mime = Mime[mime_name] + + unless @mime + raise ArgumentError, "Can't register a request encoder for " \ + "unregistered MIME Type: #{mime_name}. See `Mime::Type.register`." + end + + @url_encoded_form = url_encoded_form + @path_format = ".#{@mime.symbol}" unless @url_encoded_form + @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc + end + + def append_format_to(path) + path << @path_format unless @url_encoded_form + path + end + + def content_type + @mime.to_s + end + + def encode_params(params) + @param_encoder.call(params) + end + + def self.encoder(name) + @encoders[name] || WWWFormEncoder + end + + def self.register_encoder(mime_name, ¶m_encoder) + @encoders[mime_name] = new(mime_name, param_encoder) + end + + register_encoder :json + + WWWFormEncoder = new(:url_encoded_form, -> params { params }, true) + end end module Runner @@ -643,6 +690,30 @@ module ActionDispatch # end # end # + # You can also test your JSON API easily by setting what the request should + # be encoded as: + # + # require 'test_helper' + # + # class ApiTest < ActionDispatch::IntegrationTest + # test "creates articles" do + # assert_difference -> { Article.count } do + # post articles_path, params: { article: { title: 'Ahoy!' } }, as: :json + # end + # + # assert_response :success + # end + # end + # + # The `as` option sets the format to JSON, sets the content type to + # 'application/json' and encodes the parameters as JSON. + # + # For any custom MIME Types you've registered, you can even add your own encoders with: + # + # ActionDispatch::IntegrationTest.register_encoder :wibble do |params| + # params.to_wibble + # end + # # Consult the Rails Testing Guide for more. class IntegrationTest < ActiveSupport::TestCase @@ -671,5 +742,9 @@ module ActionDispatch def document_root_element html_document.root end + + def self.register_encoder(*args, ¶m_encoder) + Integration::Session::RequestEncoder.register_encoder(*args, ¶m_encoder) + end end end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index d0a1d1285f..296bc1baad 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -1126,3 +1126,46 @@ class IntegrationRequestsWithSessionSetup < ActionDispatch::IntegrationTest assert_equal({"user_name"=>"david"}, cookies.to_hash) end end + +class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest + class FooController < ActionController::Base + def foos + render plain: 'ok' + end + end + + def test_encoding_as_json + assert_encoded_as :json, content_type: 'application/json' + end + + def test_encoding_as_without_mime_registration + assert_raise ArgumentError do + ActionDispatch::IntegrationTest.register_encoder :wibble + end + end + + def test_registering_custom_encoder + Mime::Type.register 'text/wibble', :wibble + + ActionDispatch::IntegrationTest.register_encoder(:wibble, &:itself) + + assert_encoded_as :wibble, content_type: 'text/wibble', + parsed_parameters: Hash.new # Unregistered MIME Type can't be parsed + ensure + Mime::Type.unregister :wibble + end + + private + def assert_encoded_as(format, content_type:, parsed_parameters: { 'foo' => 'fighters' }) + with_routing do |routes| + routes.draw { post ':action' => FooController } + + post '/foos', params: { foo: 'fighters' }, as: format + + assert_response :success + assert_match "foos.#{format}", request.path + assert_equal content_type, request.content_type + assert_equal parsed_parameters, request.request_parameters + end + end +end -- cgit v1.2.3 From 82dc7786297a20a7a32e6bee7884ec1d4335410e Mon Sep 17 00:00:00 2001 From: James Coleman Date: Fri, 22 Jan 2016 10:04:01 -0600 Subject: Clarify DatabaseStatements#execute docs re: memory usage. --- .../connection_adapters/abstract/database_statements.rb | 6 +++++- .../connection_adapters/postgresql/database_statements.rb | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) 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 7a2a1a0e33..7e0c9f7837 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -69,7 +69,11 @@ module ActiveRecord end undef_method :select_rows - # Executes the SQL statement in the context of this connection. + # Executes the SQL statement in the context of this connection and returns + # the raw result from the connection adapter. + # Note: depending on your database connector, the result returned by this + # method may be manually memory managed. Consider using the exec_query + # wrapper instead. def execute(sql, name = nil) end undef_method :execute 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 6c15facf3b..8c7cfae7c1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -128,6 +128,8 @@ module ActiveRecord # Executes an SQL statement, returning a PGresult object on success # or raising a PGError exception otherwise. + # Note: the PGresult object is manually memory managed; if you don't + # need it specifically, you many want consider the exec_query wrapper. def execute(sql, name = nil) log(sql, name) do @connection.async_exec(sql) -- cgit v1.2.3 From 9433dde5d1ff33731e5a928166c1b9192bca92ae Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Sun, 24 Jan 2016 10:10:15 -0500 Subject: Remove empty lines in Rails development logger This is causing bugs like #23215 to occur, due to the extra spaces being inserted. Also, this is fixed upstream in the upcoming release of Sprockets 4. --- railties/lib/rails/rack/logger.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb index 12676b18bc..aa7d3ca6c6 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -30,12 +30,6 @@ module Rails protected def call_app(request, env) - # Put some space between requests in development logs. - if development? - logger.debug '' - logger.debug '' - end - instrumenter = ActiveSupport::Notifications.instrumenter instrumenter.start 'request.action_dispatch', request: request logger.info { started_request_message(request) } -- cgit v1.2.3 From 38492590cf30bae0f4817c610bc0237ccdea7c1a Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Mon, 25 Jan 2016 18:26:20 +0200 Subject: Introduce new welcome page for new projects As requested by David in 23233. --- .../images/getting_started/rails_welcome.png | Bin 142320 -> 1053549 bytes .../rails/templates/rails/welcome/index.html.erb | 326 ++++----------------- railties/test/application/routing_test.rb | 3 +- railties/test/isolation/abstract_unit.rb | 4 +- 4 files changed, 65 insertions(+), 268 deletions(-) diff --git a/guides/assets/images/getting_started/rails_welcome.png b/guides/assets/images/getting_started/rails_welcome.png index 4d0cb417b7..baccb11322 100644 Binary files a/guides/assets/images/getting_started/rails_welcome.png and b/guides/assets/images/getting_started/rails_welcome.png differ diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb index acf04af416..5cdb7e6a20 100644 --- a/railties/lib/rails/templates/rails/welcome/index.html.erb +++ b/railties/lib/rails/templates/rails/welcome/index.html.erb @@ -1,268 +1,64 @@ - - Ruby on Rails: Welcome aboard - - - - -
- - -
- - - - -
-

Getting started

-

Here’s how to get rolling:

- -
    -
  1. -

    Use bin/rails generate to create your models and controllers

    -

    To see all available options, run it without parameters.

    -
  2. - -
  3. -

    Set up a root route to replace this page

    -

    You're seeing this page because you're running in development mode and you haven't set a root route yet.

    -

    Routes are set up in config/routes.rb.

    -
  4. - -
  5. -

    Configure your database

    -

    If you're not using SQLite (the default), edit config/database.yml with your username and password.

    -
  6. -
-
-
- - -
- + + Ruby on Rails + + + + + + +
+
+

+ + Ruby on Rails + +

+ +

Yay! You’re on Rails!

+ + Welcome + +

+ Rails version: <%= Rails.version %>
+ Ruby version: <%= RUBY_VERSION %> (<%= RUBY_PLATFORM %>) +

+
+
+ diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index 0777714d35..e51f32aaed 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -42,8 +42,7 @@ module ApplicationTests test "root takes precedence over internal welcome controller" do app("development") - get '/' - assert_match %r{

Getting started

} , last_response.body + assert_welcome get('/') controller :foo, <<-RUBY class FooController < ApplicationController diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index df3c2ca66d..dddf8bd257 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -74,10 +74,12 @@ module TestHelpers end def assert_welcome(resp) + resp = Array(resp) + assert_equal 200, resp[0] assert_match 'text/html', resp[1]["Content-Type"] assert_match 'charset=utf-8', resp[1]["Content-Type"] - assert extract_body(resp).match(/Welcome aboard/) + assert extract_body(resp).match(/Yay! You.*re on Rails!/) end def assert_success(resp) -- cgit v1.2.3 From 0361d8449ff1c18da041df4b7dfe648abf0f1887 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 26 Jan 2016 17:06:31 -0800 Subject: clear view path cache between tests The cache for `render file:` seems to also be used in the case of `render(string)`. If one is supposed to be a hit and the other is supposed to be a miss, and they both reference the same file, then the cache could return incorrect values. This commit clears the cache between runs so that we get non-cached behavior. --- actionpack/test/controller/render_test.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 3f569230c2..db73de6010 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -253,6 +253,11 @@ end class ExpiresInRenderTest < ActionController::TestCase tests TestController + def setup + super + ActionController::Base.view_paths.paths.each(&:clear_cache) + end + def test_dynamic_render_with_file # This is extremely bad, but should be possible to do. assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')) -- cgit v1.2.3 From 534b12afb5cd0240128c3552394004f15b18520c Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Mon, 25 Jan 2016 15:21:11 -0500 Subject: Fix undefined error for `ActionController::Parameters` --- actionpack/lib/abstract_controller/rendering.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 63fd76d9b7..841a4c07ad 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -82,13 +82,10 @@ module AbstractController # render :file => "foo/bar". # :api: plugin def _normalize_args(action=nil, options={}) - case action - when ActionController::Parameters - unless action.permitted? - raise ArgumentError, "render parameters are not permitted" - end + if action.respond_to?(:permitted?) && action.permitted? + raise ArgumentError, "render parameters are not permitted" action - when Hash + elsif action.is_a?(Hash) action else options -- cgit v1.2.3 From f31a386c7ecdfe8a9173655b515470a2fbc9fcdd Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Tue, 26 Jan 2016 15:43:29 -0500 Subject: Fix sanitizer tests These tests were failing due to backwards incompatible changes, as apart of the v1.0.3 release of rails-html-sanitizer. --- actionview/test/template/text_helper_test.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb index fb98ac6330..03c7597505 100644 --- a/actionview/test/template/text_helper_test.rb +++ b/actionview/test/template/text_helper_test.rb @@ -43,11 +43,11 @@ class TextHelperTest < ActionView::TestCase end def test_simple_format_should_sanitize_input_when_sanitize_option_is_not_false - assert_equal "

test with unsafe string

", simple_format(" test with unsafe string ") + assert_equal "

test with unsafe string code!

", simple_format(" test with unsafe string ") end def test_simple_format_should_sanitize_input_when_sanitize_option_is_true - assert_equal '

test with unsafe string

', + assert_equal '

test with unsafe string code!

', simple_format(' test with unsafe string ', {}, sanitize: true) end @@ -198,7 +198,7 @@ class TextHelperTest < ActionView::TestCase def test_highlight_should_sanitize_input assert_equal( - "This is a beautiful morning", + "This is a beautiful morningcode!", highlight("This is a beautiful morning", "beautiful") ) end -- cgit v1.2.3 From 00285e7cf75c96553719072a27c27e4ab7d25b40 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 26 Jan 2016 18:00:05 -0800 Subject: fix permitted? conditional for `render` calls --- actionpack/lib/abstract_controller/rendering.rb | 9 ++++++--- actionpack/test/controller/render_test.rb | 11 +++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 841a4c07ad..e765d73ce4 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -82,9 +82,12 @@ module AbstractController # render :file => "foo/bar". # :api: plugin def _normalize_args(action=nil, options={}) - if action.respond_to?(:permitted?) && action.permitted? - raise ArgumentError, "render parameters are not permitted" - action + if action.respond_to?(:permitted?) + if action.permitted? + action + else + raise ArgumentError, "render parameters are not permitted" + end elsif action.is_a?(Hash) action else diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index db73de6010..f205b96ce8 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -66,6 +66,10 @@ class TestController < ActionController::Base render params[:id] # => String, AC:Params end + def dynamic_render_permit + render params[:id].permit(:file) + end + def dynamic_render_with_file # This is extremely bad, but should be possible to do. file = params[:id] # => String, AC:Params @@ -273,6 +277,13 @@ class ExpiresInRenderTest < ActionController::TestCase end end + def test_permitted_dynamic_render_file_hash + assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')) + response = get :dynamic_render_permit, { id: { file: '../\\../test/abstract_unit.rb' } } + assert_equal File.read(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')), + response.body + end + def test_dynamic_render_file_hash assert_raises ArgumentError do get :dynamic_render, params: { id: { file: '../\\../test/abstract_unit.rb' } } -- cgit v1.2.3 From 3844854af109fb9eee75c90bacf8bf87eb2bf968 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 26 Jan 2016 18:01:24 -0800 Subject: add a skip for failing test --- actionpack/test/controller/render_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index f205b96ce8..ea789b6121 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -278,8 +278,9 @@ class ExpiresInRenderTest < ActionController::TestCase end def test_permitted_dynamic_render_file_hash + skip "FIXME: this test passes on 4-2-stable but not master. Why?" assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')) - response = get :dynamic_render_permit, { id: { file: '../\\../test/abstract_unit.rb' } } + response = get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } } assert_equal File.read(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')), response.body end -- cgit v1.2.3 From 9c97bf5510d390bf2aa9a862ce86884b347e4c40 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Tue, 26 Jan 2016 08:58:38 +0900 Subject: Remove `limit: 11` as backward-compatibility with Rails 2.0 Integer limit as a byte size was introduced from Rails 2.1. `limit: 11` is not a byte size, but take care for backward-compatibility with Rails 2.0 (a892af6). Integer limit out of range should be allowed to raise by #6349. I think we should remove this backward-compatibility. --- .../lib/active_record/connection_adapters/abstract_mysql_adapter.rb | 1 - 1 file changed, 1 deletion(-) 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 3e84786be0..5a9020ead5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -980,7 +980,6 @@ module ActiveRecord when 3; 'mediumint' when nil, 4; 'int' when 5..8; 'bigint' - when 11; 'int(11)' # backward compatibility with Rails 2.0 else raise(ActiveRecordError, "No integer type has byte size #{limit}") end end -- cgit v1.2.3 From 42dd2336b31a8d98776d039a2b9fd7f834156a78 Mon Sep 17 00:00:00 2001 From: Akira Matsuda & Naoto Koshikawa Date: Wed, 27 Jan 2016 17:44:27 +0900 Subject: INSERT INTO schema_migrations in 1 SQL We found that inserting all 600 schema_migrations for our mid-sized app takes about a minute on a cloud based CI environment. I assume that the original code did not use multi-row-insert because SQLite3 was not supporting the syntax back then, but it's been supported since 3.7.11: http://www.sqlite.org/releaselog/3_7_11.html --- activerecord/CHANGELOG.md | 5 +++++ .../connection_adapters/abstract/schema_statements.rb | 18 ++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f3dc26ddb3..b59c9b4635 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,8 @@ +* Improve schema_migrations insertion performance by inserting all versions + in one INSERT SQL. + + *Akira Matsuda*, *Naoto Koshikawa* + * Using `references` or `belongs_to` in migrations will always add index for the referenced column by default, without adding `index: true` option to generated migration file. Users can opt out of this by passing 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 70868ebd03..002f2ea8ce 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -957,9 +957,9 @@ module ActiveRecord def dump_schema_information #:nodoc: sm_table = ActiveRecord::Migrator.schema_migrations_table_name - ActiveRecord::SchemaMigration.order('version').map { |sm| - "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');" - }.join "\n\n" + sql = "INSERT INTO #{sm_table} (version) VALUES " + sql << ActiveRecord::SchemaMigration.order('version').pluck(:version).map {|v| "('#{v}')" }.join(', ') + sql << ";\n\n" end # Should not be called normally, but this operation is non-destructive. @@ -987,14 +987,12 @@ module ActiveRecord execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" end - inserted = Set.new - (versions - migrated).each do |v| - if inserted.include?(v) - raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict." - elsif v < version - execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" - inserted << v + inserting = (versions - migrated).select {|v| v < version} + if inserting.any? + if (duplicate = inserting.detect {|v| inserting.count(v) > 1}) + raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict." end + execute "INSERT INTO #{sm_table} (version) VALUES #{inserting.map {|v| '(#{v})'}.join(', ') }" end end -- cgit v1.2.3 From 5262bf544abacb10828abcfeb046d8146bef4c14 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 27 Jan 2016 18:19:15 +0900 Subject: doc typo [ci skip] --- actionpack/test/controller/render_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index ea789b6121..d1b9586533 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -63,7 +63,7 @@ class TestController < ActionController::Base end def dynamic_render - render params[:id] # => String, AC:Params + render params[:id] # => String, AC::Params end def dynamic_render_permit @@ -72,7 +72,7 @@ class TestController < ActionController::Base def dynamic_render_with_file # This is extremely bad, but should be possible to do. - file = params[:id] # => String, AC:Params + file = params[:id] # => String, AC::Params render file: file end -- cgit v1.2.3 From 14efb42a906aa8079adff2b1e56d6444d147a386 Mon Sep 17 00:00:00 2001 From: Bogdan Gusiev Date: Wed, 27 Jan 2016 12:53:51 +0200 Subject: Introduce ActiveRecord::IrreversibleOrderError Raises when #reverse_order can not process SQL order instead of making invalid SQL before this patch --- activerecord/CHANGELOG.md | 14 +++++++++ activerecord/lib/active_record/errors.rb | 5 ++++ .../lib/active_record/relation/query_methods.rb | 18 ++++++++++-- activerecord/test/cases/relations_test.rb | 34 ++++++++++++++++++++++ 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index b59c9b4635..50a0b291b3 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,17 @@ +* `ActiveRecord::Relation#reverse_order` throws `ActiveRecord::IrreversibleOrderError` + when the order can not be reversed using current trivial algorithm. + Also raises the same error when `#reverse_order` is called on + relation without any order and table has no primary key: + + Topic.order("concat(author_name, title)").reverse_order + # Before: SELECT `topics`.* FROM `topics` ORDER BY concat(author_name DESC, title) DESC + # After: raises ActiveRecord::IrreversibleOrderError + Edge.all.reverse_order + # Before: SELECT `edges`.* FROM `edges` ORDER BY `edges`.`` DESC + # After: raises ActiveRecord::IrreversibleOrderError + + *Bogdan Gusiev* + * Improve schema_migrations insertion performance by inserting all versions in one INSERT SQL. diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index e5906b6756..87f32c042c 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -275,4 +275,9 @@ module ActiveRecord # The mysql2 and postgresql adapters support setting the transaction isolation level. class TransactionIsolationError < ActiveRecordError end + + # IrreversibleOrderError is raised when a relation's order is too complex for + # +reverse_order+ to automatically reverse. + class IrreversibleOrderError < ActiveRecordError + end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 716b1e8505..8ef9f9f627 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1104,14 +1104,21 @@ module ActiveRecord end def reverse_sql_order(order_query) - order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty? + if order_query.empty? + return [table[primary_key].desc] if primary_key + raise IrreversibleOrderError, + "Relation has no current order and table has no primary key to be used as default order" + end order_query.flat_map do |o| case o when Arel::Nodes::Ordering o.reverse when String - o.to_s.split(',').map! do |s| + if does_not_support_reverse?(o) + raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically" + end + o.split(',').map! do |s| s.strip! s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') end @@ -1121,6 +1128,13 @@ module ActiveRecord end end + def does_not_support_reverse?(order) + #uses sql function with multiple arguments + order =~ /\([^()]*,[^()]*\)/ || + # uses "nulls first" like construction + order =~ /nulls (first|last)\Z/i + end + def build_order(arel) orders = order_values.uniq orders.reject!(&:blank?) diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 0638edacbd..090b885dd5 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -19,6 +19,7 @@ require 'models/aircraft' require "models/possession" require "models/reader" require "models/categorization" +require "models/edge" class RelationTest < ActiveRecord::TestCase fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments, @@ -223,6 +224,39 @@ class RelationTest < ActiveRecord::TestCase assert_equal topics(:fifth).title, topics.first.title end + def test_reverse_order_with_function + topics = Topic.order("length(title)").reverse_order + assert_equal topics(:second).title, topics.first.title + end + + def test_reverse_order_with_function_other_predicates + topics = Topic.order("author_name, length(title), id").reverse_order + assert_equal topics(:second).title, topics.first.title + topics = Topic.order("length(author_name), id, length(title)").reverse_order + assert_equal topics(:fifth).title, topics.first.title + end + + def test_reverse_order_with_multiargument_function + assert_raises(ActiveRecord::IrreversibleOrderError) do + Topic.order("concat(author_name, title)").reverse_order + end + end + + def test_reverse_order_with_nulls_first_or_last + assert_raises(ActiveRecord::IrreversibleOrderError) do + Topic.order("title NULLS FIRST").reverse_order + end + assert_raises(ActiveRecord::IrreversibleOrderError) do + Topic.order("title nulls last").reverse_order + end + end + + def test_default_reverse_order_on_table_without_primary_key + assert_raises(ActiveRecord::IrreversibleOrderError) do + Edge.all.reverse_order + end + end + def test_order_with_hash_and_symbol_generates_the_same_sql assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql end -- cgit v1.2.3 From 10bc49710b7205a6172c3e072b3c77114fefd952 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Wed, 27 Jan 2016 19:59:06 +0900 Subject: [ci skip] Fix typo --- activesupport/lib/active_support/deprecation/proxy_wrappers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 6f0ad445fc..31e48e451a 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -118,7 +118,7 @@ module ActiveSupport # # PLANETS.map { |planet| planet.capitalize } # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead. - # (Bactrace information…) + # (Backtrace information…) # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] class DeprecatedConstantProxy < DeprecationProxy def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance) -- cgit v1.2.3 From b24b20c80ef55096d268bb49b8d4a3faf05843da Mon Sep 17 00:00:00 2001 From: Aditya Kapoor Date: Wed, 27 Jan 2016 16:39:22 +0530 Subject: remove duplication section [ci skip] --- guides/source/upgrading_ruby_on_rails.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 936547a8cc..202e5b5cb9 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -57,10 +57,6 @@ Upgrading from Rails 4.2 to Rails 5.0 ToDo... -### Ruby 2.2.2+ - -ToDo... - ### Active Record models now inherit from ApplicationRecord by default In Rails 4.2 an Active Record model inherits from `ActiveRecord::Base`. In Rails 5.0, -- cgit v1.2.3 From 9ea7aa84d16d99fd32ed1877e3fd6631a41e7042 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 27 Jan 2016 14:33:15 +0100 Subject: Revert "Eliminate the EventMachine dependency" --- Gemfile.lock | 7 +- actioncable/actioncable.gemspec | 3 +- .../lib/action_cable/channel/periodic_timers.rb | 4 +- actioncable/lib/action_cable/channel/streams.rb | 2 +- actioncable/lib/action_cable/connection.rb | 5 +- actioncable/lib/action_cable/connection/base.rb | 32 ++--- .../lib/action_cable/connection/client_socket.rb | 152 --------------------- .../action_cable/connection/internal_channel.rb | 4 +- actioncable/lib/action_cable/connection/stream.rb | 59 -------- .../action_cable/connection/stream_event_loop.rb | 68 --------- .../lib/action_cable/connection/web_socket.rb | 22 ++- actioncable/lib/action_cable/process/logging.rb | 7 + actioncable/lib/action_cable/server.rb | 4 + actioncable/lib/action_cable/server/base.rb | 4 - actioncable/lib/action_cable/server/connections.rb | 8 +- .../lib/action_cable/subscription_adapter/async.rb | 4 +- .../subscription_adapter/postgresql.rb | 4 +- .../lib/action_cable/subscription_adapter/redis.rb | 16 --- actioncable/test/channel/periodic_timers_test.rb | 2 +- actioncable/test/channel/stream_test.rb | 22 +-- actioncable/test/connection/base_test.rb | 19 ++- actioncable/test/connection/identifier_test.rb | 4 +- .../test/connection/multiple_identifiers_test.rb | 4 +- actioncable/test/stubs/test_server.rb | 3 +- actioncable/test/subscription_adapter/common.rb | 3 + actioncable/test/test_helper.rb | 26 +++- .../generators/rails/app/templates/config.ru.tt | 3 +- 27 files changed, 106 insertions(+), 385 deletions(-) delete mode 100644 actioncable/lib/action_cable/connection/client_socket.rb delete mode 100644 actioncable/lib/action_cable/connection/stream.rb delete mode 100644 actioncable/lib/action_cable/connection/stream_event_loop.rb create mode 100644 actioncable/lib/action_cable/process/logging.rb diff --git a/Gemfile.lock b/Gemfile.lock index a7b1daaef4..7d2844ae89 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -32,7 +32,8 @@ PATH actioncable (5.0.0.beta1.1) actionpack (= 5.0.0.beta1.1) coffee-rails (~> 4.1.0) - nio4r (~> 1.2) + eventmachine (~> 1.0) + faye-websocket (~> 0.10.0) websocket-driver (~> 0.6.1) actionmailer (5.0.0.beta1.1) actionpack (= 5.0.0.beta1.1) @@ -144,6 +145,9 @@ GEM erubis (2.7.0) eventmachine (1.0.9.1) execjs (2.6.0) + faye-websocket (0.10.2) + eventmachine (>= 0.12.0) + websocket-driver (>= 0.5.1) ffi (1.9.10) ffi (1.9.10-x64-mingw32) ffi (1.9.10-x86-mingw32) @@ -181,7 +185,6 @@ GEM mysql2 (0.4.2) mysql2 (0.4.2-x64-mingw32) mysql2 (0.4.2-x86-mingw32) - nio4r (1.2.0) nokogiri (1.6.7.1) mini_portile2 (~> 2.0.0.rc2) nokogiri (1.6.7.1-x64-mingw32) diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec index 14f968f1ef..a36acc8f6f 100644 --- a/actioncable/actioncable.gemspec +++ b/actioncable/actioncable.gemspec @@ -21,7 +21,8 @@ Gem::Specification.new do |s| s.add_dependency 'actionpack', version s.add_dependency 'coffee-rails', '~> 4.1.0' - s.add_dependency 'nio4r', '~> 1.2' + s.add_dependency 'eventmachine', '~> 1.0' + s.add_dependency 'faye-websocket', '~> 0.10.0' s.add_dependency 'websocket-driver', '~> 0.6.1' s.add_development_dependency 'em-hiredis', '~> 0.3.0' diff --git a/actioncable/lib/action_cable/channel/periodic_timers.rb b/actioncable/lib/action_cable/channel/periodic_timers.rb index 56597d02d7..7f0fb37afc 100644 --- a/actioncable/lib/action_cable/channel/periodic_timers.rb +++ b/actioncable/lib/action_cable/channel/periodic_timers.rb @@ -27,14 +27,14 @@ module ActionCable def start_periodic_timers self.class.periodic_timers.each do |callback, options| - active_periodic_timers << Concurrent::TimerTask.new(execution_interval: options[:every]) do + active_periodic_timers << EventMachine::PeriodicTimer.new(options[:every]) do connection.worker_pool.async_run_periodic_timer(self, callback) end end end def stop_periodic_timers - active_periodic_timers.each { |timer| timer.shutdown } + active_periodic_timers.each { |timer| timer.cancel } end end end diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb index a26373e387..e2876ef6fa 100644 --- a/actioncable/lib/action_cable/channel/streams.rb +++ b/actioncable/lib/action_cable/channel/streams.rb @@ -75,7 +75,7 @@ module ActionCable callback ||= default_stream_callback(broadcasting) streams << [ broadcasting, callback ] - Concurrent.global_io_executor.post do + EM.next_tick do pubsub.subscribe(broadcasting, callback, lambda do transmit_subscription_confirmation logger.info "#{self.class.name} is streaming from #{broadcasting}" diff --git a/actioncable/lib/action_cable/connection.rb b/actioncable/lib/action_cable/connection.rb index 902efb07e2..b672e00682 100644 --- a/actioncable/lib/action_cable/connection.rb +++ b/actioncable/lib/action_cable/connection.rb @@ -5,15 +5,12 @@ module ActionCable eager_autoload do autoload :Authorization autoload :Base - autoload :ClientSocket autoload :Identification autoload :InternalChannel autoload :MessageBuffer - autoload :Stream - autoload :StreamEventLoop + autoload :WebSocket autoload :Subscriptions autoload :TaggedLoggerProxy - autoload :WebSocket end end end diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index 0016d1a1a4..bb8850aaa0 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -49,14 +49,14 @@ module ActionCable include Authorization attr_reader :server, :env, :subscriptions, :logger - delegate :stream_event_loop, :worker_pool, :pubsub, to: :server + delegate :worker_pool, :pubsub, to: :server def initialize(server, env) @server, @env = server, env @logger = new_tagged_logger - @websocket = ActionCable::Connection::WebSocket.new(env, self, stream_event_loop) + @websocket = ActionCable::Connection::WebSocket.new(env) @subscriptions = ActionCable::Connection::Subscriptions.new(self) @message_buffer = ActionCable::Connection::MessageBuffer.new(self) @@ -70,6 +70,10 @@ module ActionCable logger.info started_request_message if websocket.possible? && allow_request_origin? + websocket.on(:open) { |event| send_async :on_open } + websocket.on(:message) { |event| on_message event.data } + websocket.on(:close) { |event| send_async :on_close } + respond_to_successful_request else respond_to_invalid_request @@ -117,22 +121,6 @@ module ActionCable transmit ActiveSupport::JSON.encode(identifier: ActionCable::INTERNAL[:identifiers][:ping], message: Time.now.to_i) end - def on_open # :nodoc: - send_async :handle_open - end - - def on_message(message) # :nodoc: - message_buffer.append message - end - - def on_error(message) # :nodoc: - # ignore - end - - def on_close # :nodoc: - send_async :handle_close - end - protected # The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc. def request @@ -151,7 +139,7 @@ module ActionCable attr_reader :message_buffer private - def handle_open + def on_open connect if respond_to?(:connect) subscribe_to_internal_channel beat @@ -162,7 +150,11 @@ module ActionCable respond_to_invalid_request end - def handle_close + def on_message(message) + message_buffer.append message + end + + def on_close logger.info finished_request_message server.remove_connection(self) diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb deleted file mode 100644 index 62dd753646..0000000000 --- a/actioncable/lib/action_cable/connection/client_socket.rb +++ /dev/null @@ -1,152 +0,0 @@ -require 'websocket/driver' - -module ActionCable - module Connection - #-- - # This class is heavily based on faye-websocket-ruby - # - # Copyright (c) 2010-2015 James Coglan - class ClientSocket # :nodoc: - def self.determine_url(env) - scheme = secure_request?(env) ? 'wss:' : 'ws:' - "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }" - end - - def self.secure_request?(env) - return true if env['HTTPS'] == 'on' - return true if env['HTTP_X_FORWARDED_SSL'] == 'on' - return true if env['HTTP_X_FORWARDED_SCHEME'] == 'https' - return true if env['HTTP_X_FORWARDED_PROTO'] == 'https' - return true if env['rack.url_scheme'] == 'https' - - return false - end - - CONNECTING = 0 - OPEN = 1 - CLOSING = 2 - CLOSED = 3 - - attr_reader :env, :url - - def initialize(env, event_target, stream_event_loop) - @env = env - @event_target = event_target - @stream_event_loop = stream_event_loop - - @url = ClientSocket.determine_url(@env) - - @driver = @driver_started = nil - - @ready_state = CONNECTING - - # The driver calls +env+, +url+, and +write+ - @driver = ::WebSocket::Driver.rack(self) - - @driver.on(:open) { |e| open } - @driver.on(:message) { |e| receive_message(e.data) } - @driver.on(:close) { |e| begin_close(e.reason, e.code) } - @driver.on(:error) { |e| emit_error(e.message) } - - @stream = ActionCable::Connection::Stream.new(@stream_event_loop, self) - - if callback = @env['async.callback'] - callback.call([101, {}, @stream]) - end - end - - def start_driver - return if @driver.nil? || @driver_started - @driver_started = true - @driver.start - end - - def rack_response - start_driver - [ -1, {}, [] ] - end - - def write(data) - @stream.write(data) - end - - def transmit(message) - return false if @ready_state > OPEN - case message - when Numeric then @driver.text(message.to_s) - when String then @driver.text(message) - when Array then @driver.binary(message) - else false - end - end - - def close(code = nil, reason = nil) - code ||= 1000 - reason ||= '' - - unless code == 1000 or (code >= 3000 and code <= 4999) - raise ArgumentError, "Failed to execute 'close' on WebSocket: " + - "The code must be either 1000, or between 3000 and 4999. " + - "#{code} is neither." - end - - @ready_state = CLOSING unless @ready_state == CLOSED - @driver.close(reason, code) - end - - def parse(data) - @driver.parse(data) - end - - def client_gone - finalize_close - end - - def alive? - @ready_state == OPEN - end - - private - def open - return unless @ready_state == CONNECTING - @ready_state = OPEN - - @event_target.on_open - end - - def receive_message(data) - return unless @ready_state == OPEN - - @event_target.on_message(data) - end - - def emit_error(message) - return if @ready_state >= CLOSING - - @event_target.on_error(message) - end - - def begin_close(reason, code) - return if @ready_state == CLOSED - @ready_state = CLOSING - @close_params = [reason, code] - - if @stream - @stream.shutdown - else - finalize_close - end - end - - def finalize_close - return if @ready_state == CLOSED - @ready_state = CLOSED - - reason = @close_params ? @close_params[0] : '' - code = @close_params ? @close_params[1] : 1006 - - @event_target.on_close(code, reason) - end - end - end -end diff --git a/actioncable/lib/action_cable/connection/internal_channel.rb b/actioncable/lib/action_cable/connection/internal_channel.rb index 27826792b3..54ed7672d2 100644 --- a/actioncable/lib/action_cable/connection/internal_channel.rb +++ b/actioncable/lib/action_cable/connection/internal_channel.rb @@ -15,14 +15,14 @@ module ActionCable @_internal_subscriptions ||= [] @_internal_subscriptions << [ internal_channel, callback ] - Concurrent.global_io_executor.post { pubsub.subscribe(internal_channel, callback) } + EM.next_tick { pubsub.subscribe(internal_channel, callback) } logger.info "Registered connection (#{connection_identifier})" end end def unsubscribe_from_internal_channel if @_internal_subscriptions.present? - @_internal_subscriptions.each { |channel, callback| Concurrent.global_io_executor.post { pubsub.unsubscribe(channel, callback) } } + @_internal_subscriptions.each { |channel, callback| EM.next_tick { pubsub.unsubscribe(channel, callback) } } end end diff --git a/actioncable/lib/action_cable/connection/stream.rb b/actioncable/lib/action_cable/connection/stream.rb deleted file mode 100644 index ace250cd16..0000000000 --- a/actioncable/lib/action_cable/connection/stream.rb +++ /dev/null @@ -1,59 +0,0 @@ -module ActionCable - module Connection - #-- - # This class is heavily based on faye-websocket-ruby - # - # Copyright (c) 2010-2015 James Coglan - class Stream - def initialize(event_loop, socket) - @event_loop = event_loop - @socket_object = socket - @stream_send = socket.env['stream.send'] - - @rack_hijack_io = nil - - hijack_rack_socket - end - - def each(&callback) - @stream_send ||= callback - end - - def close - shutdown - @socket_object.client_gone - end - - def shutdown - clean_rack_hijack - end - - def write(data) - return @rack_hijack_io.write(data) if @rack_hijack_io - return @stream_send.call(data) if @stream_send - rescue EOFError - @socket_object.client_gone - end - - def receive(data) - @socket_object.parse(data) - end - - private - def hijack_rack_socket - return unless @socket_object.env['rack.hijack'] - - @socket_object.env['rack.hijack'].call - @rack_hijack_io = @socket_object.env['rack.hijack_io'] - - @event_loop.attach(@rack_hijack_io, self) - end - - def clean_rack_hijack - return unless @rack_hijack_io - @event_loop.detach(@rack_hijack_io, self) - @rack_hijack_io = nil - end - end - end -end diff --git a/actioncable/lib/action_cable/connection/stream_event_loop.rb b/actioncable/lib/action_cable/connection/stream_event_loop.rb deleted file mode 100644 index f773814973..0000000000 --- a/actioncable/lib/action_cable/connection/stream_event_loop.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'nio' - -module ActionCable - module Connection - class StreamEventLoop - def initialize - @nio = NIO::Selector.new - @map = {} - @stopping = false - @todo = Queue.new - - Thread.new do - Thread.current.abort_on_exception = true - run - end - end - - def attach(io, stream) - @todo << lambda do - @map[io] = stream - @nio.register(io, :r) - end - @nio.wakeup - end - - def detach(io, stream) - @todo << lambda do - @nio.deregister(io) - @map.delete io - end - @nio.wakeup - end - - def stop - @stopping = true - @nio.wakeup - end - - def run - loop do - if @stopping - @nio.close - break - end - - until @todo.empty? - @todo.pop(true).call - end - - if monitors = @nio.select - monitors.each do |monitor| - io = monitor.io - stream = @map[io] - - begin - stream.receive io.read_nonblock(4096) - rescue IO::WaitReadable - next - rescue EOFError - stream.close - end - end - end - end - end - end - end -end diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb index 5e89fb9b72..670d5690ae 100644 --- a/actioncable/lib/action_cable/connection/web_socket.rb +++ b/actioncable/lib/action_cable/connection/web_socket.rb @@ -1,11 +1,13 @@ -require 'websocket/driver' +require 'faye/websocket' module ActionCable module Connection - # Wrap the real socket to minimize the externally-presented API + # Decorate the Faye::WebSocket with helpers we need. class WebSocket - def initialize(env, event_target, stream_event_loop) - @websocket = ::WebSocket::Driver.websocket?(env) ? ClientSocket.new(env, event_target, stream_event_loop) : nil + delegate :rack_response, :close, :on, to: :websocket + + def initialize(env) + @websocket = Faye::WebSocket.websocket?(env) ? Faye::WebSocket.new(env) : nil end def possible? @@ -13,19 +15,11 @@ module ActionCable end def alive? - websocket && websocket.alive? + websocket && websocket.ready_state == Faye::WebSocket::API::OPEN end def transmit(data) - websocket.transmit data - end - - def close - websocket.close - end - - def rack_response - websocket.rack_response + websocket.send data end protected diff --git a/actioncable/lib/action_cable/process/logging.rb b/actioncable/lib/action_cable/process/logging.rb new file mode 100644 index 0000000000..dce637b3ca --- /dev/null +++ b/actioncable/lib/action_cable/process/logging.rb @@ -0,0 +1,7 @@ +require 'action_cable/server' +require 'eventmachine' + +EM.error_handler do |e| + puts "Error raised inside the event loop: #{e.message}" + puts e.backtrace.join("\n") +end diff --git a/actioncable/lib/action_cable/server.rb b/actioncable/lib/action_cable/server.rb index bd6a3826a3..a2a89d5f1e 100644 --- a/actioncable/lib/action_cable/server.rb +++ b/actioncable/lib/action_cable/server.rb @@ -1,3 +1,7 @@ +require 'eventmachine' +EventMachine.epoll if EventMachine.epoll? +EventMachine.kqueue if EventMachine.kqueue? + module ActionCable module Server extend ActiveSupport::Autoload diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb index b00abd208c..3385a4c9f3 100644 --- a/actioncable/lib/action_cable/server/base.rb +++ b/actioncable/lib/action_cable/server/base.rb @@ -32,10 +32,6 @@ module ActionCable @remote_connections ||= RemoteConnections.new(self) end - def stream_event_loop - @stream_event_loop ||= ActionCable::Connection::StreamEventLoop.new - end - # The thread worker pool for handling all the connection work on this server. Default size is set by config.worker_pool_size. def worker_pool @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) diff --git a/actioncable/lib/action_cable/server/connections.rb b/actioncable/lib/action_cable/server/connections.rb index 8671dd5ebd..47dcea8c20 100644 --- a/actioncable/lib/action_cable/server/connections.rb +++ b/actioncable/lib/action_cable/server/connections.rb @@ -22,9 +22,11 @@ module ActionCable # then can't rely on being able to receive and send to it. So there's a 3 second heartbeat running on all connections. If the beat fails, we automatically # disconnect. def setup_heartbeat_timer - @heartbeat_timer ||= Concurrent::TimerTask.new(execution_interval: BEAT_INTERVAL) do - Concurrent.global_io_executor.post { connections.map(&:beat) } - end.tap(&:execute) + EM.next_tick do + @heartbeat_timer ||= EventMachine.add_periodic_timer(BEAT_INTERVAL) do + EM.next_tick { connections.map(&:beat) } + end + end end def open_connections_statistics diff --git a/actioncable/lib/action_cable/subscription_adapter/async.rb b/actioncable/lib/action_cable/subscription_adapter/async.rb index c88b03947a..85d4892e4c 100644 --- a/actioncable/lib/action_cable/subscription_adapter/async.rb +++ b/actioncable/lib/action_cable/subscription_adapter/async.rb @@ -10,11 +10,11 @@ module ActionCable class AsyncSubscriberMap < SubscriberMap def add_subscriber(*) - Concurrent.global_io_executor.post { super } + ::EM.next_tick { super } end def invoke_callback(*) - Concurrent.global_io_executor.post { super } + ::EM.next_tick { super } end end end diff --git a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb index 3ce1bbed68..78f8aeb599 100644 --- a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb +++ b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb @@ -63,7 +63,7 @@ module ActionCable case action when :listen pg_conn.exec("LISTEN #{pg_conn.escape_identifier channel}") - Concurrent.global_io_executor << callback if callback + ::EM.next_tick(&callback) if callback when :unlisten pg_conn.exec("UNLISTEN #{pg_conn.escape_identifier channel}") when :shutdown @@ -93,7 +93,7 @@ module ActionCable end def invoke_callback(*) - Concurrent.global_io_executor.post { super } + ::EM.next_tick { super } end end end diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index a035e3988d..3b86354621 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -1,18 +1,11 @@ -require 'thread' - gem 'em-hiredis', '~> 0.3.0' gem 'redis', '~> 3.0' require 'em-hiredis' require 'redis' -EventMachine.epoll if EventMachine.epoll? -EventMachine.kqueue if EventMachine.kqueue? - module ActionCable module SubscriptionAdapter class Redis < Base # :nodoc: - @@mutex = Mutex.new - def broadcast(channel, payload) redis_connection_for_broadcasts.publish(channel, payload) end @@ -34,7 +27,6 @@ module ActionCable private def redis_connection_for_subscriptions - ensure_reactor_running @redis_connection_for_subscriptions ||= EM::Hiredis.connect(@server.config.cable[:url]).tap do |redis| redis.on(:reconnect_failed) do @logger.info "[ActionCable] Redis reconnect failed." @@ -45,14 +37,6 @@ module ActionCable def redis_connection_for_broadcasts @redis_connection_for_broadcasts ||= ::Redis.new(@server.config.cable) end - - def ensure_reactor_running - return if EventMachine.reactor_running? - @@mutex.synchronize do - Thread.new { EventMachine.run } unless EventMachine.reactor_running? - Thread.pass until EventMachine.reactor_running? - end - end end end end diff --git a/actioncable/test/channel/periodic_timers_test.rb b/actioncable/test/channel/periodic_timers_test.rb index 64f0247cd6..1590a12f09 100644 --- a/actioncable/test/channel/periodic_timers_test.rb +++ b/actioncable/test/channel/periodic_timers_test.rb @@ -31,7 +31,7 @@ class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase end test "timer start and stop" do - Concurrent::TimerTask.expects(:new).times(2).returns(true) + EventMachine::PeriodicTimer.expects(:new).times(2).returns(true) channel = ChatChannel.new @connection, "{id: 1}", { id: 1 } channel.expects(:stop_periodic_timers).once diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb index 947efd96d4..3fa2b291b7 100644 --- a/actioncable/test/channel/stream_test.rb +++ b/actioncable/test/channel/stream_test.rb @@ -31,7 +31,9 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase test "stream_for" do run_in_eventmachine do connection = TestConnection.new - connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) } + EM.next_tick do + connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) } + end channel = ChatChannel.new connection, "" channel.stream_for Room.new(1) @@ -39,35 +41,39 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase end test "stream_from subscription confirmation" do - run_in_eventmachine do + EM.run do connection = TestConnection.new ChatChannel.new connection, "{id: 1}", { id: 1 } assert_nil connection.last_transmission - wait_for_async + EM::Timer.new(0.1) do + expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription" + connection.transmit(expected) - expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription" - connection.transmit(expected) + assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s" - assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s" + EM.run_deferred_callbacks + EM.stop + end end end test "subscription confirmation should only be sent out once" do - run_in_eventmachine do + EM.run do connection = TestConnection.new channel = ChatChannel.new connection, "test_channel" channel.send_confirmation channel.send_confirmation - wait_for_async + EM.run_deferred_callbacks expected = ActiveSupport::JSON.encode "identifier" => "test_channel", "type" => "confirm_subscription" assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation" assert_equal 1, connection.transmissions.size + EM.stop end end diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb index e2b017a9a1..182562db82 100644 --- a/actioncable/test/connection/base_test.rb +++ b/actioncable/test/connection/base_test.rb @@ -37,8 +37,6 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase connection.process assert connection.websocket.possible? - - wait_for_async assert connection.websocket.alive? end end @@ -55,15 +53,16 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase test "on connection open" do run_in_eventmachine do connection = open_connection + connection.process connection.websocket.expects(:transmit).with(regexp_matches(/\_ping/)) connection.message_buffer.expects(:process!) - connection.process - wait_for_async - - assert_equal [ connection ], @server.connections - assert connection.connected + # Allow EM to run on_open callback + EM.next_tick do + assert_equal [ connection ], @server.connections + assert connection.connected + end end end @@ -73,12 +72,12 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase connection.process # Setup the connection - Concurrent::TimerTask.stubs(:new).returns(true) - connection.send :handle_open + EventMachine.stubs(:add_periodic_timer).returns(true) + connection.send :on_open assert connection.connected connection.subscriptions.expects(:unsubscribe_from_all) - connection.send :handle_close + connection.send :on_close assert ! connection.connected assert_equal [], @server.connections diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb index 1019ad541e..a110dfdee0 100644 --- a/actioncable/test/connection/identifier_test.rb +++ b/actioncable/test/connection/identifier_test.rb @@ -68,10 +68,10 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase @connection = Connection.new(server, env) @connection.process - @connection.send :handle_open + @connection.send :on_open end def close_connection - @connection.send :handle_close + @connection.send :on_close end end diff --git a/actioncable/test/connection/multiple_identifiers_test.rb b/actioncable/test/connection/multiple_identifiers_test.rb index e9bb4e6d7f..55a9f96cb3 100644 --- a/actioncable/test/connection/multiple_identifiers_test.rb +++ b/actioncable/test/connection/multiple_identifiers_test.rb @@ -32,10 +32,10 @@ class ActionCable::Connection::MultipleIdentifiersTest < ActionCable::TestCase @connection = Connection.new(server, env) @connection.process - @connection.send :handle_open + @connection.send :on_open end def close_connection - @connection.send :handle_close + @connection.send :on_close end end diff --git a/actioncable/test/stubs/test_server.rb b/actioncable/test/stubs/test_server.rb index 56d132b30a..6e6541a952 100644 --- a/actioncable/test/stubs/test_server.rb +++ b/actioncable/test/stubs/test_server.rb @@ -14,7 +14,6 @@ class TestServer @config.subscription_adapter.new(self) end - def stream_event_loop - @stream_event_loop ||= ActionCable::Connection::StreamEventLoop.new + def send_async end end diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb index 361858784e..d4a13be889 100644 --- a/actioncable/test/subscription_adapter/common.rb +++ b/actioncable/test/subscription_adapter/common.rb @@ -1,6 +1,7 @@ require 'test_helper' require 'concurrent' +require 'action_cable/process/logging' require 'active_support/core_ext/hash/indifferent_access' require 'pathname' @@ -23,6 +24,8 @@ module CommonSubscriptionAdapterTest # and now the "real" setup for our test: + spawn_eventmachine + server.config.cable = cable_config.with_indifferent_access adapter_klass = server.config.pubsub_adapter diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb index 8ddbd4e764..6636ce078b 100644 --- a/actioncable/test/test_helper.rb +++ b/actioncable/test/test_helper.rb @@ -13,16 +13,28 @@ require 'rack/mock' # Require all the stubs and models Dir[File.dirname(__FILE__) + '/stubs/*.rb'].each {|file| require file } +require 'faye/websocket' +class << Faye::WebSocket + remove_method :ensure_reactor_running + + # We don't want Faye to start the EM reactor in tests because it makes testing much harder. + # We want to be able to start and stop EM loop in tests to make things simpler. + def ensure_reactor_running + # no-op + end +end + class ActionCable::TestCase < ActiveSupport::TestCase - def wait_for_async - e = Concurrent.global_io_executor - until e.completed_task_count == e.scheduled_task_count - sleep 0.1 + def run_in_eventmachine + EM.run do + yield + + EM.run_deferred_callbacks + EM.stop end end - def run_in_eventmachine - yield - wait_for_async + def spawn_eventmachine + Thread.new { EventMachine.run } unless EventMachine.reactor_running? end end diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru.tt b/railties/lib/rails/generators/rails/app/templates/config.ru.tt index 343c0833d7..70556fcc99 100644 --- a/railties/lib/rails/generators/rails/app/templates/config.ru.tt +++ b/railties/lib/rails/generators/rails/app/templates/config.ru.tt @@ -3,8 +3,9 @@ require ::File.expand_path('../config/environment', __FILE__) <%- unless options[:skip_action_cable] -%> -# Action Cable requires that all classes are loaded in advance +# Action Cable uses EventMachine which requires that all classes are loaded in advance Rails.application.eager_load! +require 'action_cable/process/logging' <%- end -%> run Rails.application -- cgit v1.2.3 From 18e700e9d63dbb40ece80e37458f270ad632dac7 Mon Sep 17 00:00:00 2001 From: Daniel Gomez de Souza Date: Wed, 27 Jan 2016 10:49:58 -0300 Subject: Fix doc [ci skip] --- actionview/lib/action_view/helpers/tags/collection_helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb index 1d3b1ecf0b..fb51460c8e 100644 --- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb +++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb @@ -94,7 +94,7 @@ module ActionView end end - # Append a hidden field to make sure something will be sent back to the + # Prepend a hidden field to make sure something will be sent back to the # server if all radio buttons are unchecked. if options.fetch('include_hidden', true) hidden_field + rendered_collection -- cgit v1.2.3 From f7d825d778062a6a337e9064aabdb0fda7db12d2 Mon Sep 17 00:00:00 2001 From: Djoume Salvetti Date: Wed, 27 Jan 2016 10:55:49 -0500 Subject: Fix typos in asset_pipeline.md --- guides/source/asset_pipeline.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 60f78d011c..5bdaf600ad 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -45,7 +45,7 @@ gem 'coffee-rails' ``` Using the `--skip-sprockets` option will prevent Rails 4 from adding -`sass-rails` and `uglifier` to Gemfile, so if you later want to enable +`sass-rails` and `uglifier` to your Gemfile, so if you later want to enable the asset pipeline you will have to add those gems to your Gemfile. Also, creating an application with the `--skip-sprockets` option will generate a slightly different `config/application.rb` file, with a require statement @@ -66,7 +66,7 @@ config.assets.js_compressor = :uglifier ``` NOTE: The `sass-rails` gem is automatically used for CSS compression if included -in Gemfile and no `config.assets.css_compressor` option is set. +in the Gemfile and no `config.assets.css_compressor` option is set. ### Main Features @@ -1300,7 +1300,7 @@ Rails 4 no longer sets default config values for Sprockets in `test.rb`, so environment are: `config.assets.compile = true`, `config.assets.compress = false`, `config.assets.debug = false` and `config.assets.digest = false`. -The following should also be added to `Gemfile`: +The following should also be added to your `Gemfile`: ```ruby gem 'sass-rails', "~> 3.2.3" -- cgit v1.2.3 From a9a64c490dd5f5038b05338debe439855323afb5 Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Wed, 27 Jan 2016 11:29:18 -0500 Subject: Update ActiveJob adapter for sucker_punch 2.0 This PR includes two changes for 2.0.0: - Breaking API change around `async.perform` --> `perform_async` - New addition of `perform_in`, which now allows end users of the adapter to use the `enqueued_at` public API method. --- Gemfile | 3 ++- Gemfile.lock | 10 +++++----- activejob/lib/active_job/queue_adapters.rb | 2 +- .../lib/active_job/queue_adapters/sucker_punch_adapter.rb | 15 +++++++++++++-- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index b69c05025f..51020dbea2 100644 --- a/Gemfile +++ b/Gemfile @@ -48,7 +48,7 @@ group :job do gem 'resque', require: false gem 'resque-scheduler', require: false gem 'sidekiq', require: false - gem 'sucker_punch', '< 2.0', require: false + gem 'sucker_punch', require: false gem 'delayed_job', require: false gem 'queue_classic', github: "QueueClassic/queue_classic", branch: 'master', require: false, platforms: :ruby gem 'sneakers', require: false @@ -58,6 +58,7 @@ group :job do gem 'qu-redis', require: false gem 'delayed_job_active_record', require: false gem 'sequel', require: false + gem 'celluloid', require: false end # Action Cable diff --git a/Gemfile.lock b/Gemfile.lock index a7b1daaef4..52add9070a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -105,7 +105,7 @@ GEM bunny (2.2.1) amq-protocol (>= 2.0.0) byebug (8.2.1) - celluloid (0.17.2) + celluloid (0.17.3) celluloid-essentials celluloid-extras celluloid-fsm @@ -151,7 +151,6 @@ GEM activesupport (>= 4.1.0) hiredis (0.5.2) hitimes (1.2.3) - hitimes (1.2.3-x86-mingw32) i18n (0.7.0) jquery-rails (4.0.5) rails-dom-testing (~> 1.0) @@ -260,8 +259,8 @@ GEM sqlite3 (1.3.11-x64-mingw32) sqlite3 (1.3.11-x86-mingw32) stackprof (0.2.7) - sucker_punch (1.6.0) - celluloid (~> 0.17.2) + sucker_punch (2.0.0) + concurrent-ruby (~> 1.0.0) thor (0.19.1) thread (0.1.7) thread_safe (0.3.5) @@ -299,6 +298,7 @@ DEPENDENCIES bcrypt-ruby (~> 3.0.0) benchmark-ips byebug + celluloid coffee-rails (~> 4.1.0) dalli (>= 2.2.1) delayed_job @@ -334,7 +334,7 @@ DEPENDENCIES sneakers sqlite3 (~> 1.3.6) stackprof - sucker_punch (< 2.0) + sucker_punch turbolinks tzinfo-data uglifier (>= 1.3.0) diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb index aeb1fe1e73..2c5039ef4d 100644 --- a/activejob/lib/active_job/queue_adapters.rb +++ b/activejob/lib/active_job/queue_adapters.rb @@ -27,7 +27,7 @@ module ActiveJob # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes | # | Sidekiq | Yes | Yes | Yes | Queue | No | Job | # | Sneakers | Yes | Yes | No | Queue | Queue | No | - # | Sucker Punch | Yes | Yes | No | No | No | No | + # | Sucker Punch | Yes | Yes | Yes | No | No | No | # | Active Job Async | Yes | Yes | Yes | No | No | No | # | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A | # diff --git a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb index c6c35f8ab4..1037084341 100644 --- a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb @@ -19,11 +19,22 @@ module ActiveJob # Rails.application.config.active_job.queue_adapter = :sucker_punch class SuckerPunchAdapter def enqueue(job) #:nodoc: - JobWrapper.new.async.perform job.serialize + if JobWrapper.respond_to?(:perform_async) + # sucker_punch 2.0 API + JobWrapper.perform_async job.serialize + else + # sucker_punch 1.0 API + JobWrapper.new.async.perform job.serialize + end end def enqueue_at(job, timestamp) #:nodoc: - raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html" + if JobWrapper.respond_to?(:perform_in) + delay = timestamp - Time.current.to_f + JobWrapper.perform_in delay, job.serialize + else + raise NotImplementedError, 'sucker_punch 1.0 does not support `enqueued_at`. Please upgrade to version ~> 2.0.0 to enable this behavior.' + end end class JobWrapper #:nodoc: -- cgit v1.2.3 From 77383fc1e473819971d1e3ca614d7b361fa5cc33 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Wed, 27 Jan 2016 13:26:20 -0500 Subject: Do not use default attributes for STI when instantiating a subclass The commit which originally added this behavior did not consider that doing `Subclass.new` does not actually populate the `type` field in the attributes (though perhaps it should). We simply need to not use the defaults for STI related things unless we are instantiating the base class. Fixes #23285. --- activerecord/lib/active_record/inheritance.rb | 6 +++++- activerecord/test/cases/inheritance_test.rb | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 3a17f74b1d..3b6fb70d0d 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -52,7 +52,11 @@ module ActiveRecord attrs = args.first if has_attribute?(inheritance_column) - subclass = subclass_from_attributes(attrs) || subclass_from_attributes(column_defaults) + subclass = subclass_from_attributes(attrs) + + if subclass.nil? && base_class == self + subclass = subclass_from_attributes(column_defaults) + end end if subclass && subclass != self diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index c870247a4a..7da6842047 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -493,6 +493,10 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase assert_equal 'Firm', firm.type assert_instance_of Firm, firm + client = Client.new + assert_equal 'Client', client.type + assert_instance_of Client, client + firm = Company.new(type: 'Client') # overwrite the default type assert_equal 'Client', firm.type assert_instance_of Client, firm -- cgit v1.2.3 From 5e3a23a3079488a9fc8434e312df838a51732917 Mon Sep 17 00:00:00 2001 From: Bart de Water Date: Wed, 27 Jan 2016 19:35:37 +0100 Subject: Fix img alt attribute generation when using Sprockets >= 3.0 --- actionview/CHANGELOG.md | 5 +++++ actionview/lib/action_view/helpers/asset_tag_helper.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 98ac2c1c22..d85681e0d1 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,8 @@ +* Fix stripping the digest from the automatically generated img tag alt + attribute when assets are handled by Sprockets >=3.0. + + *Bart de Water* + * Create a new `ActiveSupport::SafeBuffer` instance when `content_for` is flushed. Fixes #19890 diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index cc54faa778..413c35954c 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -239,7 +239,7 @@ module ActionView # image_alt('underscored_file_name.png') # # => Underscored file name def image_alt(src) - File.basename(src, '.*'.freeze).sub(/-[[:xdigit:]]{32}\z/, ''.freeze).tr('-_'.freeze, ' '.freeze).capitalize + File.basename(src, '.*'.freeze).sub(/-[[:xdigit:]]{32,64}\z/, ''.freeze).tr('-_'.freeze, ' '.freeze).capitalize end # Returns an HTML video tag for the +sources+. If +sources+ is a string, -- cgit v1.2.3 From 385e0a3311960032fa149f8650d73fd483dc5f75 Mon Sep 17 00:00:00 2001 From: Marek Date: Wed, 27 Jan 2016 18:41:48 +0000 Subject: Fix typo in strong params hash deprecation message and remove unecessary spaces in string interpolation. --- actionpack/lib/action_controller/metal/strong_parameters.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index ba03430930..d3382ef296 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -587,11 +587,11 @@ module ActionController def method_missing(method_sym, *args, &block) if @parameters.respond_to?(method_sym) message = <<-DEPRECATE.squish - Method #{ method_sym } is deprecated and will be removed in Rails 5.1, + Method #{method_sym} is deprecated and will be removed in Rails 5.1, as `ActionController::Parameters` no longer inherits from hash. Using this deprecated behavior exposes potential security problems. If you continue to use this method you may be creating - a security vulunerability in your app that can be exploited. Instead, + a security vulnerability in your app that can be exploited. Instead, consider using one of these documented methods which are not deprecated: http://api.rubyonrails.org/v#{ActionPack.version}/classes/ActionController/Parameters.html DEPRECATE -- cgit v1.2.3 From e5e42a3687801a1dc1c3e36ea784a7a1479a9230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 27 Jan 2016 14:12:32 -0500 Subject: Add tests to #23288 --- actionview/test/template/asset_tag_helper_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 8592a2a083..8bfd19eb26 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -459,6 +459,7 @@ class AssetTagHelperTest < ActionView::TestCase [nil, '/', '/foo/bar/', 'foo/bar/'].each do |prefix| assert_equal 'Rails', image_alt("#{prefix}rails.png") assert_equal 'Rails', image_alt("#{prefix}rails-9c0a079bdd7701d7e729bd956823d153.png") + assert_equal 'Rails', image_alt("#{prefix}rails-f56ef62bc41b040664e801a38f068082a75d506d9048307e8096737463503d0b.png") assert_equal 'Long file name with hyphens', image_alt("#{prefix}long-file-name-with-hyphens.png") assert_equal 'Long file name with underscores', image_alt("#{prefix}long_file_name_with_underscores.png") end -- cgit v1.2.3 From 2c131141ca5018f41f85be429a02afac93a0241d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 27 Jan 2016 15:44:26 -0500 Subject: Remove celluloid from the Gemfile --- Gemfile | 1 - Gemfile.lock | 21 --------------------- .../test/support/integration/adapters/sidekiq.rb | 4 +--- 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/Gemfile b/Gemfile index 51020dbea2..9dd841d535 100644 --- a/Gemfile +++ b/Gemfile @@ -58,7 +58,6 @@ group :job do gem 'qu-redis', require: false gem 'delayed_job_active_record', require: false gem 'sequel', require: false - gem 'celluloid', require: false end # Action Cable diff --git a/Gemfile.lock b/Gemfile.lock index 67a0832dfa..f02a85f70d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -106,23 +106,6 @@ GEM bunny (2.2.1) amq-protocol (>= 2.0.0) byebug (8.2.1) - celluloid (0.17.3) - celluloid-essentials - celluloid-extras - celluloid-fsm - celluloid-pool - celluloid-supervision - timers (>= 4.1.1) - celluloid-essentials (0.20.5) - timers (>= 4.1.1) - celluloid-extras (0.20.5) - timers (>= 4.1.1) - celluloid-fsm (0.20.5) - timers (>= 4.1.1) - celluloid-pool (0.20.5) - timers (>= 4.1.1) - celluloid-supervision (0.20.5) - timers (>= 4.1.1) coffee-rails (4.1.0) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) @@ -154,7 +137,6 @@ GEM globalid (0.3.6) activesupport (>= 4.1.0) hiredis (0.5.2) - hitimes (1.2.3) i18n (0.7.0) jquery-rails (4.0.5) rails-dom-testing (~> 1.0) @@ -267,8 +249,6 @@ GEM thor (0.19.1) thread (0.1.7) thread_safe (0.3.5) - timers (4.1.1) - hitimes turbolinks (2.5.3) coffee-rails tzinfo (1.2.2) @@ -301,7 +281,6 @@ DEPENDENCIES bcrypt-ruby (~> 3.0.0) benchmark-ips byebug - celluloid coffee-rails (~> 4.1.0) dalli (>= 2.2.1) delayed_job diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb index 9aa07bcb52..2f19d7dacc 100644 --- a/activejob/test/support/integration/adapters/sidekiq.rb +++ b/activejob/test/support/integration/adapters/sidekiq.rb @@ -26,7 +26,7 @@ module SidekiqJobsManager continue_read.close death_write.close - # Celluloid & Sidekiq are not warning-clean :( + # Sidekiq is not warning-clean :( $VERBOSE = false $stdin.reopen('/dev/null') @@ -49,8 +49,6 @@ module SidekiqJobsManager self_write.puts("TERM") end - require 'celluloid' - Celluloid.logger = nil require 'sidekiq/launcher' sidekiq = Sidekiq::Launcher.new({queues: ["integration_tests"], environment: "test", -- cgit v1.2.3 From 0db5882bb09231f3d7ae40e3554eb67599b11d57 Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Wed, 27 Jan 2016 16:07:46 -0500 Subject: Update sucker_punch adapter's description [ci skip] --- .../lib/active_job/queue_adapters/sucker_punch_adapter.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb index 1037084341..311109e958 100644 --- a/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/sucker_punch_adapter.rb @@ -5,12 +5,10 @@ module ActiveJob # == Sucker Punch adapter for Active Job # # Sucker Punch is a single-process Ruby asynchronous processing library. - # It's girl_friday and DSL sugar on top of Celluloid. With Celluloid's - # actor pattern, we can do asynchronous processing within a single process. - # This reduces costs of hosting on a service like Heroku along with the - # memory footprint of having to maintain additional jobs if hosting on - # a dedicated server. All queues can run within a single Rails/Sinatra - # process. + # This reduces the cost of of hosting on a service like Heroku along + # with the memory footprint of having to maintain additional jobs if + # hosting on a dedicated server. All queues can run within a + # single application (eg. Rails, Sinatra, etc.) process. # # Read more about Sucker Punch {here}[https://github.com/brandonhilkert/sucker_punch]. # -- cgit v1.2.3 From e2ca039e5a4f819af70d794931c491b71c812335 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 28 Jan 2016 10:52:22 +1100 Subject: Slice out options for cache_fragment_name explicitly This allows expire_in (and other options) to be passed to the cache method --- actionview/lib/action_view/helpers/cache_helper.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 18b2102d73..401f398721 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -166,7 +166,8 @@ module ActionView # You can only declare one collection in a partial template file. def cache(name = {}, options = {}, &block) if controller.respond_to?(:perform_caching) && controller.perform_caching - safe_concat(fragment_for(cache_fragment_name(name, options), options, &block)) + name_options = options.slice(:skip_digest, :virtual_path) + safe_concat(fragment_for(cache_fragment_name(name, name_options), options, &block)) else yield end -- cgit v1.2.3 From 542b2e9c9b7e24f5337fd529a36f9d89f727e189 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 27 Jan 2016 17:46:06 -0800 Subject: change `@text_xml_idx` to an lvar and cache it on the stack this eliminates the ivar lookup and also eliminates the `||=` conditional that happens every time we called the `text_xml_idx` method. --- actionpack/lib/action_dispatch/http/mime_type.rb | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 6abbf9c754..a90c691ff7 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -116,13 +116,18 @@ module Mime def assort! sort! + text_xml_idx = index('text/xml') + # Take care of the broken text/xml entry by renaming or deleting it if text_xml_idx && app_xml_idx - app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two - exchange_xml_items if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list + app_xml.q = [text_xml(text_xml_idx).q, app_xml.q].max # set the q value to the max of the two + if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list + exchange_xml_items(text_xml_idx) + @app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx + end delete_at(text_xml_idx) # delete text_xml from the list elsif text_xml_idx - text_xml.name = Mime[:xml].to_s + text_xml(text_xml_idx).name = Mime[:xml].to_s end # Look for more specific XML-based types and sort them ahead of app/xml @@ -146,25 +151,18 @@ module Mime end private - def text_xml_idx - @text_xml_idx ||= index('text/xml') - end - def app_xml_idx @app_xml_idx ||= index(Mime[:xml].to_s) end - def text_xml - self[text_xml_idx] - end + alias :text_xml :[] def app_xml self[app_xml_idx] end - def exchange_xml_items - self[app_xml_idx], self[text_xml_idx] = text_xml, app_xml - @app_xml_idx, @text_xml_idx = text_xml_idx, app_xml_idx + def exchange_xml_items(text_xml_idx) + self[app_xml_idx], self[text_xml_idx] = text_xml(text_xml_idx), app_xml end end -- cgit v1.2.3 From f7f59c2eb732e9cb26060058e0ccac7dc74f324c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 27 Jan 2016 17:50:16 -0800 Subject: change `@app_xml_idx` to an lvar and cache it on the stack same strategy as `@text_xml_idx`: cache it on the stack to avoid ivar lookups and the `||=` call. --- actionpack/lib/action_dispatch/http/mime_type.rb | 26 +++++++++--------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index a90c691ff7..d953030139 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -117,13 +117,14 @@ module Mime sort! text_xml_idx = index('text/xml') + app_xml_idx = index(Mime[:xml].to_s) # Take care of the broken text/xml entry by renaming or deleting it if text_xml_idx && app_xml_idx - app_xml.q = [text_xml(text_xml_idx).q, app_xml.q].max # set the q value to the max of the two + app_xml(app_xml_idx).q = [text_xml(text_xml_idx).q, app_xml(app_xml_idx).q].max # set the q value to the max of the two if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list - exchange_xml_items(text_xml_idx) - @app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx + exchange_xml_items(text_xml_idx, app_xml_idx) + app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx end delete_at(text_xml_idx) # delete text_xml from the list elsif text_xml_idx @@ -136,11 +137,11 @@ module Mime while idx < length type = self[idx] - break if type.q < app_xml.q + break if type.q < app_xml(app_xml_idx).q if type.name.ends_with? '+xml' - self[app_xml_idx], self[idx] = self[idx], app_xml - @app_xml_idx = idx + self[app_xml_idx], self[idx] = self[idx], app_xml(app_xml_idx) + app_xml_idx = idx end idx += 1 end @@ -151,18 +152,11 @@ module Mime end private - def app_xml_idx - @app_xml_idx ||= index(Mime[:xml].to_s) - end - alias :text_xml :[] + alias :app_xml :[] - def app_xml - self[app_xml_idx] - end - - def exchange_xml_items(text_xml_idx) - self[app_xml_idx], self[text_xml_idx] = text_xml(text_xml_idx), app_xml + def exchange_xml_items(text_xml_idx, app_xml_idx) + self[app_xml_idx], self[text_xml_idx] = text_xml(text_xml_idx), app_xml(app_xml_idx) end end -- cgit v1.2.3 From 7fc79bc7907ea53aead3ff8461daedaacbf1cd71 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 27 Jan 2016 17:58:26 -0800 Subject: remove useless private methods This commit refactors the private methods that were just aliases to [] to just directly use [] and cache the return values on the stack. --- actionpack/lib/action_dispatch/http/mime_type.rb | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index d953030139..4a57e04c3a 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -121,26 +121,30 @@ module Mime # Take care of the broken text/xml entry by renaming or deleting it if text_xml_idx && app_xml_idx - app_xml(app_xml_idx).q = [text_xml(text_xml_idx).q, app_xml(app_xml_idx).q].max # set the q value to the max of the two + app_xml = self[app_xml_idx] + text_xml = self[text_xml_idx] + + app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list - exchange_xml_items(text_xml_idx, app_xml_idx) + self[app_xml_idx], self[text_xml_idx] = text_xml, app_xml app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx end delete_at(text_xml_idx) # delete text_xml from the list elsif text_xml_idx - text_xml(text_xml_idx).name = Mime[:xml].to_s + self[text_xml_idx].name = Mime[:xml].to_s end # Look for more specific XML-based types and sort them ahead of app/xml if app_xml_idx + app_xml = self[app_xml_idx] idx = app_xml_idx while idx < length type = self[idx] - break if type.q < app_xml(app_xml_idx).q + break if type.q < app_xml.q if type.name.ends_with? '+xml' - self[app_xml_idx], self[idx] = self[idx], app_xml(app_xml_idx) + self[app_xml_idx], self[idx] = self[idx], app_xml app_xml_idx = idx end idx += 1 @@ -150,14 +154,6 @@ module Mime map! { |i| Mime::Type.lookup(i.name) }.uniq! to_a end - - private - alias :text_xml :[] - alias :app_xml :[] - - def exchange_xml_items(text_xml_idx, app_xml_idx) - self[app_xml_idx], self[text_xml_idx] = text_xml(text_xml_idx), app_xml(app_xml_idx) - end end class << self -- cgit v1.2.3 From a447252ac4c97b06df271c04ddd7530014dd8c86 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 27 Jan 2016 18:05:15 -0800 Subject: remove == from AcceptItem Remove nonsense definition of == from `AcceptItem`. The definition only compared names and not `q` values or even object identity. The only use was in the `assort!` method that really just wanted the index of the item given the item's name. Instead we just change the caller to use `index` with the block form. --- actionpack/lib/action_dispatch/http/mime_type.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 4a57e04c3a..463b5fe405 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -106,18 +106,14 @@ module Mime result = @index <=> item.index if result == 0 result end - - def ==(item) - @name == item.to_s - end end class AcceptList < Array #:nodoc: def assort! sort! - text_xml_idx = index('text/xml') - app_xml_idx = index(Mime[:xml].to_s) + text_xml_idx = find_item_by_name self, 'text/xml' + app_xml_idx = find_item_by_name self, Mime[:xml].to_s # Take care of the broken text/xml entry by renaming or deleting it if text_xml_idx && app_xml_idx @@ -154,6 +150,11 @@ module Mime map! { |i| Mime::Type.lookup(i.name) }.uniq! to_a end + + private + def find_item_by_name(array, name) + array.index { |item| item.name == name } + end end class << self -- cgit v1.2.3 From 69d657db6faa24a237bebe564193a0a61decb01f Mon Sep 17 00:00:00 2001 From: Collin Graves Date: Wed, 27 Jan 2016 21:54:34 -0600 Subject: English explanation to multi-level nested join Added an "Or, in English..." explanation to the "Joining Nested Associations (Multiple Level)" example. --- guides/source/active_record_querying.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 784be91845..63658e7c8b 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1085,6 +1085,8 @@ SELECT categories.* FROM categories INNER JOIN tags ON tags.article_id = articles.id ``` +Or, in English: "return all categories that have articles, where those articles have a comment made by a guest, and where those articles also have a tag." + #### Specifying Conditions on the Joined Tables You can specify conditions on the joined tables using the regular [Array](#array-conditions) and [String](#pure-string-conditions) conditions. [Hash conditions](#hash-conditions) provide a special syntax for specifying conditions for the joined tables: -- cgit v1.2.3 From ed15217b054de379d7b0d283d08b71f185fdf2e9 Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Wed, 27 Jan 2016 21:14:31 -0800 Subject: Consistently warn that passing an offset to `find_nth` is deprecated @bogdan pointed out that a `loaded?` relation would not warn that the supplied offset would be removed. This fixes that oversight. https://github.com/rails/rails/commit/16a476e4f8f802774ae7c8dca2e59f4e672dc591#commitcomment-15706834 Although this second argument is probably not widely used, and would be ignored anyway in the loaded? case, this could protect callers from gotchas. [Ben Woosley & Victor Kmita] --- .../lib/active_record/relation/finder_methods.rb | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 3cbb12a09d..3f5d6de78a 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -489,20 +489,19 @@ module ActiveRecord end def find_nth(index, offset = nil) + # TODO: once the offset argument is removed we rely on offset_index + # within find_nth_with_limit, rather than pass it in via + # find_nth_with_limit_and_offset + if offset + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing an offset argument to find_nth is deprecated, + please use Relation#offset instead. + MSG + end if loaded? @records[index] else - # TODO: once the offset argument is removed we rely on offset_index - # within find_nth_with_limit, rather than pass it in via - # find_nth_with_limit_and_offset - if offset - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing an offset argument to find_nth is deprecated, - please use Relation#offset instead. - MSG - else - offset = offset_index - end + offset ||= offset_index @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first end end -- cgit v1.2.3 From a69281614a7ed6c134d9a799419dd34dd5293a81 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 28 Jan 2016 18:22:01 +0900 Subject: :arrow_left: indentation [ci-skip] --- activesupport/lib/active_support/core_ext/module/deprecation.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb index 56d670fbe8..f3f2e7f5fc 100644 --- a/activesupport/lib/active_support/core_ext/module/deprecation.rb +++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb @@ -13,8 +13,8 @@ class Module # # class MyLib::Deprecator # def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil) - # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}" - # Kernel.warn message + # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}" + # Kernel.warn message # end # end def deprecate(*method_names) -- cgit v1.2.3 From dc925119a3912ecfe0df400007163f33b99d6385 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 28 Jan 2016 17:13:47 +0900 Subject: Revert "Remove valid_scope_name? check - use ruby" This reverts commit f6db31ec16e42ee7713029f7120f0b011d1ddc6c. Reason: Scope names can very easily conflict, particularly when sharing Concerns within the team, or using multiple gems that extend AR models. It is true that Ruby has the ability to detect this with the -w option, but the reality is that we are depending on too many gems that do not care about Ruby warnings, therefore it might not be a realistic solution to turn this switch on in our real-world apps. --- activerecord/lib/active_record/scoping/named.rb | 10 ++++++++++ activerecord/test/cases/scoping/named_scoping_test.rb | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 103569c84d..5395bd6076 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -151,6 +151,7 @@ module ActiveRecord "a class method with the same name." end + valid_scope_name?(name) extension = Module.new(&block) if block if body.respond_to?(:to_proc) @@ -169,6 +170,15 @@ module ActiveRecord end end end + + protected + + def valid_scope_name?(name) + if respond_to?(name, true) + logger.warn "Creating scope :#{name}. " \ + "Overwriting existing method #{self.name}.#{name}." + end + end end end end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 7a8eaeccb7..db1e42fcc2 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -440,6 +440,25 @@ class NamedScopingTest < ActiveRecord::TestCase end end + def test_scopes_with_reserved_names + class << Topic + def public_method; end + public :public_method + + def protected_method; end + protected :protected_method + + def private_method; end + private :private_method + end + + [:public_method, :protected_method, :private_method].each do |reserved_method| + assert Topic.respond_to?(reserved_method, true) + ActiveRecord::Base.logger.expects(:warn) + Topic.scope(reserved_method) + end + end + def test_scopes_on_relations # Topic.replied approved_topics = Topic.all.approved.order('id DESC') -- cgit v1.2.3 From 60b8ab710d6afd5810f1a4625b6551190e12d81f Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 28 Jan 2016 22:29:55 +0900 Subject: scope needs the second argument --- activerecord/test/cases/scoping/named_scoping_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index db1e42fcc2..722153bb33 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -455,7 +455,7 @@ class NamedScopingTest < ActiveRecord::TestCase [:public_method, :protected_method, :private_method].each do |reserved_method| assert Topic.respond_to?(reserved_method, true) ActiveRecord::Base.logger.expects(:warn) - Topic.scope(reserved_method) + Topic.scope(reserved_method, -> { }) end end -- cgit v1.2.3 From 2812af720e4869f03e836d1b527b0a50c2a52b22 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 28 Jan 2016 22:30:52 +0900 Subject: Suppress :warning:s --- activerecord/test/cases/scoping/named_scoping_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 722153bb33..acba97bbb8 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -455,7 +455,7 @@ class NamedScopingTest < ActiveRecord::TestCase [:public_method, :protected_method, :private_method].each do |reserved_method| assert Topic.respond_to?(reserved_method, true) ActiveRecord::Base.logger.expects(:warn) - Topic.scope(reserved_method, -> { }) + silence_warnings { Topic.scope(reserved_method, -> { }) } end end -- cgit v1.2.3 From e0026e232744123a45aebd2a1bce8b2a748cac7b Mon Sep 17 00:00:00 2001 From: Mikhail Grachev Date: Thu, 28 Jan 2016 12:09:27 +0300 Subject: Fix a bug with initialize schema_migrations table This line causes an error when executing the command: `rails db:drop db:create db:schema:load` ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near "{" LINE 1: ...NSERT INTO "schema_migrations" (version) VALUES (#{v}), (#{v... --- .../lib/active_record/connection_adapters/abstract/schema_statements.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 002f2ea8ce..c7aff63228 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -992,7 +992,7 @@ module ActiveRecord if (duplicate = inserting.detect {|v| inserting.count(v) > 1}) raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict." end - execute "INSERT INTO #{sm_table} (version) VALUES #{inserting.map {|v| '(#{v})'}.join(', ') }" + execute "INSERT INTO #{sm_table} (version) VALUES #{inserting.map {|v| "('#{v}')"}.join(', ') }" end end -- cgit v1.2.3 From 7705fcf0b6c0a1217fa91a709dcf00701f4af694 Mon Sep 17 00:00:00 2001 From: Bogdan Gusiev Date: Thu, 28 Jan 2016 17:45:53 +0200 Subject: Reworked ActiveRecord::Relation#last to always use SQL instead of loading relation --- activerecord/CHANGELOG.md | 20 +++++++++++ .../lib/active_record/relation/finder_methods.rb | 39 ++++++++++------------ activerecord/test/cases/finder_test.rb | 32 +++++++++++++----- 3 files changed, 60 insertions(+), 31 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 50a0b291b3..faab0f9554 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,23 @@ +* Rework `ActiveRecord::Relation#last` + + 1. Always find last with ruby if relation is loaded + 2. Always use SQL instead if relation is not loaded. + 3. Deprecated relation loading when SQL order can not be automatically reversed + + Topic.order("title").load.last(3) + # before: SELECT ... + # after: No SQL + + Topic.order("title").last + # before: SELECT * FROM `topics` + # after: SELECT * FROM `topics` ORDER BY `topics`.`title` DESC LIMIT 1 + + Topic.order("coalesce(author, title)").last + # before: SELECT * FROM `topics` + # after: Deprecation Warning for irreversible order + + *Bogdan Gusiev* + * `ActiveRecord::Relation#reverse_order` throws `ActiveRecord::IrreversibleOrderError` when the order can not be reversed using current trivial algorithm. Also raises the same error when `#reverse_order` is called on diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 3f5d6de78a..5d4a045097 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -145,15 +145,23 @@ module ActiveRecord # # [#, #, #] def last(limit = nil) - if limit - if order_values.empty? && primary_key - order(arel_table[primary_key].desc).limit(limit).reverse - else - to_a.last(limit) - end - else - find_last - end + return find_last(limit) if loaded? + + result = order_values.empty? && primary_key ? order(arel_table[primary_key].desc) : reverse_order + result = result.limit!(limit || 1) + limit ? result.reverse : result.first + rescue ActiveRecord::IrreversibleOrderError + ActiveSupport::Deprecation.warn(<<-WARNING.squish) + Finding a last element by loading the relation when SQL ORDER + can not be reversed is deprecated. + Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case. + Please call `to_a.last` if you still want to load the relation. + WARNING + find_last(limit) + end + + def find_last(limit) + limit ? to_a.last(limit) : to_a.last end # Same as #last but raises ActiveRecord::RecordNotFound if no record @@ -523,19 +531,6 @@ module ActiveRecord relation.limit(limit).to_a end - def find_last - if loaded? - @records.last - else - @last ||= - if limit_value - to_a.last - else - reverse_order.limit(1).to_a.first - end - end - end - private def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc: diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 75a74c052d..cbeb37dd22 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -506,7 +506,7 @@ class FinderTest < ActiveRecord::TestCase end end - def test_take_and_first_and_last_with_integer_should_use_sql_limit + def test_take_and_first_and_last_with_integer_should_use_sql assert_sql(/LIMIT|ROWNUM <=/) { Topic.take(3).entries } assert_sql(/LIMIT|ROWNUM <=/) { Topic.first(2).entries } assert_sql(/LIMIT|ROWNUM <=/) { Topic.last(5).entries } @@ -516,16 +516,30 @@ class FinderTest < ActiveRecord::TestCase assert_equal Topic.order("title").to_a.last(2), Topic.order("title").last(2) end - def test_last_with_integer_and_order_should_not_use_sql_limit - query = assert_sql { Topic.order("title").last(5).entries } - assert_equal 1, query.length - assert_no_match(/LIMIT/, query.first) + def test_last_with_integer_and_order_should_use_sql + relation = Topic.order("title") + assert_queries(1) { relation.last(5) } + assert !relation.loaded? end - def test_last_with_integer_and_reorder_should_not_use_sql_limit - query = assert_sql { Topic.reorder("title").last(5).entries } - assert_equal 1, query.length - assert_no_match(/LIMIT/, query.first) + def test_last_with_integer_and_reorder_should_use_sql + relation = Topic.reorder("title") + assert_queries(1) { relation.last(5) } + assert !relation.loaded? + end + + def test_last_on_loaded_relation_should_not_use_sql + relation = Topic.limit(10).load + assert_no_queries do + relation.last + relation.last(2) + end + end + + def test_last_with_irreversible_order + assert_deprecated do + Topic.order("coalesce(author_name, title)").last + end end def test_take_and_first_and_last_with_integer_should_return_an_array -- cgit v1.2.3 From 31701fbdf28688ba53b8f2d2d26904954ae1b976 Mon Sep 17 00:00:00 2001 From: Ryan Manuel Date: Thu, 28 Jan 2016 12:43:24 -0600 Subject: Add an after_bundle callback in Rails plugin templates --- guides/source/5_0_release_notes.md | 3 +++ railties/CHANGELOG.md | 7 ++++++ .../generators/rails/plugin/plugin_generator.rb | 6 +++++ railties/test/generators/app_generator_test.rb | 5 ++-- railties/test/generators/plugin_generator_test.rb | 28 ++++++++++++++++++++++ 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 2650384df3..6537b089a9 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -120,6 +120,9 @@ 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)) +* Introduced an `after_bundle` callback for use in Rails plugin templates. + ([Pull Request](https://github.com/rails/rails/pull/)) + Action Pack ----------- diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 2c363c55da..91e879dd73 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -448,4 +448,11 @@ *Alex Robbin* +* Add `after_bundle` callbacks in Rails plugin templates. Useful for allowing + templates to perform actions that are dependent upon `bundle install`. + + Fixes #<> + + *Ryan Manuel* + Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/railties/CHANGELOG.md) for previous changes. diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 0c7a73a54e..b5e836b584 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -259,6 +259,12 @@ task default: :test public_task :apply_rails_template, :run_bundle + def run_after_bundle_callbacks + @after_bundle_callbacks.each do |callback| + callback.call + end + end + def name @name ||= begin # same as ActiveSupport::Inflector#underscore except not replacing '-' diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 136bdd1694..53f8ed6af0 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -703,9 +703,8 @@ class AppGeneratorTest < Rails::Generators::TestCase end sequence = ['install', 'exec spring binstub --all', 'echo ran after_bundle'] - ensure_bundler_first = -> command do @sequence_step ||= 0 - + ensure_bundler_first = -> command do assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}" @sequence_step += 1 end @@ -717,6 +716,8 @@ class AppGeneratorTest < Rails::Generators::TestCase end end end + + assert_equal 3, @sequence_step end protected diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 874bda17b7..ef33cd4ff7 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -634,6 +634,34 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "app/models/bukkits/article.rb", /class Article < ApplicationRecord/ end + def test_after_bundle_callback + path = 'http://example.org/rails_template' + template = %{ after_bundle { run 'echo ran after_bundle' } } + template.instance_eval "def read; self; end" # Make the string respond to read + + check_open = -> *args do + assert_equal [ path, 'Accept' => 'application/x-thor-template' ], args + template + end + + sequence = ['install', 'echo ran after_bundle'] + @sequence_step ||= 0 + ensure_bundler_first = -> command do + assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}" + @sequence_step += 1 + end + + generator([destination_root], template: path).stub(:open, check_open, template) do + generator.stub(:bundle_command, ensure_bundler_first) do + generator.stub(:run, ensure_bundler_first) do + quietly { generator.invoke_all } + end + end + end + + assert_equal 2, @sequence_step + end + protected def action(*args, &block) silence(:stdout){ generator.send(*args, &block) } -- cgit v1.2.3 From 3a06a06ca9cd508f32e6c7f73662140949c9c748 Mon Sep 17 00:00:00 2001 From: Ryan Manuel Date: Thu, 28 Jan 2016 13:06:11 -0600 Subject: Update doc with appropriate issue and pull request versions --- guides/source/5_0_release_notes.md | 2 +- railties/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 6537b089a9..b98983b59d 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -121,7 +121,7 @@ Please refer to the [Changelog][railties] for detailed changes. [Pull Request](https://github.com/rails/rails/pull/22288)) * Introduced an `after_bundle` callback for use in Rails plugin templates. - ([Pull Request](https://github.com/rails/rails/pull/)) + ([Pull Request](https://github.com/rails/rails/pull/23317)) Action Pack diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 91e879dd73..6076940323 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -451,7 +451,7 @@ * Add `after_bundle` callbacks in Rails plugin templates. Useful for allowing templates to perform actions that are dependent upon `bundle install`. - Fixes #<> + Fixes #<23315> *Ryan Manuel* -- cgit v1.2.3 From 020d6fda29c1f04818dbf467764fc8ac16b7042f Mon Sep 17 00:00:00 2001 From: eileencodes Date: Thu, 28 Jan 2016 14:18:01 -0500 Subject: Regression test for rendering file from absolute path Test that we are not allowing you to grab a file with an absolute path outside of your application directory. This is dangerous because it could be used to retrieve files from the server like `/etc/passwd`. --- actionpack/test/controller/render_test.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index d1b9586533..2e1a687513 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -270,6 +270,17 @@ class ExpiresInRenderTest < ActionController::TestCase response.body end + def test_dynamic_render_with_absolute_path + file = Tempfile.new + file.write "secrets!" + file.flush + assert_raises ActionView::MissingTemplate do + response = get :dynamic_render, params: { id: file.path } + end + ensure + file.unlink + end + def test_dynamic_render assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')) assert_raises ActionView::MissingTemplate do -- cgit v1.2.3 From 04de7353c2b920eb48b747c10facb6f237873a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 28 Jan 2016 14:57:08 -0500 Subject: Bundle update to fix tests locally --- Gemfile.lock | 53 ++++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f02a85f70d..04d0073800 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,9 @@ GIT remote: git://github.com/QueueClassic/queue_classic.git - revision: d144db29f1436e9e8b3c7a1a1ecd4442316a9ecd + revision: 2e3b624f3043849751b24a758d8c156337ead862 branch: master specs: - queue_classic (3.2.0.alpha) + queue_classic (3.2.0.RC1) pg (>= 0.17, < 0.19) GIT @@ -21,10 +21,10 @@ GIT GIT remote: git://github.com/sass/sass.git - revision: bce9509f396225d721501ea1070a6871b708abb1 + revision: ac8e7aa8e99c425237c708f347f76495a2e18519 branch: stable specs: - sass (3.4.20) + sass (3.4.21) PATH remote: . @@ -92,7 +92,7 @@ PATH GEM remote: https://rubygems.org/ specs: - amq-protocol (2.0.0) + amq-protocol (2.0.1) arel (7.0.0) backburner (1.2.0) beaneater (~> 1.0) @@ -103,12 +103,12 @@ GEM beaneater (1.0.0) benchmark-ips (2.3.0) builder (3.2.2) - bunny (2.2.1) - amq-protocol (>= 2.0.0) + bunny (2.2.2) + amq-protocol (>= 2.0.1) byebug (8.2.1) - coffee-rails (4.1.0) + coffee-rails (4.1.1) coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.0) + railties (>= 4.0.0, < 5.1.x) coffee-script (2.4.1) coffee-script-source execjs @@ -122,9 +122,9 @@ GEM delayed_job_active_record (4.1.0) activerecord (>= 3.0, < 5) delayed_job (>= 3.0, < 5) - em-hiredis (0.3.0) + em-hiredis (0.3.1) eventmachine (~> 1.0) - hiredis (~> 0.5.0) + hiredis (~> 0.6.0) erubis (2.7.0) eventmachine (1.0.9.1) execjs (2.6.0) @@ -136,9 +136,9 @@ GEM ffi (1.9.10-x86-mingw32) globalid (0.3.6) activesupport (>= 4.1.0) - hiredis (0.5.2) + hiredis (0.6.1) i18n (0.7.0) - jquery-rails (4.0.5) + jquery-rails (4.1.0) rails-dom-testing (~> 1.0) railties (>= 4.2.0) thor (>= 0.14, < 2.0) @@ -166,17 +166,17 @@ GEM mysql2 (0.4.2) mysql2 (0.4.2-x64-mingw32) mysql2 (0.4.2-x86-mingw32) - nokogiri (1.6.7.1) + nokogiri (1.6.7.2) mini_portile2 (~> 2.0.0.rc2) - nokogiri (1.6.7.1-x64-mingw32) + nokogiri (1.6.7.2-x64-mingw32) mini_portile2 (~> 2.0.0.rc2) - nokogiri (1.6.7.1-x86-mingw32) + nokogiri (1.6.7.2-x86-mingw32) mini_portile2 (~> 2.0.0.rc2) pg (0.18.4) pg (0.18.4-x64-mingw32) pg (0.18.4-x86-mingw32) - psych (2.0.16) - puma (2.15.3) + psych (2.0.17) + puma (2.16.0) que (0.11.2) racc (1.4.14) rack (2.0.0.alpha) @@ -191,13 +191,13 @@ GEM activesupport (>= 4.2.0.beta, < 5.0) nokogiri (~> 1.6.0) rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.2) + rails-html-sanitizer (1.0.3) loofah (~> 2.0) - rake (10.4.2) - rb-fsevent (0.9.6) + rake (10.5.0) + rb-fsevent (0.9.7) rb-inotify (0.9.5) ffi (>= 0.5.0) - rdoc (4.2.0) + rdoc (4.2.1) redcarpet (3.2.3) redis (3.2.2) redis-namespace (1.5.2) @@ -213,17 +213,16 @@ GEM redis (~> 3.0) resque (~> 1.25) rufus-scheduler (~> 3.0) - rufus-scheduler (3.1.10) + rufus-scheduler (3.2.0) sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) - sequel (4.29.0) + sequel (4.30.0) serverengine (1.5.11) sigdump (~> 0.2.2) - sidekiq (4.0.1) + sidekiq (4.0.2) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) - json (~> 1.0) redis (~> 3.2, >= 3.2.1) sigdump (0.2.3) sinatra (1.0) @@ -243,7 +242,7 @@ GEM sqlite3 (1.3.11) sqlite3 (1.3.11-x64-mingw32) sqlite3 (1.3.11-x86-mingw32) - stackprof (0.2.7) + stackprof (0.2.8) sucker_punch (2.0.0) concurrent-ruby (~> 1.0.0) thor (0.19.1) -- cgit v1.2.3 From 5181d5c283430762ea444d26e72890501a6584ff Mon Sep 17 00:00:00 2001 From: eileencodes Date: Thu, 28 Jan 2016 15:22:02 -0500 Subject: Run `file.close` before unlinking for travis This works on OSX but for some reason travis is throwing a ``` 1) Error: ExpiresInRenderTest#test_dynamic_render_with_absolute_path: NoMethodError: undefined method `unlink' for nil:NilClass ``` Looking at other tests in Railties the file has a name and we close it before unlinking, so I'm going to try that. --- actionpack/test/controller/render_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 2e1a687513..9430ab8db8 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -271,13 +271,14 @@ class ExpiresInRenderTest < ActionController::TestCase end def test_dynamic_render_with_absolute_path - file = Tempfile.new + file = Tempfile.new('name') file.write "secrets!" file.flush assert_raises ActionView::MissingTemplate do response = get :dynamic_render, params: { id: file.path } end ensure + file.close file.unlink end -- cgit v1.2.3 From 52a82f15f673ce9ab1a91564b672535f6fda8a10 Mon Sep 17 00:00:00 2001 From: Piotr Jakubowski Date: Thu, 28 Jan 2016 21:39:53 +0100 Subject: Put "Using Rails for API-only Applications" in table of contents When I was looking for more info regarding this the only way I ended up on that page was by googling something along the lines of "rails new api" (as I wanted to find out what are the proper parameters when generating api app). I think it's beneficial to have that page in table of contents. --- guides/source/documents.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index 4473eba478..2cf613f47f 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -135,6 +135,10 @@ work_in_progress: true url: profiling.html description: This guide explains how to profile your Rails applications to improve performance. + - + name: Using Rails for API-only Applications + url: api_app.html + description: This guide explains how to effectively use Rails to develop a JSON API application. - name: Extending Rails -- cgit v1.2.3 From f8c9ef8401ab11db5f64fc1e13e8b1fe5a0803ce Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 28 Jan 2016 14:14:17 -0800 Subject: convert AcceptList to a regular class we never use this custom array outside the mime type `parse` method. We can reduce the interaction to just a regular array, so we should use that instead (IOW, there was nothing special about AcceptList so we should remove it). --- actionpack/lib/action_dispatch/http/mime_type.rb | 39 ++++++++++++------------ 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 463b5fe405..065d7dd355 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -108,51 +108,50 @@ module Mime end end - class AcceptList < Array #:nodoc: - def assort! - sort! + class AcceptList #:nodoc: + def self.sort!(list) + list.sort! - text_xml_idx = find_item_by_name self, 'text/xml' - app_xml_idx = find_item_by_name self, Mime[:xml].to_s + text_xml_idx = find_item_by_name list, 'text/xml' + app_xml_idx = find_item_by_name list, Mime[:xml].to_s # Take care of the broken text/xml entry by renaming or deleting it if text_xml_idx && app_xml_idx - app_xml = self[app_xml_idx] - text_xml = self[text_xml_idx] + app_xml = list[app_xml_idx] + text_xml = list[text_xml_idx] app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list - self[app_xml_idx], self[text_xml_idx] = text_xml, app_xml + list[app_xml_idx], list[text_xml_idx] = text_xml, app_xml app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx end - delete_at(text_xml_idx) # delete text_xml from the list + list.delete_at(text_xml_idx) # delete text_xml from the list elsif text_xml_idx - self[text_xml_idx].name = Mime[:xml].to_s + list[text_xml_idx].name = Mime[:xml].to_s end # Look for more specific XML-based types and sort them ahead of app/xml if app_xml_idx - app_xml = self[app_xml_idx] + app_xml = list[app_xml_idx] idx = app_xml_idx - while idx < length - type = self[idx] + while idx < list.length + type = list[idx] break if type.q < app_xml.q if type.name.ends_with? '+xml' - self[app_xml_idx], self[idx] = self[idx], app_xml + list[app_xml_idx], list[idx] = list[idx], app_xml app_xml_idx = idx end idx += 1 end end - map! { |i| Mime::Type.lookup(i.name) }.uniq! - to_a + list.map! { |i| Mime::Type.lookup(i.name) }.uniq! + list end - private - def find_item_by_name(array, name) + def self.find_item_by_name(array, name) array.index { |item| item.name == name } end end @@ -198,7 +197,7 @@ module Mime accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact else - list, index = AcceptList.new, 0 + list, index = [], 0 accept_header.split(',').each do |header| params, q = header.split(PARAMETER_SEPARATOR_REGEXP) if params.present? @@ -212,7 +211,7 @@ module Mime end end end - list.assort! + AcceptList.sort! list end end -- cgit v1.2.3 From e9dff8e7b1b51ede2944890efb1e15257052f791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 28 Jan 2016 17:16:29 -0500 Subject: Mark API guide as work in progress The documentation team didn't reviewed it yet so it can't be published. --- guides/source/documents.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index 2cf613f47f..fdd6d4d33d 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -137,6 +137,7 @@ description: This guide explains how to profile your Rails applications to improve performance. - name: Using Rails for API-only Applications + work_in_progress: true url: api_app.html description: This guide explains how to effectively use Rails to develop a JSON API application. -- cgit v1.2.3 From 904e662ff229773145932073611e6765d674ea1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 28 Jan 2016 17:23:11 -0500 Subject: Middleware have no plural [ci skip] --- guides/source/api_app.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 86baa9ee84..92ef71aa0d 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -8,7 +8,7 @@ In this guide you will learn: * What Rails provides for API-only applications * How to configure Rails to start without any browser features -* How to decide which middlewares you will want to include +* How to decide which middleware you will want to include * How to decide which modules to use in your controller -------------------------------------------------------------------------------- @@ -86,7 +86,7 @@ Handled at the middleware layer: and return just the headers on the way out. This makes `HEAD` work reliably in all Rails APIs. -While you could obviously build these up in terms of existing Rack middlewares, +While you could obviously build these up in terms of existing Rack middleware, this list demonstrates that the default Rails middleware stack provides a lot of value, even if you're "just generating JSON". @@ -143,11 +143,11 @@ $ rails new my_api --api This will do three main things for you: -- Configure your application to start with a more limited set of middlewares +- Configure your application to start with a more limited set of middleware than normal. Specifically, it will not include any middleware primarily useful for browser applications (like cookies support) by default. - Make `ApplicationController` inherit from `ActionController::API` instead of - `ActionController::Base`. As with middlewares, this will leave out any Action + `ActionController::Base`. As with middleware, this will leave out any Action Controller modules that provide functionalities primarily used by browser applications. - Configure the generators to skip generating views, helpers and assets when @@ -185,10 +185,10 @@ class ApplicationController < ActionController::API end ``` -Choosing Middlewares +Choosing Middleware -------------------- -An API application comes with the following middlewares by default: +An API application comes with the following middleware by default: - `Rack::Sendfile` - `ActionDispatch::Static` @@ -206,14 +206,14 @@ An API application comes with the following middlewares by default: - `Rack::ConditionalGet` - `Rack::ETag` -See the [internal middlewares](rails_on_rack.html#internal-middleware-stack) +See the [internal middleware](rails_on_rack.html#internal-middleware-stack) section of the Rack guide for further information on them. -Other plugins, including Active Record, may add additional middlewares. In -general, these middlewares are agnostic to the type of application you are +Other plugins, including Active Record, may add additional middleware. In +general, these middleware are agnostic to the type of application you are building, and make sense in an API-only Rails application. -You can get a list of all middlewares in your application via: +You can get a list of all middleware in your application via: ```bash $ rails middleware @@ -329,7 +329,7 @@ will be: ### Other Middlewares -Rails ships with a number of other middlewares that you might want to use in an +Rails ships with a number of other middleware that you might want to use in an API application, especially if one of your API clients is the browser: - `Rack::MethodOverride` @@ -340,13 +340,13 @@ API application, especially if one of your API clients is the browser: * `ActionDispatch::Session::CookieStore` * `ActionDispatch::Session::MemCacheStore` -Any of these middlewares can be added via: +Any of these middleware can be added via: ```ruby config.middleware.use Rack::MethodOverride ``` -### Removing Middlewares +### Removing Middleware If you don't want to use a middleware that is included by default in the API-only middleware set, you can remove it with: @@ -355,7 +355,7 @@ middleware set, you can remove it with: config.middleware.delete ::Rack::Sendfile ``` -Keep in mind that removing these middlewares will remove support for certain +Keep in mind that removing these middleware will remove support for certain features in Action Controller. Choosing Controller Modules -- cgit v1.2.3 From ee9d479941061acdec6b3a6a3805408624f8cc22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 28 Jan 2016 17:23:27 -0500 Subject: Remove Rack::Lock from the API guides It is not always there anymore [ci skip] --- guides/source/api_app.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 92ef71aa0d..38ccfdd1b8 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -192,7 +192,6 @@ An API application comes with the following middleware by default: - `Rack::Sendfile` - `ActionDispatch::Static` -- `Rack::Lock` - `ActiveSupport::Cache::Strategy::LocalCache::Middleware` - `ActionDispatch::RequestId` - `Rails::Rack::Logger` @@ -296,9 +295,6 @@ config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" Make sure to configure your server to support these options following the instructions in the `Rack::Sendfile` documentation. -NOTE: The `Rack::Sendfile` middleware is always outside of the `Rack::Lock` -mutex, even in single-threaded applications. - ### Using ActionDispatch::Request `ActionDispatch::Request#params` will take parameters from the client in the JSON -- cgit v1.2.3 From 3745d5430b44a6c3511b5b7a5feaf51b3db49c7e Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Thu, 28 Jan 2016 17:56:51 -0500 Subject: Docs review of api_app.md Pass through correcting api_app.md. The list of included modules and middleware was tested through a sample API app, and was listed in the same order an end user would see in their terminal. [ci skip] --- guides/source/api_app.md | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 38ccfdd1b8..e3481ce046 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -44,11 +44,11 @@ using Rails is: "isn't using Rails to spit out some JSON overkill? Shouldn't I just use something like Sinatra?". For very simple APIs, this may be true. However, even in very HTML-heavy -applications, most of an application's logic is actually outside of the view +applications, most of an application's logic lives outside of the view layer. The reason most people use Rails is that it provides a set of defaults that -allows us to get up and running quickly without having to make a lot of trivial +allows developers to get up and running quickly, without having to make a lot of trivial decisions. Let's take a look at some of the things that Rails provides out of the box that are @@ -97,7 +97,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](http://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 @@ -126,7 +126,7 @@ when configuring Active Record. **The short version is**: you may not have thought about which parts of Rails are still applicable even if you remove the view layer, but the answer turns out -to be "most of it". +to be most of it. The Basic Configuration ----------------------- @@ -192,10 +192,11 @@ An API application comes with the following middleware by default: - `Rack::Sendfile` - `ActionDispatch::Static` +- `ActionDispatch::LoadInterlock` - `ActiveSupport::Cache::Strategy::LocalCache::Middleware` +- `Rack::Runtime` - `ActionDispatch::RequestId` - `Rails::Rack::Logger` -- `Rack::Runtime` - `ActionDispatch::ShowExceptions` - `ActionDispatch::DebugExceptions` - `ActionDispatch::RemoteIp` @@ -323,7 +324,7 @@ will be: { :person => { :firstName => "Yehuda", :lastName => "Katz" } } ``` -### Other Middlewares +### Other Middleware Rails ships with a number of other middleware that you might want to use in an API application, especially if one of your API clients is the browser: @@ -360,22 +361,24 @@ Choosing Controller Modules An API application (using `ActionController::API`) comes with the following controller modules by default: -- `ActionController::UrlFor`: Makes `url_for` and friends available. +- `ActionController::UrlFor`: Makes `url_for` and similar helpers available. - `ActionController::Redirecting`: Support for `redirect_to`. -- `ActionController::Rendering`: Basic support for rendering. +- `AbstractController::Rendering` and `ActionController::ApiRendering`: Basic support for rendering. - `ActionController::Renderers::All`: Support for `render :json` and friends. - `ActionController::ConditionalGet`: Support for `stale?`. -- `ActionController::ForceSSL`: Support for `force_ssl`. -- `ActionController::DataStreaming`: Support for `send_file` and `send_data`. -- `AbstractController::Callbacks`: Support for `before_action` and friends. -- `ActionController::Instrumentation`: Support for the instrumentation - hooks defined by Action Controller (see [the instrumentation - guide](active_support_instrumentation.html#action-controller)). -- `ActionController::Rescue`: Support for `rescue_from`. - `ActionController::BasicImplicitRender`: Makes sure to return an empty response if there's not an explicit one. - `ActionController::StrongParameters`: Support for parameters white-listing in combination with Active Model mass assignment. +- `ActionController::ForceSSL`: Support for `force_ssl`. +- `ActionController::DataStreaming`: Support for `send_file` and `send_data`. +- `AbstractController::Callbacks`: Support for `before_action` and + similar helpers. +- `ActionController::Rescue`: Support for `rescue_from`. +- `ActionController::Instrumentation`: Support for the instrumentation + hooks defined by Action Controller (see [the instrumentation + guide](active_support_instrumentation.html#action-controller) for +more information regarding this). - `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash so you don't have to specify root elements sending POST requests for instance. @@ -404,5 +407,5 @@ Some common modules you might want to add: - `ActionController::Cookies`: Support for `cookies`, which includes support for signed and encrypted cookies. This requires the cookies middleware. -The best place to add a module is in your `ApplicationController` but you can +The best place to add a module is in your `ApplicationController`, but you can also add modules to individual controllers. -- cgit v1.2.3 From c082a7aae4a41b52e58c7dea43e7e9c6c41d11ad Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 28 Jan 2016 16:08:03 -0800 Subject: speed up accept header parsing a bit. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Accept header is taken from what Safari on El Capitan sends: ```ruby require 'benchmark/ips' require 'action_dispatch/http/mime_type' require 'active_support/all' accept = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' Benchmark.ips do |x| x.report "omg" do Mime::Type.parse(accept) end end ``` Before: ``` [aaron@TC actionpack (master)]$ be ruby ../x.rb Calculating ------------------------------------- omg 3.181k i/100ms ------------------------------------------------- omg 35.062k (±12.8%) i/s - 174.955k [aaron@TC actionpack (master)]$ be ruby ../x.rb Calculating ------------------------------------- omg 3.153k i/100ms ------------------------------------------------- omg 33.724k (±12.4%) i/s - 167.109k [aaron@TC actionpack (master)]$ be ruby ../x.rb Calculating ------------------------------------- omg 3.575k i/100ms ------------------------------------------------- omg 37.251k (±10.4%) i/s - 185.900k ``` After: ``` [aaron@TC actionpack (master)]$ be ruby ../x.rb Calculating ------------------------------------- omg 3.365k i/100ms ------------------------------------------------- omg 40.069k (±16.1%) i/s - 198.535k [aaron@TC actionpack (master)]$ be ruby ../x.rb Calculating ------------------------------------- omg 4.168k i/100ms ------------------------------------------------- omg 47.596k (± 7.7%) i/s - 237.576k [aaron@TC actionpack (master)]$ be ruby ../x.rb Calculating ------------------------------------- omg 4.282k i/100ms ------------------------------------------------- omg 43.626k (±17.7%) i/s - 209.818k ``` --- actionpack/lib/action_dispatch/http/mime_type.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 065d7dd355..4672ea7199 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -1,3 +1,5 @@ +# -*- frozen-string-literal: true -*- + require 'singleton' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/string/starts_ends_with' @@ -157,7 +159,7 @@ module Mime end class << self - TRAILING_STAR_REGEXP = /(text|application)\/\*/ + TRAILING_STAR_REGEXP = /^(text|application)\/\*/ PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/ def register_callback(&block) @@ -200,15 +202,16 @@ module Mime list, index = [], 0 accept_header.split(',').each do |header| params, q = header.split(PARAMETER_SEPARATOR_REGEXP) - if params.present? - params.strip! - params = parse_trailing_star(params) || [params] + next unless params + params.strip! + next if params.empty? + + params = parse_trailing_star(params) || [params] - params.each do |m| - list << AcceptItem.new(index, m.to_s, q) - index += 1 - end + params.each do |m| + list << AcceptItem.new(index, m.to_s, q) + index += 1 end end AcceptList.sort! list -- cgit v1.2.3 From a48f97e0e611496f96882a5383756a11758c7596 Mon Sep 17 00:00:00 2001 From: Francis Go Date: Fri, 29 Jan 2016 14:18:11 +1100 Subject: Getting Started Guide: Update Ruby version to 2.3.0p0 --- guides/source/getting_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 9677ab1583..3392dad897 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -93,7 +93,7 @@ current version of Ruby installed: ```bash $ ruby -v -ruby 2.2.2p95 +ruby 2.3.0p0 ``` TIP: A number of tools exist to help you quickly install Ruby and Ruby -- cgit v1.2.3 From d3f178bb92473b4d7bb400be56c983203b1662e9 Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Fri, 29 Jan 2016 11:46:39 +0530 Subject: Change number_to_currency behavior for checking negativity - Instead of using `to_f.phase`, just use `to_f.negative`?. - This change works same for all cases except when number is "-0.0". -0.0.to_f.negative? => false -0.0.to_f.phase? => pi - So -0.0 will be treated as positive from now onwards. - So this change reverts changes from https://github.com/rails/rails/pull/6512. - But it should be acceptable as we could not find any currency which supports negative zeros. --- activesupport/CHANGELOG.md | 15 +++++++++++++++ .../number_helper/number_to_currency_converter.rb | 6 +----- activesupport/test/number_helper_test.rb | 1 - 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 21c79949ca..7701e0a7a2 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,18 @@ +* 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. + This change works same for all cases except when number is "-0.0". + + -0.0.to_f.negative? => false + -0.0.to_f.phase? => 3.14 + + This change reverts changes from https://github.com/rails/rails/pull/6512. + But it should be acceptable as we could not find any currency which + supports negative zeros. + + *Prathamesh Sonpatki*, *Rafael Mendonça França* + * Match `HashWithIndifferentAccess#default`'s behaviour with `Hash#default`. *David Cornu* diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb index 7986eb50f0..fe8e871fed 100644 --- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb @@ -7,7 +7,7 @@ module ActiveSupport number = self.number.to_s.strip format = options[:format] - if is_negative?(number) + if number.to_f.negative? format = options[:negative_format] number = absolute_value(number) end @@ -18,10 +18,6 @@ module ActiveSupport private - def is_negative?(number) - number.to_f.phase != 0 - end - def absolute_value(number) number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, '') end diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb index b3464462c8..6696111476 100644 --- a/activesupport/test/number_helper_test.rb +++ b/activesupport/test/number_helper_test.rb @@ -74,7 +74,6 @@ module ActiveSupport assert_equal("1,234,567,890.50 Kč", number_helper.number_to_currency("1234567890.50", {:unit => "Kč", :format => "%n %u"})) assert_equal("1,234,567,890.50 - Kč", number_helper.number_to_currency("-1234567890.50", {:unit => "Kč", :format => "%n %u", :negative_format => "%n - %u"})) assert_equal("0.00", number_helper.number_to_currency(+0.0, {:unit => "", :negative_format => "(%n)"})) - assert_equal("(0.00)", number_helper.number_to_currency(-0.0, {:unit => "", :negative_format => "(%n)"})) end end -- cgit v1.2.3 From 1b46a1158c221c3910f80f183f6591f14e68c236 Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Fri, 29 Jan 2016 14:12:00 +0530 Subject: Fix broken number_to_currency tests --- .../lib/active_support/number_helper/number_to_currency_converter.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb index fe8e871fed..57f40f33bf 100644 --- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/numeric/inquiry' + module ActiveSupport module NumberHelper class NumberToCurrencyConverter < NumberConverter # :nodoc: -- cgit v1.2.3 From 92e565825d102a0f4d6ed2368d9510008da1da21 Mon Sep 17 00:00:00 2001 From: Ryan Manuel Date: Fri, 29 Jan 2016 09:27:00 -0600 Subject: Implemented pull request changes. --- guides/source/5_0_release_notes.md | 3 --- railties/CHANGELOG.md | 12 +++++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index b98983b59d..2650384df3 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -120,9 +120,6 @@ 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)) -* Introduced an `after_bundle` callback for use in Rails plugin templates. - ([Pull Request](https://github.com/rails/rails/pull/23317)) - Action Pack ----------- diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 6076940323..f94bb49135 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,8 @@ +* Add `after_bundle` callbacks in Rails plugin templates. Useful for allowing + templates to perform actions that are dependent upon `bundle install`. + + *Ryan Manuel* + * Bring back `TEST=` env for `rake test` task. *Yves Senn* @@ -448,11 +453,4 @@ *Alex Robbin* -* Add `after_bundle` callbacks in Rails plugin templates. Useful for allowing - templates to perform actions that are dependent upon `bundle install`. - - Fixes #<23315> - - *Ryan Manuel* - Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/railties/CHANGELOG.md) for previous changes. -- cgit v1.2.3 From c8818dfcdf9e92364745000eefe46132a43f8700 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 29 Jan 2016 09:28:24 -0700 Subject: Don't recommend using `ActiveRecord::Base[]` These methods are more expensive than the alternatives, and have strange semantics that are likely undesirable. --- activerecord/lib/active_record/base.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 4a31a1aa84..fdffc3e6b9 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -132,9 +132,6 @@ module ActiveRecord #:nodoc: # end # end # - # You can alternatively use self[:attribute]=(value) and self[:attribute] - # or write_attribute(:attribute, value) and read_attribute(:attribute). - # # == Attribute query methods # # In addition to the basic accessors, query methods are also automatically available on the Active Record object. -- cgit v1.2.3 From 74497eabd52f2f9f8c383808b11286283046c2b2 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Thu, 28 Jan 2016 15:25:31 +1030 Subject: Revert "Revert "Eliminate the EventMachine dependency"" --- Gemfile.lock | 7 +- actioncable/actioncable.gemspec | 3 +- .../lib/action_cable/channel/periodic_timers.rb | 4 +- actioncable/lib/action_cable/channel/streams.rb | 2 +- actioncable/lib/action_cable/connection.rb | 5 +- actioncable/lib/action_cable/connection/base.rb | 32 +++-- .../lib/action_cable/connection/client_socket.rb | 152 +++++++++++++++++++++ .../action_cable/connection/internal_channel.rb | 4 +- actioncable/lib/action_cable/connection/stream.rb | 59 ++++++++ .../action_cable/connection/stream_event_loop.rb | 68 +++++++++ .../lib/action_cable/connection/web_socket.rb | 22 +-- actioncable/lib/action_cable/process/logging.rb | 7 - actioncable/lib/action_cable/server.rb | 4 - actioncable/lib/action_cable/server/base.rb | 4 + actioncable/lib/action_cable/server/connections.rb | 8 +- .../lib/action_cable/subscription_adapter/async.rb | 4 +- .../subscription_adapter/postgresql.rb | 4 +- .../lib/action_cable/subscription_adapter/redis.rb | 16 +++ actioncable/test/channel/periodic_timers_test.rb | 2 +- actioncable/test/channel/stream_test.rb | 22 ++- actioncable/test/connection/base_test.rb | 19 +-- actioncable/test/connection/identifier_test.rb | 4 +- .../test/connection/multiple_identifiers_test.rb | 4 +- actioncable/test/stubs/test_server.rb | 3 +- actioncable/test/subscription_adapter/common.rb | 3 - actioncable/test/test_helper.rb | 26 +--- .../generators/rails/app/templates/config.ru.tt | 3 +- 27 files changed, 385 insertions(+), 106 deletions(-) create mode 100644 actioncable/lib/action_cable/connection/client_socket.rb create mode 100644 actioncable/lib/action_cable/connection/stream.rb create mode 100644 actioncable/lib/action_cable/connection/stream_event_loop.rb delete mode 100644 actioncable/lib/action_cable/process/logging.rb diff --git a/Gemfile.lock b/Gemfile.lock index 04d0073800..ddf5cc88a9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -32,8 +32,7 @@ PATH actioncable (5.0.0.beta1.1) actionpack (= 5.0.0.beta1.1) coffee-rails (~> 4.1.0) - eventmachine (~> 1.0) - faye-websocket (~> 0.10.0) + nio4r (~> 1.2) websocket-driver (~> 0.6.1) actionmailer (5.0.0.beta1.1) actionpack (= 5.0.0.beta1.1) @@ -128,9 +127,6 @@ GEM erubis (2.7.0) eventmachine (1.0.9.1) execjs (2.6.0) - faye-websocket (0.10.2) - eventmachine (>= 0.12.0) - websocket-driver (>= 0.5.1) ffi (1.9.10) ffi (1.9.10-x64-mingw32) ffi (1.9.10-x86-mingw32) @@ -166,6 +162,7 @@ GEM mysql2 (0.4.2) mysql2 (0.4.2-x64-mingw32) mysql2 (0.4.2-x86-mingw32) + nio4r (1.2.0) nokogiri (1.6.7.2) mini_portile2 (~> 2.0.0.rc2) nokogiri (1.6.7.2-x64-mingw32) diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec index a36acc8f6f..14f968f1ef 100644 --- a/actioncable/actioncable.gemspec +++ b/actioncable/actioncable.gemspec @@ -21,8 +21,7 @@ Gem::Specification.new do |s| s.add_dependency 'actionpack', version s.add_dependency 'coffee-rails', '~> 4.1.0' - s.add_dependency 'eventmachine', '~> 1.0' - s.add_dependency 'faye-websocket', '~> 0.10.0' + s.add_dependency 'nio4r', '~> 1.2' s.add_dependency 'websocket-driver', '~> 0.6.1' s.add_development_dependency 'em-hiredis', '~> 0.3.0' diff --git a/actioncable/lib/action_cable/channel/periodic_timers.rb b/actioncable/lib/action_cable/channel/periodic_timers.rb index 7f0fb37afc..56597d02d7 100644 --- a/actioncable/lib/action_cable/channel/periodic_timers.rb +++ b/actioncable/lib/action_cable/channel/periodic_timers.rb @@ -27,14 +27,14 @@ module ActionCable def start_periodic_timers self.class.periodic_timers.each do |callback, options| - active_periodic_timers << EventMachine::PeriodicTimer.new(options[:every]) do + active_periodic_timers << Concurrent::TimerTask.new(execution_interval: options[:every]) do connection.worker_pool.async_run_periodic_timer(self, callback) end end end def stop_periodic_timers - active_periodic_timers.each { |timer| timer.cancel } + active_periodic_timers.each { |timer| timer.shutdown } end end end diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb index e2876ef6fa..a26373e387 100644 --- a/actioncable/lib/action_cable/channel/streams.rb +++ b/actioncable/lib/action_cable/channel/streams.rb @@ -75,7 +75,7 @@ module ActionCable callback ||= default_stream_callback(broadcasting) streams << [ broadcasting, callback ] - EM.next_tick do + Concurrent.global_io_executor.post do pubsub.subscribe(broadcasting, callback, lambda do transmit_subscription_confirmation logger.info "#{self.class.name} is streaming from #{broadcasting}" diff --git a/actioncable/lib/action_cable/connection.rb b/actioncable/lib/action_cable/connection.rb index b672e00682..902efb07e2 100644 --- a/actioncable/lib/action_cable/connection.rb +++ b/actioncable/lib/action_cable/connection.rb @@ -5,12 +5,15 @@ module ActionCable eager_autoload do autoload :Authorization autoload :Base + autoload :ClientSocket autoload :Identification autoload :InternalChannel autoload :MessageBuffer - autoload :WebSocket + autoload :Stream + autoload :StreamEventLoop autoload :Subscriptions autoload :TaggedLoggerProxy + autoload :WebSocket end end end diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index bb8850aaa0..0016d1a1a4 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -49,14 +49,14 @@ module ActionCable include Authorization attr_reader :server, :env, :subscriptions, :logger - delegate :worker_pool, :pubsub, to: :server + delegate :stream_event_loop, :worker_pool, :pubsub, to: :server def initialize(server, env) @server, @env = server, env @logger = new_tagged_logger - @websocket = ActionCable::Connection::WebSocket.new(env) + @websocket = ActionCable::Connection::WebSocket.new(env, self, stream_event_loop) @subscriptions = ActionCable::Connection::Subscriptions.new(self) @message_buffer = ActionCable::Connection::MessageBuffer.new(self) @@ -70,10 +70,6 @@ module ActionCable logger.info started_request_message if websocket.possible? && allow_request_origin? - websocket.on(:open) { |event| send_async :on_open } - websocket.on(:message) { |event| on_message event.data } - websocket.on(:close) { |event| send_async :on_close } - respond_to_successful_request else respond_to_invalid_request @@ -121,6 +117,22 @@ module ActionCable transmit ActiveSupport::JSON.encode(identifier: ActionCable::INTERNAL[:identifiers][:ping], message: Time.now.to_i) end + def on_open # :nodoc: + send_async :handle_open + end + + def on_message(message) # :nodoc: + message_buffer.append message + end + + def on_error(message) # :nodoc: + # ignore + end + + def on_close # :nodoc: + send_async :handle_close + end + protected # The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc. def request @@ -139,7 +151,7 @@ module ActionCable attr_reader :message_buffer private - def on_open + def handle_open connect if respond_to?(:connect) subscribe_to_internal_channel beat @@ -150,11 +162,7 @@ module ActionCable respond_to_invalid_request end - def on_message(message) - message_buffer.append message - end - - def on_close + def handle_close logger.info finished_request_message server.remove_connection(self) diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb new file mode 100644 index 0000000000..62dd753646 --- /dev/null +++ b/actioncable/lib/action_cable/connection/client_socket.rb @@ -0,0 +1,152 @@ +require 'websocket/driver' + +module ActionCable + module Connection + #-- + # This class is heavily based on faye-websocket-ruby + # + # Copyright (c) 2010-2015 James Coglan + class ClientSocket # :nodoc: + def self.determine_url(env) + scheme = secure_request?(env) ? 'wss:' : 'ws:' + "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }" + end + + def self.secure_request?(env) + return true if env['HTTPS'] == 'on' + return true if env['HTTP_X_FORWARDED_SSL'] == 'on' + return true if env['HTTP_X_FORWARDED_SCHEME'] == 'https' + return true if env['HTTP_X_FORWARDED_PROTO'] == 'https' + return true if env['rack.url_scheme'] == 'https' + + return false + end + + CONNECTING = 0 + OPEN = 1 + CLOSING = 2 + CLOSED = 3 + + attr_reader :env, :url + + def initialize(env, event_target, stream_event_loop) + @env = env + @event_target = event_target + @stream_event_loop = stream_event_loop + + @url = ClientSocket.determine_url(@env) + + @driver = @driver_started = nil + + @ready_state = CONNECTING + + # The driver calls +env+, +url+, and +write+ + @driver = ::WebSocket::Driver.rack(self) + + @driver.on(:open) { |e| open } + @driver.on(:message) { |e| receive_message(e.data) } + @driver.on(:close) { |e| begin_close(e.reason, e.code) } + @driver.on(:error) { |e| emit_error(e.message) } + + @stream = ActionCable::Connection::Stream.new(@stream_event_loop, self) + + if callback = @env['async.callback'] + callback.call([101, {}, @stream]) + end + end + + def start_driver + return if @driver.nil? || @driver_started + @driver_started = true + @driver.start + end + + def rack_response + start_driver + [ -1, {}, [] ] + end + + def write(data) + @stream.write(data) + end + + def transmit(message) + return false if @ready_state > OPEN + case message + when Numeric then @driver.text(message.to_s) + when String then @driver.text(message) + when Array then @driver.binary(message) + else false + end + end + + def close(code = nil, reason = nil) + code ||= 1000 + reason ||= '' + + unless code == 1000 or (code >= 3000 and code <= 4999) + raise ArgumentError, "Failed to execute 'close' on WebSocket: " + + "The code must be either 1000, or between 3000 and 4999. " + + "#{code} is neither." + end + + @ready_state = CLOSING unless @ready_state == CLOSED + @driver.close(reason, code) + end + + def parse(data) + @driver.parse(data) + end + + def client_gone + finalize_close + end + + def alive? + @ready_state == OPEN + end + + private + def open + return unless @ready_state == CONNECTING + @ready_state = OPEN + + @event_target.on_open + end + + def receive_message(data) + return unless @ready_state == OPEN + + @event_target.on_message(data) + end + + def emit_error(message) + return if @ready_state >= CLOSING + + @event_target.on_error(message) + end + + def begin_close(reason, code) + return if @ready_state == CLOSED + @ready_state = CLOSING + @close_params = [reason, code] + + if @stream + @stream.shutdown + else + finalize_close + end + end + + def finalize_close + return if @ready_state == CLOSED + @ready_state = CLOSED + + reason = @close_params ? @close_params[0] : '' + code = @close_params ? @close_params[1] : 1006 + + @event_target.on_close(code, reason) + end + end + end +end diff --git a/actioncable/lib/action_cable/connection/internal_channel.rb b/actioncable/lib/action_cable/connection/internal_channel.rb index 54ed7672d2..27826792b3 100644 --- a/actioncable/lib/action_cable/connection/internal_channel.rb +++ b/actioncable/lib/action_cable/connection/internal_channel.rb @@ -15,14 +15,14 @@ module ActionCable @_internal_subscriptions ||= [] @_internal_subscriptions << [ internal_channel, callback ] - EM.next_tick { pubsub.subscribe(internal_channel, callback) } + Concurrent.global_io_executor.post { pubsub.subscribe(internal_channel, callback) } logger.info "Registered connection (#{connection_identifier})" end end def unsubscribe_from_internal_channel if @_internal_subscriptions.present? - @_internal_subscriptions.each { |channel, callback| EM.next_tick { pubsub.unsubscribe(channel, callback) } } + @_internal_subscriptions.each { |channel, callback| Concurrent.global_io_executor.post { pubsub.unsubscribe(channel, callback) } } end end diff --git a/actioncable/lib/action_cable/connection/stream.rb b/actioncable/lib/action_cable/connection/stream.rb new file mode 100644 index 0000000000..ace250cd16 --- /dev/null +++ b/actioncable/lib/action_cable/connection/stream.rb @@ -0,0 +1,59 @@ +module ActionCable + module Connection + #-- + # This class is heavily based on faye-websocket-ruby + # + # Copyright (c) 2010-2015 James Coglan + class Stream + def initialize(event_loop, socket) + @event_loop = event_loop + @socket_object = socket + @stream_send = socket.env['stream.send'] + + @rack_hijack_io = nil + + hijack_rack_socket + end + + def each(&callback) + @stream_send ||= callback + end + + def close + shutdown + @socket_object.client_gone + end + + def shutdown + clean_rack_hijack + end + + def write(data) + return @rack_hijack_io.write(data) if @rack_hijack_io + return @stream_send.call(data) if @stream_send + rescue EOFError + @socket_object.client_gone + end + + def receive(data) + @socket_object.parse(data) + end + + private + def hijack_rack_socket + return unless @socket_object.env['rack.hijack'] + + @socket_object.env['rack.hijack'].call + @rack_hijack_io = @socket_object.env['rack.hijack_io'] + + @event_loop.attach(@rack_hijack_io, self) + end + + def clean_rack_hijack + return unless @rack_hijack_io + @event_loop.detach(@rack_hijack_io, self) + @rack_hijack_io = nil + end + end + end +end diff --git a/actioncable/lib/action_cable/connection/stream_event_loop.rb b/actioncable/lib/action_cable/connection/stream_event_loop.rb new file mode 100644 index 0000000000..f773814973 --- /dev/null +++ b/actioncable/lib/action_cable/connection/stream_event_loop.rb @@ -0,0 +1,68 @@ +require 'nio' + +module ActionCable + module Connection + class StreamEventLoop + def initialize + @nio = NIO::Selector.new + @map = {} + @stopping = false + @todo = Queue.new + + Thread.new do + Thread.current.abort_on_exception = true + run + end + end + + def attach(io, stream) + @todo << lambda do + @map[io] = stream + @nio.register(io, :r) + end + @nio.wakeup + end + + def detach(io, stream) + @todo << lambda do + @nio.deregister(io) + @map.delete io + end + @nio.wakeup + end + + def stop + @stopping = true + @nio.wakeup + end + + def run + loop do + if @stopping + @nio.close + break + end + + until @todo.empty? + @todo.pop(true).call + end + + if monitors = @nio.select + monitors.each do |monitor| + io = monitor.io + stream = @map[io] + + begin + stream.receive io.read_nonblock(4096) + rescue IO::WaitReadable + next + rescue EOFError + stream.close + end + end + end + end + end + end + end +end diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb index 670d5690ae..5e89fb9b72 100644 --- a/actioncable/lib/action_cable/connection/web_socket.rb +++ b/actioncable/lib/action_cable/connection/web_socket.rb @@ -1,13 +1,11 @@ -require 'faye/websocket' +require 'websocket/driver' module ActionCable module Connection - # Decorate the Faye::WebSocket with helpers we need. + # Wrap the real socket to minimize the externally-presented API class WebSocket - delegate :rack_response, :close, :on, to: :websocket - - def initialize(env) - @websocket = Faye::WebSocket.websocket?(env) ? Faye::WebSocket.new(env) : nil + def initialize(env, event_target, stream_event_loop) + @websocket = ::WebSocket::Driver.websocket?(env) ? ClientSocket.new(env, event_target, stream_event_loop) : nil end def possible? @@ -15,11 +13,19 @@ module ActionCable end def alive? - websocket && websocket.ready_state == Faye::WebSocket::API::OPEN + websocket && websocket.alive? end def transmit(data) - websocket.send data + websocket.transmit data + end + + def close + websocket.close + end + + def rack_response + websocket.rack_response end protected diff --git a/actioncable/lib/action_cable/process/logging.rb b/actioncable/lib/action_cable/process/logging.rb deleted file mode 100644 index dce637b3ca..0000000000 --- a/actioncable/lib/action_cable/process/logging.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'action_cable/server' -require 'eventmachine' - -EM.error_handler do |e| - puts "Error raised inside the event loop: #{e.message}" - puts e.backtrace.join("\n") -end diff --git a/actioncable/lib/action_cable/server.rb b/actioncable/lib/action_cable/server.rb index a2a89d5f1e..bd6a3826a3 100644 --- a/actioncable/lib/action_cable/server.rb +++ b/actioncable/lib/action_cable/server.rb @@ -1,7 +1,3 @@ -require 'eventmachine' -EventMachine.epoll if EventMachine.epoll? -EventMachine.kqueue if EventMachine.kqueue? - module ActionCable module Server extend ActiveSupport::Autoload diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb index 3385a4c9f3..b00abd208c 100644 --- a/actioncable/lib/action_cable/server/base.rb +++ b/actioncable/lib/action_cable/server/base.rb @@ -32,6 +32,10 @@ module ActionCable @remote_connections ||= RemoteConnections.new(self) end + def stream_event_loop + @stream_event_loop ||= ActionCable::Connection::StreamEventLoop.new + end + # The thread worker pool for handling all the connection work on this server. Default size is set by config.worker_pool_size. def worker_pool @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) diff --git a/actioncable/lib/action_cable/server/connections.rb b/actioncable/lib/action_cable/server/connections.rb index 47dcea8c20..8671dd5ebd 100644 --- a/actioncable/lib/action_cable/server/connections.rb +++ b/actioncable/lib/action_cable/server/connections.rb @@ -22,11 +22,9 @@ module ActionCable # then can't rely on being able to receive and send to it. So there's a 3 second heartbeat running on all connections. If the beat fails, we automatically # disconnect. def setup_heartbeat_timer - EM.next_tick do - @heartbeat_timer ||= EventMachine.add_periodic_timer(BEAT_INTERVAL) do - EM.next_tick { connections.map(&:beat) } - end - end + @heartbeat_timer ||= Concurrent::TimerTask.new(execution_interval: BEAT_INTERVAL) do + Concurrent.global_io_executor.post { connections.map(&:beat) } + end.tap(&:execute) end def open_connections_statistics diff --git a/actioncable/lib/action_cable/subscription_adapter/async.rb b/actioncable/lib/action_cable/subscription_adapter/async.rb index 85d4892e4c..c88b03947a 100644 --- a/actioncable/lib/action_cable/subscription_adapter/async.rb +++ b/actioncable/lib/action_cable/subscription_adapter/async.rb @@ -10,11 +10,11 @@ module ActionCable class AsyncSubscriberMap < SubscriberMap def add_subscriber(*) - ::EM.next_tick { super } + Concurrent.global_io_executor.post { super } end def invoke_callback(*) - ::EM.next_tick { super } + Concurrent.global_io_executor.post { super } end end end diff --git a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb index 78f8aeb599..3ce1bbed68 100644 --- a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb +++ b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb @@ -63,7 +63,7 @@ module ActionCable case action when :listen pg_conn.exec("LISTEN #{pg_conn.escape_identifier channel}") - ::EM.next_tick(&callback) if callback + Concurrent.global_io_executor << callback if callback when :unlisten pg_conn.exec("UNLISTEN #{pg_conn.escape_identifier channel}") when :shutdown @@ -93,7 +93,7 @@ module ActionCable end def invoke_callback(*) - ::EM.next_tick { super } + Concurrent.global_io_executor.post { super } end end end diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index 3b86354621..a035e3988d 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -1,11 +1,18 @@ +require 'thread' + gem 'em-hiredis', '~> 0.3.0' gem 'redis', '~> 3.0' require 'em-hiredis' require 'redis' +EventMachine.epoll if EventMachine.epoll? +EventMachine.kqueue if EventMachine.kqueue? + module ActionCable module SubscriptionAdapter class Redis < Base # :nodoc: + @@mutex = Mutex.new + def broadcast(channel, payload) redis_connection_for_broadcasts.publish(channel, payload) end @@ -27,6 +34,7 @@ module ActionCable private def redis_connection_for_subscriptions + ensure_reactor_running @redis_connection_for_subscriptions ||= EM::Hiredis.connect(@server.config.cable[:url]).tap do |redis| redis.on(:reconnect_failed) do @logger.info "[ActionCable] Redis reconnect failed." @@ -37,6 +45,14 @@ module ActionCable def redis_connection_for_broadcasts @redis_connection_for_broadcasts ||= ::Redis.new(@server.config.cable) end + + def ensure_reactor_running + return if EventMachine.reactor_running? + @@mutex.synchronize do + Thread.new { EventMachine.run } unless EventMachine.reactor_running? + Thread.pass until EventMachine.reactor_running? + end + end end end end diff --git a/actioncable/test/channel/periodic_timers_test.rb b/actioncable/test/channel/periodic_timers_test.rb index 1590a12f09..64f0247cd6 100644 --- a/actioncable/test/channel/periodic_timers_test.rb +++ b/actioncable/test/channel/periodic_timers_test.rb @@ -31,7 +31,7 @@ class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase end test "timer start and stop" do - EventMachine::PeriodicTimer.expects(:new).times(2).returns(true) + Concurrent::TimerTask.expects(:new).times(2).returns(true) channel = ChatChannel.new @connection, "{id: 1}", { id: 1 } channel.expects(:stop_periodic_timers).once diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb index 3fa2b291b7..947efd96d4 100644 --- a/actioncable/test/channel/stream_test.rb +++ b/actioncable/test/channel/stream_test.rb @@ -31,9 +31,7 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase test "stream_for" do run_in_eventmachine do connection = TestConnection.new - EM.next_tick do - connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) } - end + connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) } channel = ChatChannel.new connection, "" channel.stream_for Room.new(1) @@ -41,39 +39,35 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase end test "stream_from subscription confirmation" do - EM.run do + run_in_eventmachine do connection = TestConnection.new ChatChannel.new connection, "{id: 1}", { id: 1 } assert_nil connection.last_transmission - EM::Timer.new(0.1) do - expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription" - connection.transmit(expected) + wait_for_async - assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s" + expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription" + connection.transmit(expected) - EM.run_deferred_callbacks - EM.stop - end + assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s" end end test "subscription confirmation should only be sent out once" do - EM.run do + run_in_eventmachine do connection = TestConnection.new channel = ChatChannel.new connection, "test_channel" channel.send_confirmation channel.send_confirmation - EM.run_deferred_callbacks + wait_for_async expected = ActiveSupport::JSON.encode "identifier" => "test_channel", "type" => "confirm_subscription" assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation" assert_equal 1, connection.transmissions.size - EM.stop end end diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb index 182562db82..e2b017a9a1 100644 --- a/actioncable/test/connection/base_test.rb +++ b/actioncable/test/connection/base_test.rb @@ -37,6 +37,8 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase connection.process assert connection.websocket.possible? + + wait_for_async assert connection.websocket.alive? end end @@ -53,16 +55,15 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase test "on connection open" do run_in_eventmachine do connection = open_connection - connection.process connection.websocket.expects(:transmit).with(regexp_matches(/\_ping/)) connection.message_buffer.expects(:process!) - # Allow EM to run on_open callback - EM.next_tick do - assert_equal [ connection ], @server.connections - assert connection.connected - end + connection.process + wait_for_async + + assert_equal [ connection ], @server.connections + assert connection.connected end end @@ -72,12 +73,12 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase connection.process # Setup the connection - EventMachine.stubs(:add_periodic_timer).returns(true) - connection.send :on_open + Concurrent::TimerTask.stubs(:new).returns(true) + connection.send :handle_open assert connection.connected connection.subscriptions.expects(:unsubscribe_from_all) - connection.send :on_close + connection.send :handle_close assert ! connection.connected assert_equal [], @server.connections diff --git a/actioncable/test/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb index a110dfdee0..1019ad541e 100644 --- a/actioncable/test/connection/identifier_test.rb +++ b/actioncable/test/connection/identifier_test.rb @@ -68,10 +68,10 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase @connection = Connection.new(server, env) @connection.process - @connection.send :on_open + @connection.send :handle_open end def close_connection - @connection.send :on_close + @connection.send :handle_close end end diff --git a/actioncable/test/connection/multiple_identifiers_test.rb b/actioncable/test/connection/multiple_identifiers_test.rb index 55a9f96cb3..e9bb4e6d7f 100644 --- a/actioncable/test/connection/multiple_identifiers_test.rb +++ b/actioncable/test/connection/multiple_identifiers_test.rb @@ -32,10 +32,10 @@ class ActionCable::Connection::MultipleIdentifiersTest < ActionCable::TestCase @connection = Connection.new(server, env) @connection.process - @connection.send :on_open + @connection.send :handle_open end def close_connection - @connection.send :on_close + @connection.send :handle_close end end diff --git a/actioncable/test/stubs/test_server.rb b/actioncable/test/stubs/test_server.rb index 6e6541a952..56d132b30a 100644 --- a/actioncable/test/stubs/test_server.rb +++ b/actioncable/test/stubs/test_server.rb @@ -14,6 +14,7 @@ class TestServer @config.subscription_adapter.new(self) end - def send_async + def stream_event_loop + @stream_event_loop ||= ActionCable::Connection::StreamEventLoop.new end end diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb index d4a13be889..361858784e 100644 --- a/actioncable/test/subscription_adapter/common.rb +++ b/actioncable/test/subscription_adapter/common.rb @@ -1,7 +1,6 @@ require 'test_helper' require 'concurrent' -require 'action_cable/process/logging' require 'active_support/core_ext/hash/indifferent_access' require 'pathname' @@ -24,8 +23,6 @@ module CommonSubscriptionAdapterTest # and now the "real" setup for our test: - spawn_eventmachine - server.config.cable = cable_config.with_indifferent_access adapter_klass = server.config.pubsub_adapter diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb index 6636ce078b..8ddbd4e764 100644 --- a/actioncable/test/test_helper.rb +++ b/actioncable/test/test_helper.rb @@ -13,28 +13,16 @@ require 'rack/mock' # Require all the stubs and models Dir[File.dirname(__FILE__) + '/stubs/*.rb'].each {|file| require file } -require 'faye/websocket' -class << Faye::WebSocket - remove_method :ensure_reactor_running - - # We don't want Faye to start the EM reactor in tests because it makes testing much harder. - # We want to be able to start and stop EM loop in tests to make things simpler. - def ensure_reactor_running - # no-op - end -end - class ActionCable::TestCase < ActiveSupport::TestCase - def run_in_eventmachine - EM.run do - yield - - EM.run_deferred_callbacks - EM.stop + def wait_for_async + e = Concurrent.global_io_executor + until e.completed_task_count == e.scheduled_task_count + sleep 0.1 end end - def spawn_eventmachine - Thread.new { EventMachine.run } unless EventMachine.reactor_running? + def run_in_eventmachine + yield + wait_for_async end end diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru.tt b/railties/lib/rails/generators/rails/app/templates/config.ru.tt index 70556fcc99..343c0833d7 100644 --- a/railties/lib/rails/generators/rails/app/templates/config.ru.tt +++ b/railties/lib/rails/generators/rails/app/templates/config.ru.tt @@ -3,9 +3,8 @@ require ::File.expand_path('../config/environment', __FILE__) <%- unless options[:skip_action_cable] -%> -# Action Cable uses EventMachine which requires that all classes are loaded in advance +# Action Cable requires that all classes are loaded in advance Rails.application.eager_load! -require 'action_cable/process/logging' <%- end -%> run Rails.application -- cgit v1.2.3 From a928aa3d3f1e6f8780acc22d69f4d5d1f5917926 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 25 Jan 2016 03:55:05 +1030 Subject: Fix arguments to on_close --- actioncable/lib/action_cable/connection/base.rb | 2 +- actioncable/lib/action_cable/connection/client_socket.rb | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index 0016d1a1a4..b5f898436a 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -129,7 +129,7 @@ module ActionCable # ignore end - def on_close # :nodoc: + def on_close(reason, code) # :nodoc: send_async :handle_close end diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb index 62dd753646..ef937d7c16 100644 --- a/actioncable/lib/action_cable/connection/client_socket.rb +++ b/actioncable/lib/action_cable/connection/client_socket.rb @@ -37,6 +37,7 @@ module ActionCable @url = ClientSocket.determine_url(@env) @driver = @driver_started = nil + @close_params = ['', 1006] @ready_state = CONNECTING @@ -142,10 +143,7 @@ module ActionCable return if @ready_state == CLOSED @ready_state = CLOSED - reason = @close_params ? @close_params[0] : '' - code = @close_params ? @close_params[1] : 1006 - - @event_target.on_close(code, reason) + @event_target.on_close(*@close_params) end end end -- cgit v1.2.3 From 16a6603956551703e3bbd06101c568a73bcdaa52 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 25 Jan 2016 03:53:27 +1030 Subject: Synchronize the lazy setters in Server They're all at risk of races on the first requests. --- actioncable/lib/action_cable/server/base.rb | 23 +++++++++++++++------- .../lib/action_cable/subscription_adapter/async.rb | 4 ++-- .../action_cable/subscription_adapter/inline.rb | 11 ++++++++++- .../subscription_adapter/postgresql.rb | 7 ++++++- .../lib/action_cable/subscription_adapter/redis.rb | 17 ++++++++++++---- 5 files changed, 47 insertions(+), 15 deletions(-) diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb index b00abd208c..fe48c112df 100644 --- a/actioncable/lib/action_cable/server/base.rb +++ b/actioncable/lib/action_cable/server/base.rb @@ -1,3 +1,5 @@ +require 'thread' + module ActionCable module Server # A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the rack process that starts the cable server, but @@ -13,7 +15,12 @@ module ActionCable def self.logger; config.logger; end delegate :logger, to: :config + attr_reader :mutex + def initialize + @mutex = Mutex.new + + @remote_connections = @stream_event_loop = @worker_pool = @channel_classes = @pubsub = nil end # Called by rack to setup the server. @@ -29,29 +36,31 @@ module ActionCable # Gateway to RemoteConnections. See that class for details. def remote_connections - @remote_connections ||= RemoteConnections.new(self) + @remote_connections || @mutex.synchronize { @remote_connections ||= RemoteConnections.new(self) } end def stream_event_loop - @stream_event_loop ||= ActionCable::Connection::StreamEventLoop.new + @stream_event_loop || @mutex.synchronize { @stream_event_loop ||= ActionCable::Connection::StreamEventLoop.new } end # The thread worker pool for handling all the connection work on this server. Default size is set by config.worker_pool_size. def worker_pool - @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) + @worker_pool || @mutex.synchronize { @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) } end # Requires and returns a hash of all the channel class constants keyed by name. def channel_classes - @channel_classes ||= begin - config.channel_paths.each { |channel_path| require channel_path } - config.channel_class_names.each_with_object({}) { |name, hash| hash[name] = name.constantize } + @channel_classes || @mutex.synchronize do + @channel_classes ||= begin + config.channel_paths.each { |channel_path| require channel_path } + config.channel_class_names.each_with_object({}) { |name, hash| hash[name] = name.constantize } + end end end # Adapter used for all streams/broadcasting. def pubsub - @pubsub ||= config.pubsub_adapter.new(self) + @pubsub || @mutex.synchronize { @pubsub ||= config.pubsub_adapter.new(self) } end # All the identifiers applied to the connection class associated with this server. diff --git a/actioncable/lib/action_cable/subscription_adapter/async.rb b/actioncable/lib/action_cable/subscription_adapter/async.rb index c88b03947a..cca6894289 100644 --- a/actioncable/lib/action_cable/subscription_adapter/async.rb +++ b/actioncable/lib/action_cable/subscription_adapter/async.rb @@ -4,8 +4,8 @@ module ActionCable module SubscriptionAdapter class Async < Inline # :nodoc: private - def subscriber_map - @subscriber_map ||= AsyncSubscriberMap.new + def new_subscriber_map + AsyncSubscriberMap.new end class AsyncSubscriberMap < SubscriberMap diff --git a/actioncable/lib/action_cable/subscription_adapter/inline.rb b/actioncable/lib/action_cable/subscription_adapter/inline.rb index 4a2a8d23a2..81357faead 100644 --- a/actioncable/lib/action_cable/subscription_adapter/inline.rb +++ b/actioncable/lib/action_cable/subscription_adapter/inline.rb @@ -1,6 +1,11 @@ module ActionCable module SubscriptionAdapter class Inline < Base # :nodoc: + def initialize(*) + super + @subscriber_map = nil + end + def broadcast(channel, payload) subscriber_map.broadcast(channel, payload) end @@ -19,7 +24,11 @@ module ActionCable private def subscriber_map - @subscriber_map ||= SubscriberMap.new + @subscriber_map || @server.mutex.synchronize { @subscriber_map ||= new_subscriber_map } + end + + def new_subscriber_map + SubscriberMap.new end end end diff --git a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb index 3ce1bbed68..abaeb92e54 100644 --- a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb +++ b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb @@ -5,6 +5,11 @@ require 'thread' module ActionCable module SubscriptionAdapter class PostgreSQL < Base # :nodoc: + def initialize(*) + super + @listener = nil + end + def broadcast(channel, payload) with_connection do |pg_conn| pg_conn.exec("NOTIFY #{pg_conn.escape_identifier(channel)}, '#{pg_conn.escape_string(payload)}'") @@ -37,7 +42,7 @@ module ActionCable private def listener - @listener ||= Listener.new(self) + @listener || @server.mutex.synchronize { @listener ||= Listener.new(self) } end class Listener < SubscriberMap diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index a035e3988d..560b79df16 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -13,6 +13,11 @@ module ActionCable class Redis < Base # :nodoc: @@mutex = Mutex.new + def initialize(*) + super + @redis_connection_for_broadcasts = @redis_connection_for_subscriptions = nil + end + def broadcast(channel, payload) redis_connection_for_broadcasts.publish(channel, payload) end @@ -35,15 +40,19 @@ module ActionCable private def redis_connection_for_subscriptions ensure_reactor_running - @redis_connection_for_subscriptions ||= EM::Hiredis.connect(@server.config.cable[:url]).tap do |redis| - redis.on(:reconnect_failed) do - @logger.info "[ActionCable] Redis reconnect failed." + @redis_connection_for_subscriptions || @server.mutex.synchronize do + @redis_connection_for_subscriptions ||= EM::Hiredis.connect(@server.config.cable[:url]).tap do |redis| + redis.on(:reconnect_failed) do + @logger.info "[ActionCable] Redis reconnect failed." + end end end end def redis_connection_for_broadcasts - @redis_connection_for_broadcasts ||= ::Redis.new(@server.config.cable) + @redis_connection_for_broadcasts || @server.mutex.synchronize do + @redis_connection_for_broadcasts ||= ::Redis.new(@server.config.cable) + end end def ensure_reactor_running -- cgit v1.2.3 From ce37de4a19447fc89d2d16f15ba9314fba30d47e Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 25 Jan 2016 03:55:53 +1030 Subject: Add a couple of tests that connect with a WS client --- Gemfile | 2 + Gemfile.lock | 4 + actioncable/test/client/echo_channel.rb | 13 ++ actioncable/test/client_test.rb | 238 ++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 actioncable/test/client/echo_channel.rb create mode 100644 actioncable/test/client_test.rb diff --git a/Gemfile b/Gemfile index 9dd841d535..f8df08ee43 100644 --- a/Gemfile +++ b/Gemfile @@ -66,6 +66,8 @@ group :cable do gem 'em-hiredis', require: false gem 'redis', require: false + + gem 'faye-websocket', require: false end # Add your own local bundler stuff. diff --git a/Gemfile.lock b/Gemfile.lock index ddf5cc88a9..d2d616b39d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -127,6 +127,9 @@ GEM erubis (2.7.0) eventmachine (1.0.9.1) execjs (2.6.0) + faye-websocket (0.10.2) + eventmachine (>= 0.12.0) + websocket-driver (>= 0.5.1) ffi (1.9.10) ffi (1.9.10-x64-mingw32) ffi (1.9.10-x86-mingw32) @@ -282,6 +285,7 @@ DEPENDENCIES delayed_job delayed_job_active_record em-hiredis + faye-websocket jquery-rails json kindlerb (= 0.1.1) diff --git a/actioncable/test/client/echo_channel.rb b/actioncable/test/client/echo_channel.rb new file mode 100644 index 0000000000..9a54080d4d --- /dev/null +++ b/actioncable/test/client/echo_channel.rb @@ -0,0 +1,13 @@ +class EchoChannel < ActionCable::Channel::Base + def subscribed + stream_from "global" + end + + def ding(data) + transmit(dong: data['message']) + end + + def bulk(data) + ActionCable.server.broadcast "global", wide: data['message'] + end +end diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb new file mode 100644 index 0000000000..b185654c71 --- /dev/null +++ b/actioncable/test/client_test.rb @@ -0,0 +1,238 @@ +require 'test_helper' +require 'concurrent' + +require 'active_support/core_ext/hash/indifferent_access' +require 'pathname' + +require 'faye/websocket' +require 'json' + +class ClientTest < ActionCable::TestCase + WAIT_WHEN_EXPECTING_EVENT = 3 + WAIT_WHEN_NOT_EXPECTING_EVENT = 0.2 + + def setup + # TODO: ActionCable requires a *lot* of setup at the moment... + ::Object.const_set(:ApplicationCable, Module.new) + ::ApplicationCable.const_set(:Connection, Class.new(ActionCable::Connection::Base)) + + ::Object.const_set(:Rails, Module.new) + ::Rails.singleton_class.send(:define_method, :root) { Pathname.new(__dir__) } + + ActionCable.instance_variable_set(:@server, nil) + server = ActionCable.server + server.config = ActionCable::Server::Configuration.new + inner_logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } + server.config.logger = ActionCable::Connection::TaggedLoggerProxy.new(inner_logger, tags: []) + + server.config.cable = { adapter: 'async' }.with_indifferent_access + + # and now the "real" setup for our test: + server.config.disable_request_forgery_protection = true + server.config.channel_load_paths = [File.expand_path('client', __dir__)] + + @reactor_mutex = Mutex.new + @reactor_users = 0 + @reactor_running = Concurrent::Event.new + + # faye-websocket is warning-rich + @previous_verbose, $VERBOSE = $VERBOSE, nil + end + + def teardown + $VERBOSE = @previous_verbose + + if @reactor_running.set? + EM.stop + end + + begin + ::Object.send(:remove_const, :ApplicationCable) + rescue NameError + end + begin + ::Object.send(:remove_const, :Rails) + rescue NameError + end + end + + def with_puma_server(rack_app = ActionCable.server, port = 3099) + server = ::Puma::Server.new(rack_app, ::Puma::Events.strings) + server.add_tcp_listener '127.0.0.1', port + server.min_threads = 1 + server.max_threads = 4 + + t = Thread.new { server.run.join } + yield port + + ensure + server.stop(true) + t.join + end + + def start_event_machine + @reactor_mutex.synchronize do + unless @reactor_running.set? + @reactor ||= Thread.new do + EM.next_tick do + @reactor_running.set + end + EM.run + end + @reactor_running.wait(WAIT_WHEN_EXPECTING_EVENT) + end + @reactor_users += 1 + end + end + + def stop_event_machine + @reactor_mutex.synchronize do + @reactor_users -= 1 + if @reactor_users < 1 + @reactor_running.reset + EM.stop + @reactor = nil + end + end + end + + class SyncClient + attr_reader :pings + + def initialize(em_controller, port) + em_controller.start_event_machine + + @ws = Faye::WebSocket::Client.new("ws://127.0.0.1:#{port}/") + @messages = Queue.new + @closed = Concurrent::Event.new + @has_messages = Concurrent::Event.new + @pings = 0 + + open = Concurrent::Event.new + error = nil + + @ws.on(:error) do |event| + if open.set? + @messages << RuntimeError.new(event.message) + else + error = event.message + open.set + end + end + + @ws.on(:open) do |event| + open.set + end + + @ws.on(:message) do |event| + hash = JSON.parse(event.data) + if hash['identifier'] == '_ping' + @pings += 1 + else + @messages << hash + @has_messages.set + end + end + + @ws.on(:close) do |event| + @ws = nil + em_controller.stop_event_machine + @closed.set + end + + open.wait(WAIT_WHEN_EXPECTING_EVENT) + raise error if error + end + + def read_message + @has_messages.wait(WAIT_WHEN_EXPECTING_EVENT) if @messages.empty? + @has_messages.reset if @messages.size < 2 + + msg = @messages.pop(true) + raise msg if msg.is_a?(Exception) + + msg + end + + def read_messages + list = [] + loop do + @has_messages.wait(WAIT_WHEN_NOT_EXPECTING_EVENT) + if @has_messages.set? + list << read_message + else + break + end + end + list + end + + def send_message(hash) + @ws.send(JSON.dump(hash)) + end + + def close + sleep WAIT_WHEN_NOT_EXPECTING_EVENT + + unless @messages.empty? + raise "#{@messages.size} messages unprocessed" + end + + @ws.close + @closed.wait(WAIT_WHEN_EXPECTING_EVENT) + end + end + + def faye_client(port) + SyncClient.new(self, port) + end + + def test_single_client + with_puma_server do |port| + c = faye_client(port) + c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') + assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') + assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "message"=>{"dong"=>"hello"}}, c.read_message) + c.close + end + end + + def test_interacting_clients + with_puma_server do |port| + clients = 20.times.map { faye_client(port) } + + barrier_1 = Concurrent::CyclicBarrier.new(clients.size) + barrier_2 = Concurrent::CyclicBarrier.new(clients.size) + + clients.map {|c| Concurrent::Future.execute { + c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') + assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') + assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) + barrier_1.wait WAIT_WHEN_EXPECTING_EVENT + c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'bulk', message: 'hello') + barrier_2.wait WAIT_WHEN_EXPECTING_EVENT + sleep 1 + assert_equal clients.size, c.read_messages.size + } }.each(&:wait!) + + clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!) + end + end + + def test_many_clients + with_puma_server do |port| + clients = 200.times.map { faye_client(port) } + + clients.map {|c| Concurrent::Future.execute { + c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') + assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') + assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) + } }.each(&:wait!) + + clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!) + end + end +end -- cgit v1.2.3 From 786ed1b3ad8eeddb911211b67031016730ed55c8 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Thu, 28 Jan 2016 18:46:14 +1030 Subject: Handle more IO errors (especially, ECONNRESET) Also, address the possibility of the listen thread dying and needing to be respawned. As a bonus, we now defer construction of the thread until we are first given something to monitor. --- .../action_cable/connection/stream_event_loop.rb | 68 +++++++++++++++------- actioncable/test/client/echo_channel.rb | 5 ++ actioncable/test/client_test.rb | 26 +++++++++ 3 files changed, 78 insertions(+), 21 deletions(-) diff --git a/actioncable/lib/action_cable/connection/stream_event_loop.rb b/actioncable/lib/action_cable/connection/stream_event_loop.rb index f773814973..e6335082d2 100644 --- a/actioncable/lib/action_cable/connection/stream_event_loop.rb +++ b/actioncable/lib/action_cable/connection/stream_event_loop.rb @@ -1,18 +1,17 @@ require 'nio' +require 'thread' module ActionCable module Connection class StreamEventLoop def initialize - @nio = NIO::Selector.new + @nio = @thread = nil @map = {} @stopping = false @todo = Queue.new - Thread.new do - Thread.current.abort_on_exception = true - run - end + @spawn_mutex = Mutex.new + spawn end def attach(io, stream) @@ -20,34 +19,53 @@ module ActionCable @map[io] = stream @nio.register(io, :r) end - @nio.wakeup + wakeup end def detach(io, stream) @todo << lambda do - @nio.deregister(io) + @nio.deregister io @map.delete io end - @nio.wakeup + wakeup end def stop @stopping = true - @nio.wakeup + wakeup if @nio end - def run - loop do - if @stopping - @nio.close - break - end + private + def spawn + return if @thread && @thread.status + + @spawn_mutex.synchronize do + return if @thread && @thread.status + + @nio ||= NIO::Selector.new + @thread = Thread.new { run } - until @todo.empty? - @todo.pop(true).call + return true end + end + + def wakeup + spawn || @nio.wakeup + end + + def run + loop do + if @stopping + @nio.close + break + end + + until @todo.empty? + @todo.pop(true).call + end + + next unless monitors = @nio.select - if monitors = @nio.select monitors.each do |monitor| io = monitor.io stream = @map[io] @@ -56,13 +74,21 @@ module ActionCable stream.receive io.read_nonblock(4096) rescue IO::WaitReadable next - rescue EOFError - stream.close + rescue + # We expect one of EOFError or Errno::ECONNRESET in + # normal operation (when the client goes away). But if + # anything else goes wrong, this is still the best way + # to handle it. + begin + stream.close + rescue + @nio.deregister io + @map.delete io + end end end end end - end end end end diff --git a/actioncable/test/client/echo_channel.rb b/actioncable/test/client/echo_channel.rb index 9a54080d4d..63e35f194a 100644 --- a/actioncable/test/client/echo_channel.rb +++ b/actioncable/test/client/echo_channel.rb @@ -7,6 +7,11 @@ class EchoChannel < ActionCable::Channel::Base transmit(dong: data['message']) end + def delay(data) + sleep 1 + transmit(dong: data['message']) + end + def bulk(data) ActionCable.server.broadcast "global", wide: data['message'] end diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index b185654c71..7617e93426 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -181,6 +181,15 @@ class ClientTest < ActionCable::TestCase @ws.close @closed.wait(WAIT_WHEN_EXPECTING_EVENT) end + + def close! + sock = BasicSocket.for_fd(@ws.instance_variable_get(:@stream).detach) + + # Force a TCP reset + sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack('ii')) + + sock.close + end end def faye_client(port) @@ -235,4 +244,21 @@ class ClientTest < ActionCable::TestCase clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!) end end + + def test_disappearing_client + with_puma_server do |port| + c = faye_client(port) + c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') + assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'delay', message: 'hello') + c.close! # disappear before write + + c = faye_client(port) + c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') + assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) + c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') + assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) + c.close! # disappear before read + end + end end -- cgit v1.2.3 From 0b94afb0757e02deccced2d85b8478b78f269e0b Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Thu, 28 Jan 2016 21:19:10 +1030 Subject: Be more patient while gathering the expected responses --- actioncable/test/client_test.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index 7617e93426..66fa79afd6 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -154,10 +154,10 @@ class ClientTest < ActionCable::TestCase msg end - def read_messages + def read_messages(expected_size = 0) list = [] loop do - @has_messages.wait(WAIT_WHEN_NOT_EXPECTING_EVENT) + @has_messages.wait(list.size < expected_size ? WAIT_WHEN_EXPECTING_EVENT : WAIT_WHEN_NOT_EXPECTING_EVENT) if @has_messages.set? list << read_message else @@ -222,8 +222,7 @@ class ClientTest < ActionCable::TestCase barrier_1.wait WAIT_WHEN_EXPECTING_EVENT c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'bulk', message: 'hello') barrier_2.wait WAIT_WHEN_EXPECTING_EVENT - sleep 1 - assert_equal clients.size, c.read_messages.size + assert_equal clients.size, c.read_messages(clients.size).size } }.each(&:wait!) clients.map {|c| Concurrent::Future.execute { c.close } }.each(&:wait!) -- cgit v1.2.3 From 3043601d7ab4a3b0e7eea1c6353ab96258f8a78c Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Thu, 28 Jan 2016 22:02:48 +1030 Subject: Reduce the client count, in hope of a more consistent test --- actioncable/test/client_test.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index 66fa79afd6..4629d858a1 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -40,12 +40,12 @@ class ClientTest < ActionCable::TestCase end def teardown - $VERBOSE = @previous_verbose - if @reactor_running.set? EM.stop end + $VERBOSE = @previous_verbose + begin ::Object.send(:remove_const, :ApplicationCable) rescue NameError @@ -209,7 +209,7 @@ class ClientTest < ActionCable::TestCase def test_interacting_clients with_puma_server do |port| - clients = 20.times.map { faye_client(port) } + clients = 10.times.map { faye_client(port) } barrier_1 = Concurrent::CyclicBarrier.new(clients.size) barrier_2 = Concurrent::CyclicBarrier.new(clients.size) -- cgit v1.2.3 From 4d01cd1545a00ed6f96d6cb658a590afd36e1871 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Thu, 28 Jan 2016 22:46:33 +1030 Subject: Keep the socket reference after close We may still try to send to it. --- actioncable/test/client_test.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index 4629d858a1..3d9690fb01 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -66,8 +66,8 @@ class ClientTest < ActionCable::TestCase yield port ensure - server.stop(true) - t.join + server.stop(true) if server + t.join if t end def start_event_machine @@ -135,7 +135,6 @@ class ClientTest < ActionCable::TestCase end @ws.on(:close) do |event| - @ws = nil em_controller.stop_event_machine @closed.set end -- cgit v1.2.3 From 1c893bc2f94fd2e9e2c0d253eac0faedcd0046b8 Mon Sep 17 00:00:00 2001 From: Kesha Antonov Date: Fri, 29 Jan 2016 21:56:51 +0300 Subject: remove require logging --- actioncable/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/actioncable/README.md b/actioncable/README.md index cad71ddf94..ac57532b62 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -397,8 +397,6 @@ application. The recommended basic setup is as follows: require ::File.expand_path('../../config/environment', __FILE__) Rails.application.eager_load! -require 'action_cable/process/logging' - run ActionCable.server ``` -- cgit v1.2.3 From da6342779b3228e1796ed4e3c385b52d60821b0f Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Sat, 30 Jan 2016 05:45:41 +1030 Subject: Intervene on change_table as well as create_table --- activerecord/lib/active_record/migration/compatibility.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 5d742b523b..45e35a4f71 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -30,6 +30,19 @@ module ActiveRecord end end + def change_table(table_name, options = {}) + if block_given? + super(table_name, options) do |t| + class << t + prepend TableDefinition + end + yield t + end + else + super + end + end + def add_reference(*, **options) options[:index] ||= false super -- cgit v1.2.3 From e6d0d4b1ae1970f3c4379f5bc2c4147a227221c3 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Sat, 30 Jan 2016 06:41:14 +1030 Subject: Don't bother stopping EM between tests It's not strictly necessary, and maybe this will help with the current test failure. --- actioncable/test/client_test.rb | 41 +++-------------------------------------- 1 file changed, 3 insertions(+), 38 deletions(-) diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index 3d9690fb01..fd03c2a24c 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -31,19 +31,13 @@ class ClientTest < ActionCable::TestCase server.config.disable_request_forgery_protection = true server.config.channel_load_paths = [File.expand_path('client', __dir__)] - @reactor_mutex = Mutex.new - @reactor_users = 0 - @reactor_running = Concurrent::Event.new + Thread.new { EventMachine.run } unless EventMachine.reactor_running? # faye-websocket is warning-rich @previous_verbose, $VERBOSE = $VERBOSE, nil end def teardown - if @reactor_running.set? - EM.stop - end - $VERBOSE = @previous_verbose begin @@ -70,38 +64,10 @@ class ClientTest < ActionCable::TestCase t.join if t end - def start_event_machine - @reactor_mutex.synchronize do - unless @reactor_running.set? - @reactor ||= Thread.new do - EM.next_tick do - @reactor_running.set - end - EM.run - end - @reactor_running.wait(WAIT_WHEN_EXPECTING_EVENT) - end - @reactor_users += 1 - end - end - - def stop_event_machine - @reactor_mutex.synchronize do - @reactor_users -= 1 - if @reactor_users < 1 - @reactor_running.reset - EM.stop - @reactor = nil - end - end - end - class SyncClient attr_reader :pings - def initialize(em_controller, port) - em_controller.start_event_machine - + def initialize(port) @ws = Faye::WebSocket::Client.new("ws://127.0.0.1:#{port}/") @messages = Queue.new @closed = Concurrent::Event.new @@ -135,7 +101,6 @@ class ClientTest < ActionCable::TestCase end @ws.on(:close) do |event| - em_controller.stop_event_machine @closed.set end @@ -192,7 +157,7 @@ class ClientTest < ActionCable::TestCase end def faye_client(port) - SyncClient.new(self, port) + SyncClient.new(port) end def test_single_client -- cgit v1.2.3 From 349f187f58491f2c2ef929ec0b46d78d4e888e85 Mon Sep 17 00:00:00 2001 From: Tawan Sierek Date: Fri, 29 Jan 2016 21:01:33 +0100 Subject: Add additional documentation on Headers#[] [ci skip] Issue #16519 covers confusion potentially caused by how HTTP headers, that contain underscores in their names, are retrieved through `ActionDispatch::Http::Headers#[]`. This confusion has its origin in how a CGI maps HTTP header names to variable names. Even though underscores in header names are rarely encountered, they are valid according to RFC822 [1]. Nonetheless CGI like variable names, as requested by the Rack specfication, will only contain underscores and therefore the original header name cannot be recovered after the Rack server passed on the environemnt hash. Please, see also the disscussion on StackOverflow [2], which also links to an explaination in the nginx documentation [3]. [1] http://www.ietf.org/rfc/rfc822.txt [2] http://stackoverflow.com/questions/22856136/why-underscores-are-forbidden-in-http-header-names [3] https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#missing-disappearing-http-headers --- actionpack/lib/action_dispatch/http/headers.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 12f81dc1a5..8e899174c6 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -2,9 +2,23 @@ module ActionDispatch module Http # Provides access to the request's HTTP headers from the environment. # - # env = { "CONTENT_TYPE" => "text/plain" } + # 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" + # + # Also note that when headers are mapped to CGI-like variables by the Rack + # server, both dashes and underscores are converted to underscores. This + # ambiguity cannot be resolved at this stage anymore. Both underscores and + # dashes have to be interpreted as if they were originally sent as dashes. + # + # # GET / HTTP/1.1 + # # ... + # # User-Agent: curl/7.43.0 + # # X_Custom_Header: token + # + # headers["X_Custom_Header"] # => nil + # headers["X-Custom-Header"] # => "token" class Headers CGI_VARIABLES = Set.new(%W[ AUTH_TYPE -- cgit v1.2.3 From 0ae187961c75c44ea418f44ce0c09f78cdf520ff Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Sat, 30 Jan 2016 07:51:41 +1030 Subject: Use a gentler disconnect The detach used by close! seems to be making EM very sad on Travis. --- actioncable/test/client_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index fd03c2a24c..d7eecfa322 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -214,14 +214,14 @@ class ClientTest < ActionCable::TestCase c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'delay', message: 'hello') - c.close! # disappear before write + c.close # disappear before write c = faye_client(port) c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message) c.send_message command: 'message', identifier: JSON.dump(channel: 'EchoChannel'), data: JSON.dump(action: 'ding', message: 'hello') assert_equal({"identifier"=>'{"channel":"EchoChannel"}', "message"=>{"dong"=>"hello"}}, c.read_message) - c.close! # disappear before read + c.close # disappear before read end end end -- cgit v1.2.3 From 5563c329eee5febc22a3330e16fe8a6899d42fe2 Mon Sep 17 00:00:00 2001 From: schneems Date: Thu, 14 Jan 2016 13:05:41 -0600 Subject: Add Default Puma Config When the `puma` command is run without any configuration options it will detect presence of a `config/puma.rb` file and use that. Currently there is discrepancy between `puma` command and `rails server` but Evan said it would be reasonable to add in reading in config from the default location. I am working on that right now as a feature in puma/puma. Why do we need this? By default Puma uses 16 threads, and by default ActiveRecord only has 5 threads. Due to the architecture of AR it is guaranteed that if you're running with fewer DB connections than your server has threads you will hit `ActiveRecord::ConnectionTimeoutError ` eventually if your app gets modest amounts of traffic. Since we are providing a default webserver, we should provide reasonable configuration for that webserver. This PR does a few things, first it sets the default Puma thread count to 5 to mach ActiveRecord's default. It sets the default environment to `"development"` and the default port to 300 so that booting the server with `$ puma` will give you the same default port as `rails server`. It is worth mentioning that by reading in from `PORT` environment variable this config can work with containerized deployments, such as on Heroku. We are not using worker processes by default, that way JRuby and windows devs can use this configuration without modification. I went ahead and included a default `on_worker_boot`. It won't be used unless a worker count is specified, that means this config will not use it. Even though it's not being used now It will make someone who wants to try modifying their config to run extra workers easier. cc/ @pixeltrix --- railties/lib/rails/generators/app_base.rb | 10 +++++ .../rails/generators/rails/app/app_generator.rb | 1 + .../rails/generators/rails/app/templates/Gemfile | 3 -- .../generators/rails/app/templates/config/puma.rb | 44 ++++++++++++++++++++++ railties/test/generators/app_generator_test.rb | 9 +++++ 5 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 railties/lib/rails/generators/rails/app/templates/config/puma.rb diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index c629459d95..420e9673a1 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -51,6 +51,9 @@ module Rails class_option :skip_active_record, type: :boolean, aliases: '-O', default: false, desc: 'Skip Active Record files' + class_option :skip_puma, type: :boolean, aliases: '-P', default: false, + desc: 'Skip Puma related files' + class_option :skip_action_cable, type: :boolean, aliases: '-C', default: false, desc: 'Skip Action Cable files' @@ -113,6 +116,7 @@ module Rails def gemfile_entries [rails_gemfile_entry, database_gemfile_entry, + webserver_gemfile_entry, assets_gemfile_entry, javascript_gemfile_entry, jbuilder_gemfile_entry, @@ -171,6 +175,12 @@ module Rails "Use #{options[:database]} as the database for Active Record" end + def webserver_gemfile_entry + return [] if options[:skip_puma] + comment = 'Use Puma as the app server' + GemfileEntry.new('puma', nil, comment) + end + def include_all_railties? options.values_at(:skip_active_record, :skip_action_mailer, :skip_test, :skip_sprockets, :skip_action_cable).none? end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 3d689ff37e..63bb02cf9a 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -79,6 +79,7 @@ module Rails template "environment.rb" template "secrets.yml" template "cable.yml" unless options[:skip_action_cable] + template "puma.rb" unless options[:skip_puma] directory "environments" directory "initializers" diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index da5af4eefc..3825dc4e38 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -12,9 +12,6 @@ source 'https://rubygems.org' <% end -%> <% end -%> -# Use Puma as the app server -gem 'puma' - # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb b/railties/lib/rails/generators/rails/app/templates/config/puma.rb new file mode 100644 index 0000000000..1bf274bc66 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb @@ -0,0 +1,44 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum, this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests, default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. If you use this option +# you need to make sure to reconnect any threads in the `on_worker_boot` +# block. +# +# preload_app! + +# The code in the `on_worker_boot` will be called if you are using +# clustered mode by specifying a number of `workers`. After each worker +# process is booted this block will be run, if you are using `preload_app!` +# option you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, Ruby +# cannot share connections between processes. +# +# on_worker_boot do +# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) +# end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 5b62b500e5..39224b70ca 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -27,6 +27,7 @@ DEFAULT_APP_FILES = %w( config/initializers config/locales config/cable.yml + config/puma.rb db lib lib/tasks @@ -337,6 +338,14 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_generator_if_skip_puma_is_given + run_generator [destination_root, "--skip-puma"] + assert_no_file "config/puma.rb" + assert_file "Gemfile" do |content| + assert_no_match(/puma/, content) + end + end + def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" -- cgit v1.2.3 From 647a04cdbcb4e3abccb9487cdbc7366b4d923ea4 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Sat, 30 Jan 2016 13:40:12 +0900 Subject: remove unused variable from render test This removes the following warning. ``` rails/actionpack/test/controller/render_test.rb:278: warning: assigned but unused variable - response ``` --- actionpack/test/controller/render_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 9430ab8db8..c814d4ea54 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -275,7 +275,7 @@ class ExpiresInRenderTest < ActionController::TestCase file.write "secrets!" file.flush assert_raises ActionView::MissingTemplate do - response = get :dynamic_render, params: { id: file.path } + get :dynamic_render, params: { id: file.path } end ensure file.close -- cgit v1.2.3 From a027a750d0a3790965de9faa78c3eeb7f79f517e Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Sat, 30 Jan 2016 13:50:22 +0900 Subject: remove unused require `with_indifferent_access` had been used in `assigns` method, but has been removed in ca83436. --- actionpack/lib/action_dispatch/testing/test_process.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index eca0439909..1ecd7d14a7 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -1,6 +1,5 @@ require 'action_dispatch/middleware/cookies' require 'action_dispatch/middleware/flash' -require 'active_support/core_ext/hash/indifferent_access' module ActionDispatch module TestProcess -- cgit v1.2.3 From eba55447f092dfa99dc813dc7ea21cee9c26b85c Mon Sep 17 00:00:00 2001 From: yui-knk Date: Fri, 29 Jan 2016 14:10:33 +0900 Subject: [ci skip] Update internal documents about ActiveRecord's Reflection ActiveRecord's Reflection was refactored by f8d2899d12d59360f29c5eb6a1b1a8fe4ec82ca0 . Top of ancestors chain was changed to `AbstractReflection` from `MacroReflection`, and new Reflections were added. --- activerecord/lib/active_record/reflection.rb | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 37e18626b5..666039a9f3 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -124,9 +124,20 @@ module ActiveRecord end end - # Holds all the methods that are shared between MacroReflection, AssociationReflection - # and ThroughReflection - class AbstractReflection # :nodoc: + # Holds all the methods that are shared between MacroReflection and ThroughReflection. + # + # AbstractReflection + # MacroReflection + # AggregateReflection + # AssociationReflection + # HasManyReflection + # HasOneReflection + # BelongsToReflection + # HasAndBelongsToManyReflection + # ThroughReflection + # PolymorphicReflection + # RuntimeReflection + class AbstractReflection # :nodoc: def table_name klass.table_name end @@ -232,14 +243,6 @@ module ActiveRecord # Base class for AggregateReflection and AssociationReflection. Objects of # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods. - # - # MacroReflection - # AggregateReflection - # AssociationReflection - # HasManyReflection - # HasOneReflection - # BelongsToReflection - # ThroughReflection class MacroReflection < AbstractReflection # Returns the name of the macro. # -- cgit v1.2.3 From b0ea06b3dbf6200b4728d9cf1a3a47251c96b78d Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Sat, 30 Jan 2016 12:02:21 +0530 Subject: [ci skip] Fix one more typo - Followup of https://github.com/rails/docrails/commit/10bc49710b7205a6172c3e072b3c77114fefd952 --- activesupport/lib/active_support/deprecation/proxy_wrappers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 31e48e451a..0cb2d4d22e 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -80,7 +80,7 @@ module ActiveSupport # example.old_request.to_s # # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of # @request.to_s - # (Bactrace information…) + # (Backtrace information…) # "special_request" # # example.request.to_s -- cgit v1.2.3 From 444c4d05fea817fcad991e79fa128b640e2e4ff1 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 30 Jan 2016 16:47:51 +0900 Subject: Warn if `AR.primary_key` is called for a table who has composite primary key If `AR.primary_key` is called for a table who has composite primary key, the method returns `nil`. This behavior sometimes generates invalid SQL. The first time developers notice to invalid SQL is when they execute SQL. This commit enables developers to know they are doing something dangerous as soon as possible. --- .../connection_adapters/abstract/schema_statements.rb | 6 ++++++ activerecord/test/cases/primary_keys_test.rb | 7 +++++++ 2 files changed, 13 insertions(+) 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 c7aff63228..3b4ceb915e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -122,6 +122,12 @@ module ActiveRecord # Returns just a table's primary key def primary_key(table_name) pks = primary_keys(table_name) + warn <<-WARNING.strip_heredoc if pks.count > 1 + WARNING: Rails does not support composite primary key. + + #{table_name} has composite primary key. Composite primary key is ignored. + WARNING + pks.first if pks.one? end diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 7e18313c00..360efd0b2b 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -262,6 +262,13 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase assert_equal ["region", "code"], @connection.primary_keys("barcodes") end + def test_primary_key_issues_warning + warning = capture(:stderr) do + @connection.primary_key("barcodes") + end + assert_match(/WARNING: Rails does not support composite primary key\./, warning) + end + def test_collectly_dump_composite_primary_key schema = dump_table_schema "barcodes" assert_match %r{create_table "barcodes", primary_key: \["region", "code"\]}, schema -- cgit v1.2.3 From 92cca41bbe6d1ae4e1c1e63eb08871a57fa5e36e Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Sat, 30 Jan 2016 16:52:09 +0900 Subject: remove backward compatibility code for Minitest 4 The master branch is required Ruby 2.2.2+, for the Ruby 2.2 is bundled Minitest 5.4.3, I think backward compatibility code for Minitest 4 is unnecessary. --- guides/bug_report_templates/generic_master.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb index 0a8048cc48..fcc90fa503 100644 --- a/guides/bug_report_templates/generic_master.rb +++ b/guides/bug_report_templates/generic_master.rb @@ -19,9 +19,6 @@ require 'active_support' require 'active_support/core_ext/object/blank' require 'minitest/autorun' -# Ensure backward compatibility with Minitest 4 -Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) - class BugTest < Minitest::Test def test_stuff assert "zomg".present? -- cgit v1.2.3 From 1a071bae045ca38dc882c5906178b1c59af0cecd Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 30 Jan 2016 17:09:03 +0900 Subject: Refactor `OID::Money.precision` --- .../lib/active_record/connection_adapters/postgresql/oid/money.rb | 6 ++++-- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 8 +------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb index 2163674019..78039b719c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -3,12 +3,14 @@ module ActiveRecord module PostgreSQL module OID # :nodoc: class Money < Type::Decimal # :nodoc: - class_attribute :precision - def type :money end + def precision + 19 + end + def scale 2 end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 2de6fbfaf0..725173d995 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -213,7 +213,7 @@ module ActiveRecord @statements = StatementPool.new @connection, self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }) - if postgresql_version < 80200 + if postgresql_version < 80300 raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!" end @@ -645,12 +645,6 @@ module ActiveRecord # connected server's characteristics. def connect @connection = PGconn.connect(@connection_parameters) - - # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of - # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision - # should know about this but can't detect it there, so deal with it here. - OID::Money.precision = (postgresql_version >= 80300) ? 19 : 10 - configure_connection rescue ::PG::Error => error if error.message.include?("does not exist") -- cgit v1.2.3 From 8b9d217d0846752372791b7b8f34a0aa2e2ab812 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 30 Jan 2016 17:47:01 +0900 Subject: Explicitly define `columns` method as an interface `ActiveRecord::ConnectionAdapters::SchemaStatements#columns` is defined here as an interface method here. So changes to raise `NotImplementedError` same as `tables`, `views` ...etc. --- .../active_record/connection_adapters/abstract/schema_statements.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 c7aff63228..efa427fa88 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -92,7 +92,9 @@ module ActiveRecord # Returns an array of Column objects for the table specified by +table_name+. # See the concrete implementation for details on the expected parameter values. - def columns(table_name) end + def columns(table_name) + raise NotImplementedError, "#columns is not implemented" + end # Checks to see if a column exists in a given table. # -- cgit v1.2.3 From 617a693458437d11cea28344598cf264a9c8c47b Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 30 Jan 2016 20:26:19 +0900 Subject: Refactor `column_exists?` in `SchemaStatements` --- .../connection_adapters/abstract/schema_statements.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) 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 c7aff63228..1591de166e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -110,13 +110,14 @@ module ActiveRecord # def column_exists?(table_name, column_name, type = nil, options = {}) column_name = column_name.to_s - columns(table_name).any?{ |c| c.name == column_name && - (!type || c.type == type) && - (!options.key?(:limit) || c.limit == options[:limit]) && - (!options.key?(:precision) || c.precision == options[:precision]) && - (!options.key?(:scale) || c.scale == options[:scale]) && - (!options.key?(:default) || c.default == options[:default]) && - (!options.key?(:null) || c.null == options[:null]) } + checks = [] + checks << lambda { |c| c.name == column_name } + checks << lambda { |c| c.type == type } if type + [:limit, :precision, :scale, :default, :null].each do |attr| + checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr) + end + + columns(table_name).any? { |c| checks.all? { |check| check[c] } } end # Returns just a table's primary key -- cgit v1.2.3 From 4e551d05c9ddfc13812d8c8d87f277d1aac371c4 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 30 Jan 2016 20:52:33 +0900 Subject: Make `SchemaMigration.drop_table` to one SQL `SchemaMigration.drop_table` is only used in tests. Simply we can use `drop_table if_exists: true`. --- activerecord/lib/active_record/schema_migration.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index ee4c71f304..8f0ab2b55b 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -37,10 +37,7 @@ module ActiveRecord end def drop_table - if table_exists? - connection.remove_index table_name, name: index_name - connection.drop_table(table_name) - end + connection.drop_table table_name, if_exists: true end def normalize_migration_number(number) -- cgit v1.2.3 From 284f8d4970e50c1239236c22c9eb45ca7dbfb60f Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Sat, 30 Jan 2016 23:07:23 +0900 Subject: remove unnecessary gsub for `action_cable_meta_tag` If the specified `skip_action_cable` option, so as not to output the `action_cable_meta_tag` in template, gsub is unnecessary. ref: https://github.com/rails/rails/blob/master/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt#L6..L8 --- railties/lib/rails/generators/rails/app/app_generator.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index a4758857f2..775750d86b 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -318,7 +318,6 @@ module Rails remove_file 'config/cable.yml' remove_file 'app/assets/javascripts/cable.coffee' remove_dir 'app/channels' - gsub_file 'app/views/layouts/application.html.erb', /action_cable_meta_tag/, '' unless options[:api] end end -- cgit v1.2.3 From c4d85dfbc71043e2a746acd310e32f4f04db801a Mon Sep 17 00:00:00 2001 From: eileencodes Date: Tue, 26 Jan 2016 19:15:09 -0500 Subject: Handle response_body= when body is nil There are some cases when the `body` in `response_body=` can be set to nil. One of those cases is in `actionpack-action_caching` which I found while upgrading it for Rails 5. It's not possible to run `body.each` on a `nil` body so we have to return after we run `response.reset_body!`. --- actionpack/lib/action_controller/metal.rb | 1 + actionpack/test/controller/new_base/bare_metal_test.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index f6a93a8940..1641d01c30 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -174,6 +174,7 @@ module ActionController def response_body=(body) body = [body] unless body.nil? || body.respond_to?(:each) response.reset_body! + return unless body body.each { |part| next if part.empty? response.write part diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb index c226fa57ee..ee3c498b1c 100644 --- a/actionpack/test/controller/new_base/bare_metal_test.rb +++ b/actionpack/test/controller/new_base/bare_metal_test.rb @@ -40,6 +40,22 @@ module BareMetalTest end end + class BareEmptyController < ActionController::Metal + def index + self.response_body = nil + end + end + + class BareEmptyTest < ActiveSupport::TestCase + test "response body is nil" do + controller = BareEmptyController.new + controller.set_request!(ActionDispatch::Request.empty) + controller.set_response!(BareController.make_response!(controller.request)) + controller.index + assert_equal nil, controller.response_body + end + end + class HeadController < ActionController::Metal include ActionController::Head -- cgit v1.2.3 From e05d828b1f89f4a78e5eca67419275fb63cbeee2 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sun, 31 Jan 2016 00:01:26 +0900 Subject: There is no need to define test if a connection does not support primary_key --- activerecord/test/cases/primary_keys_test.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 7e18313c00..8ce2b813bf 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -135,22 +135,20 @@ class PrimaryKeysTest < ActiveRecord::TestCase end end - def test_primary_key_returns_value_if_it_exists - klass = Class.new(ActiveRecord::Base) do - self.table_name = 'developers' - end + if ActiveRecord::Base.connection.supports_primary_key? + def test_primary_key_returns_value_if_it_exists + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'developers' + end - if ActiveRecord::Base.connection.supports_primary_key? assert_equal 'id', klass.primary_key end - end - def test_primary_key_returns_nil_if_it_does_not_exist - klass = Class.new(ActiveRecord::Base) do - self.table_name = 'developers_projects' - end + def test_primary_key_returns_nil_if_it_does_not_exist + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'developers_projects' + end - if ActiveRecord::Base.connection.supports_primary_key? assert_nil klass.primary_key end end -- cgit v1.2.3 From a3eba8f2b21b90f7c812434ac485087dbf0ca349 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 31 Jan 2016 00:29:40 +0900 Subject: Remove unused `LOST_CONNECTION_ERROR_MESSAGES` `LOST_CONNECTION_ERROR_MESSAGES` was added by f384582. But currently unused from anywhere. --- .../lib/active_record/connection_adapters/abstract_mysql_adapter.rb | 6 ------ 1 file changed, 6 deletions(-) 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 5a9020ead5..db09170d33 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -31,12 +31,6 @@ module ActiveRecord class_attribute :emulate_booleans self.emulate_booleans = true - LOST_CONNECTION_ERROR_MESSAGES = [ - "Server shutdown in progress", - "Broken pipe", - "Lost connection to MySQL server during query", - "MySQL server has gone away" ] - QUOTED_TRUE, QUOTED_FALSE = '1', '0' NATIVE_DATABASE_TYPES = { -- cgit v1.2.3 From d0054b490848fa80f6c2036bd4e4b45293d16394 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 31 Jan 2016 01:07:02 +0900 Subject: Fix `bigint?` for Enum columns in MySQL Follow up to #22896. --- activerecord/lib/active_record/connection_adapters/column.rb | 2 +- activerecord/test/cases/adapters/mysql2/enum_test.rb | 5 +++++ activerecord/test/schema/mysql2_specific_schema.rb | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 81de7c03fb..10f908538f 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -30,7 +30,7 @@ module ActiveRecord end def bigint? - /bigint/ === sql_type + /\Abigint\b/ === sql_type end # Returns the human name of the column name. diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb index bb89e893e0..35dbc76d1b 100644 --- a/activerecord/test/cases/adapters/mysql2/enum_test.rb +++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb @@ -18,4 +18,9 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase column = EnumTest.columns_hash['enum_column'] assert_not column.unsigned? end + + def test_should_not_be_bigint + column = EnumTest.columns_hash['enum_column'] + assert_not column.bigint? + end end diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index 701e6f45b3..16ff6de24d 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -62,7 +62,7 @@ SQL ActiveRecord::Base.connection.execute <<-SQL CREATE TABLE enum_tests ( - enum_column ENUM('text','blob','tiny','medium','long','unsigned') + enum_column ENUM('text','blob','tiny','medium','long','unsigned','bigint') ) SQL end -- cgit v1.2.3 From 14037b8d8e96520a19afdac826fe660ab2e17239 Mon Sep 17 00:00:00 2001 From: Mikhail Dieterle Date: Sat, 30 Jan 2016 19:32:23 +0300 Subject: [ci skip] fix typo --- guides/source/association_basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index accce5a904..3386791cdb 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -726,7 +726,7 @@ class Author < ApplicationRecord has_many :books, inverse_of: :author end -class book < ApplicationRecord +class Book < ApplicationRecord belongs_to :author, inverse_of: :books end ``` -- cgit v1.2.3 From bb6f36ee391cdb8eb29e8678e6d27776d85bd2f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 30 Jan 2016 12:08:21 -0500 Subject: Allow failures for Action Cable tests --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ae38617b99..869507a9fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,7 @@ rvm: matrix: allow_failures: - rvm: ruby-head + - env: "GEM=ac" fast_finish: true notifications: email: false -- cgit v1.2.3 From a398cd0bcbd39998f1b0313e6fe3f04bf491b3db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 30 Jan 2016 12:42:39 -0500 Subject: Revert "Merge pull request #23346 from kamipo/refactor_oid_money_precision" This reverts commit ff835f90800a3e4122d64606cb328908c2e0e071, reversing changes made to c4d85dfbc71043e2a746acd310e32f4f04db801a. Reason: This broke the tests. We will add back after investigated. --- .../lib/active_record/connection_adapters/postgresql/oid/money.rb | 6 ++---- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 8 +++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb index 78039b719c..2163674019 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -3,14 +3,12 @@ module ActiveRecord module PostgreSQL module OID # :nodoc: class Money < Type::Decimal # :nodoc: + class_attribute :precision + def type :money end - def precision - 19 - end - def scale 2 end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 725173d995..2de6fbfaf0 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -213,7 +213,7 @@ module ActiveRecord @statements = StatementPool.new @connection, self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }) - if postgresql_version < 80300 + if postgresql_version < 80200 raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!" end @@ -645,6 +645,12 @@ module ActiveRecord # connected server's characteristics. def connect @connection = PGconn.connect(@connection_parameters) + + # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of + # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision + # should know about this but can't detect it there, so deal with it here. + OID::Money.precision = (postgresql_version >= 80300) ? 19 : 10 + configure_connection rescue ::PG::Error => error if error.message.include?("does not exist") -- cgit v1.2.3 From ff16a39922cc3379426724a4bed2122087807b9d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 31 Jan 2016 03:19:07 +0900 Subject: Make to primary key instead of an unique index for internal tables --- .../lib/active_record/internal_metadata.rb | 10 ++------- activerecord/lib/active_record/schema_migration.rb | 9 ++------ activerecord/test/cases/ar_schema_test.rb | 2 +- .../test/cases/migration/table_and_index_test.rb | 24 ---------------------- 4 files changed, 5 insertions(+), 40 deletions(-) delete mode 100644 activerecord/test/cases/migration/table_and_index_test.rb diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb index 10fee4dca2..641b137851 100644 --- a/activerecord/lib/active_record/internal_metadata.rb +++ b/activerecord/lib/active_record/internal_metadata.rb @@ -18,10 +18,6 @@ module ActiveRecord "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}" end - def index_name - "#{table_name_prefix}unique_#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}" - end - def []=(key, value) first_or_initialize(key: key).update_attributes!(value: value) end @@ -38,10 +34,8 @@ module ActiveRecord def create_table unless table_exists? connection.create_table(table_name, id: false) do |t| - t.column :key, :string, null: false, limit: KEY_LIMIT - t.column :value, :string - t.index :key, unique: true, name: index_name - + t.string :key, primary_key: true, limit: KEY_LIMIT + t.string :value t.timestamps end end diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index 8f0ab2b55b..a5b693c349 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -16,22 +16,17 @@ module ActiveRecord "#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}" end - def index_name - "#{table_name_prefix}unique_#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}" - end - def table_exists? ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) } end def create_table(limit=nil) unless table_exists? - version_options = {null: false} + version_options = { primary_key: true } version_options[:limit] = limit if limit connection.create_table(table_name, id: false) do |t| - t.column :version, :string, version_options - t.index :version, unique: true, name: index_name + t.string :version, version_options end end end diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 1f32c48b95..9c99689c1e 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -21,7 +21,7 @@ if ActiveRecord::Base.connection.supports_migrations? ActiveRecord::Migration.verbose = @original_verbose end - def test_has_has_primary_key + def test_has_primary_key old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore assert_equal "version", ActiveRecord::SchemaMigration.primary_key diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb deleted file mode 100644 index 24cba84a09..0000000000 --- a/activerecord/test/cases/migration/table_and_index_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require "cases/helper" - -module ActiveRecord - class Migration - class TableAndIndexTest < ActiveRecord::TestCase - def test_add_schema_info_respects_prefix_and_suffix - conn = ActiveRecord::Base.connection - - conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true) - # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters - ActiveRecord::Base.table_name_prefix = 'p_' - ActiveRecord::Base.table_name_suffix = '_s' - conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true) - - conn.initialize_schema_migrations_table - - assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name] - ensure - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" - end - end - end -end -- cgit v1.2.3 From bee8bb580196d62bd4cd2a10acaa5d5e8d4849d1 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 31 Jan 2016 03:59:30 +0900 Subject: `OID::Money.precision` is unused since #15239 p PostgreSQLAdapter::OID::Money.precision # => 19 p PostgreSQLAdapter::OID::Money.new.precision # => nil --- .../lib/active_record/connection_adapters/postgresql/oid/money.rb | 2 -- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 6 ------ 2 files changed, 8 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb index 2163674019..dcc12ae2a4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -3,8 +3,6 @@ module ActiveRecord module PostgreSQL module OID # :nodoc: class Money < Type::Decimal # :nodoc: - class_attribute :precision - def type :money end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 2de6fbfaf0..820ee3565c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -645,12 +645,6 @@ module ActiveRecord # connected server's characteristics. def connect @connection = PGconn.connect(@connection_parameters) - - # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of - # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision - # should know about this but can't detect it there, so deal with it here. - OID::Money.precision = (postgresql_version >= 80300) ? 19 : 10 - configure_connection rescue ::PG::Error => error if error.message.include?("does not exist") -- cgit v1.2.3 From 6c54f6c746673915eeedbfb2907547189c1e37df Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Sun, 31 Jan 2016 00:33:54 +0530 Subject: - Updated persistence documentation to make it clear that save and save! won't update a record if validation fails. - Also fixed `update` method's documention to be uniform about this statement. Fixes #20821 [ci skip] [Vipul A M & pseidemann ] --- activerecord/lib/active_record/persistence.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 4d661735cc..d9a394fb71 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -106,7 +106,7 @@ module ActiveRecord # the existing record gets updated. # # By default, save always runs validations. If any of them fail the action - # is cancelled and #save returns +false+. However, if you supply + # is cancelled and #save returns +false+, and the record won't be saved. However, if you supply # validate: false, validations are bypassed altogether. See # ActiveRecord::Validations for more information. # @@ -133,7 +133,7 @@ module ActiveRecord # the existing record gets updated. # # By default, #save! always runs validations. If any of them fail - # ActiveRecord::RecordInvalid gets raised. However, if you supply + # ActiveRecord::RecordInvalid gets raised, and the record won't be saved. However, if you supply # validate: false, validations are bypassed altogether. See # ActiveRecord::Validations for more information. # @@ -270,7 +270,7 @@ module ActiveRecord alias update_attributes update # Updates its receiver just like #update but calls #save! instead - # of +save+, so an exception is raised if the record is invalid. + # of +save+, so an exception is raised if the record is invalid and saving will fail. def update!(attributes) # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. -- cgit v1.2.3 From 4663cab6247475ef26496fcdb4794a291a441ccd Mon Sep 17 00:00:00 2001 From: Prayag Verma Date: Sun, 31 Jan 2016 00:59:14 +0530 Subject: typo fix [ci skip] Spelling mistake - direcotry > directory --- guides/source/contributing_to_ruby_on_rails.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index cbc304c87f..f02f6a18ee 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -159,7 +159,7 @@ If you want to translate the Rails guides in your own language, follows these st * Copy the contents of *guides/source* into your own language directory and translate them. * Do NOT translate the HTML files, as they are automatically generated. -To generate the guides in HTML format cd into the *guides* direcotry then run (eg. for it-IT): +To generate the guides in HTML format cd into the *guides* directory then run (eg. for it-IT): ```bash $ bundle install -- cgit v1.2.3 From c01948cc57abb89a5c7a93d2b55cdda31c74ff23 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Sat, 30 Jan 2016 23:43:46 +0530 Subject: Pass 3 over testing guide - Various grammar fixes - Added assertions for update controller action tests - Added user helper tests - Fix typos [ci skip] --- guides/source/testing.md | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/guides/source/testing.md b/guides/source/testing.md index b5e49a41f4..f2acd4deb2 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -638,9 +638,9 @@ We were able to successfully test a very small workflow for visiting our blog an Functional Tests for Your Controllers ------------------------------------- -In Rails, testing the various actions of a controller is a form of writing functional tests. Remember your controllers handle the incoming web requests to your application and eventually respond with a rendered view. When writing functional tests, you're testing how your actions handle the requests and the expected result, or response in some cases an HTML view. +In Rails, testing the various actions of a controller is a form of writing functional tests. Remember your controllers handle the incoming web requests to your application and eventually respond with a rendered view. When writing functional tests, you are testing how your actions handle the requests and the expected result or response, in some cases an HTML view. -### What to Include in your Functional Tests +### What to include in your Functional Tests You should test for things such as: @@ -650,8 +650,7 @@ You should test for things such as: * was the correct object stored in the response template? * was the appropriate message displayed to the user in the view? -The easiest way to see functional tests in action is to generate a controller -scaffold: +The easiest way to see functional tests in action is to generate a controller using the scaffold generator: ```bash $ bin/rails generate scaffold_controller article title:string body:text @@ -664,7 +663,7 @@ create test/controllers/articles_controller_test.rb ``` This will generate the controller code and tests for an `Article` resource. -You can take look at the file `articles_controller_test.rb` in the `test/controllers` directory. +You can take a look at the file `articles_controller_test.rb` in the `test/controllers` directory. If you already have a controller and just want to generate the test scaffold code for each of the seven default actions, you can use the following command: @@ -677,7 +676,7 @@ create test/controllers/articles_controller_test.rb ... ``` -Let me take you through one such test, `test_should_get_index` from the file `articles_controller_test.rb`. +Let's take a look at one such test, `test_should_get_index` from the file `articles_controller_test.rb`. ```ruby # articles_controller_test.rb @@ -693,7 +692,7 @@ end In the `test_should_get_index` test, Rails simulates a request on the action called `index`, making sure the request was successful and also ensuring that the right response body has been generated. -The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments: +The `get` method kicks off the web request and populates the results into the `@response`. It accepts 4 arguments: * The action of the controller you are requesting. This can be in the form of a string or a route (i.e. `articles_url`). @@ -705,7 +704,7 @@ The `get` method kicks off the web request and populates the results into the re * `flash`: option with a hash of flash values. -All the keyword arguments are optional. +All of these keyword arguments are optional. Example: Calling the `:show` action, passing an `id` of 12 as the `params` and setting a `user_id` of 5 in the session: @@ -753,7 +752,7 @@ NOTE: Functional tests do not verify whether the specified request type is accep ### Testing XHR (AJAX) requests To test AJAX requests, you can specify the `xhr: true` option to `get`, `post`, -`patch`, `put`, and `delete` methods: +`patch`, `put`, and `delete` methods. For example: ```ruby test "ajax request" do @@ -808,7 +807,7 @@ post article_url # simulate the request with custom env variable ### Testing `flash` notices -If you remember from earlier one of the Three Hashes of the Apocalypse was `flash`. +If you remember from earlier, one of the Three Hashes of the Apocalypse was `flash`. We want to add a `flash` message to our blog application whenever someone successfully creates a new Article. @@ -893,7 +892,7 @@ test "should show article" do end ``` -Remember from our discussion earlier on fixtures the `articles()` method will give us access to our Articles fixtures. +Remember from our discussion earlier on fixtures, the `articles()` method will give us access to our Articles fixtures. How about deleting an existing Article? @@ -913,14 +912,19 @@ We can also add a test for updating an existing Article. ```ruby test "should update article" do article = articles(:one) + patch '/article', params: { id: article.id, article: { title: "updated" } } + assert_redirected_to article_path(article) + # Reload association to fetch updated data and assert that title is updated. + article.reload + assert_equal "updated", article.title end ``` Notice we're starting to see some duplication in these three tests, they both access the same Article fixture data. We can D.R.Y. this up by using the `setup` and `teardown` methods provided by `ActiveSupport::Callbacks`. -Our test should now look something like this, disregard the other tests we're leaving them out for brevity. +Our test should now look something as what follows. Disregard the other tests for now, we're leaving them out for brevity. ```ruby require 'test_helper' @@ -952,8 +956,12 @@ class ArticlesControllerTest < ActionDispatch::IntegrationTest end test "should update article" do - patch article_url(@article), params: { article: { title: "updated" } } + patch '/article', params: { id: @article.id, article: { title: "updated" } } + assert_redirected_to article_path(@article) + # Reload association to fetch updated data and assert that title is updated. + @article.reload + assert_equal "updated", @article.title end end ``` @@ -966,7 +974,7 @@ To avoid code duplication, you can add your own test helpers. Sign in helper can be a good example: ```ruby -test/test_helper.rb +#test/test_helper.rb module SignInHelper def sign_in(user) @@ -1087,8 +1095,9 @@ have to use a mixin like this: ```ruby class UserHelperTest < ActionView::TestCase - test "should return the user name" do - # ... + test "should return the user's full name" do + user = users(:david) + assert_equal "David Heinemeier Hansson", user_full_name(user) end end ``` @@ -1123,7 +1132,7 @@ In order to test that your mailer is working as expected, you can use unit tests For the purposes of unit testing a mailer, fixtures are used to provide an example of how the output _should_ look. Because these are example emails, and not Active Record data like the other fixtures, they are kept in their own subdirectory apart from the other fixtures. The name of the directory within `test/fixtures` directly corresponds to the name of the mailer. So, for a mailer named `UserMailer`, the fixtures should reside in `test/fixtures/user_mailer` directory. -When you generated your mailer, the generator creates stub fixtures for each of the mailers actions. If you didn't use the generator you'll have to make those files yourself. +When you generated your mailer, the generator creates stub fixtures for each of the mailers actions. If you didn't use the generator, you'll have to create those files yourself. #### The Basic Test Case @@ -1204,7 +1213,7 @@ Testing Jobs ------------ Since your custom jobs can be queued at different levels inside your application, -you'll need to test both jobs themselves (their behavior when they get enqueued) +you'll need to test both, the jobs themselves (their behavior when they get enqueued) and that other entities correctly enqueue them. ### A Basic Test Case @@ -1255,7 +1264,7 @@ end Testing Time-Dependent Code --------------------------- -Rails provides inbuilt helper methods that enable you to assert that your time-sensitve code works as expected. +Rails provides inbuilt helper methods that enable you to assert that your time-sensitive code works as expected. Here is an example using the [`travel_to`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html#method-i-travel_to) helper: -- cgit v1.2.3 From d2bd7bba8e48d6c5aa0ceea4a5fcac0191e541b5 Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Sat, 30 Jan 2016 15:10:25 -0500 Subject: Small cleanup to Testing Guide - inbuilt --> built-in - Remove random spaces from code examples [ci skip] --- guides/source/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/testing.md b/guides/source/testing.md index f2acd4deb2..f4894d4c11 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -1264,7 +1264,7 @@ end Testing Time-Dependent Code --------------------------- -Rails provides inbuilt helper methods that enable you to assert that your time-sensitive code works as expected. +Rails provides built-in helper methods that enable you to assert that your time-sensitive code works as expected. Here is an example using the [`travel_to`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html#method-i-travel_to) helper: -- cgit v1.2.3 From 3ded07e7c83cf94639626aa9e54fff7958b807d5 Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Sat, 30 Jan 2016 19:13:34 -0500 Subject: Add configuration section to "Active Record Basics" guide This is to fill in some missing information as apart of #22931. It's on purpose that the sample `Message` model inherits from `ActiveRecord::Base` -- Active Record is not meant to be coupled to Rails, and we can't guarantee that users outside of the Rails world will have an `ApplicationRecord` class that inherits from `ActiveRecord::Base`. [ci skip] --- guides/source/active_record_basics.md | 102 ++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index fba89f9d13..061ad975fc 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -375,3 +375,105 @@ and to roll it back, `rails db:rollback`. Note that the above code is database-agnostic: it will run in MySQL, PostgreSQL, Oracle and others. You can learn more about migrations in the [Active Record Migrations guide](migrations.html). + +Connecting to the Database +---------------------- + +### `config/database.yml` + +When managing connections to the database, the `config/database.yml` file is +your best friend. This file helps to keep track of the adapter and +authentication parameters you are using for every database +environment in your application. This file is automatically +generated in all new Rails applications that have Active Record +enabled. + +Here's an example of what this file looks like: + +```yaml +default: &default + adapter: sqlite3 + pool: 5 + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 +``` + +As you can see, there are 3 different database configurations listed +above. One for each of the Rails environments for this application +-- development, test, and production. As well, all three +environments are sharing the same adapter, pool, and timeout +settings, so this was extracted out to the "default" group. This +extraction helps to keep your `config/database.yml` file DRY and easy +to read. + +A small side note before the next topic -- the test database +configured above will be deleted and restored both before and after +every test run, so make sure you keep your environments' databases +separated and siloed. + +### Connecting Manually + +In some special cases, you may want to establish connections for you +Active Record models directly inside the model file itself. For this +purpose, the `establish_connection` function was created. Say for +example you have a `Message` model, like below: + +```ruby +class Message < ActiveRecord::Base +end +``` + +Also, say you want to have this `Message` model connect to a +special "msg" database, instead of the one that the rest of the +application is using. In this case, you would do as follows: + +Step 1: Add the `establish_connection` helper method to your model +file: + +```ruby +class Message < ActiveRecord::Base + establish_connection() +end +``` + +Step 2: Add the configuration for this new database to your +`config/database.yml` file, under the `msg` (or whichever name you +choose) database name. + +Step 3: Turn this database name into a symbol. Remember, using string +keys here is not supported! In this case, `msg` +converts simply to `:msg`. If you are unsure of what the symbolized +database name would be, simply boot up either an `irb` or `rails c` +session, and type in the name of your database as a String, with a +`.to_sym` at the end, as follows: + +```bash +irb(main):001:0> "msg".to_sym +=> :msg +``` + +Step 4: The last and final step! Simply use this symbolized +database name as the sole parameter for the `establish_connection` +method. + +```ruby +class Message < ActiveRecord::Base + establish_connection(:msg) +end +``` + +That's all there is to it! When configuring your database via the +`config/database.yml` file, or connecting manually in your model`, +connecting to your database and making changes is easy when using Active +Record. -- cgit v1.2.3 From 3ffa5a15cc966c80029043c77adb3184422e33b3 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Thu, 21 Jan 2016 08:11:58 +0900 Subject: make rake proxy work in rails engines --- activerecord/lib/active_record/railtie.rb | 2 +- railties/lib/rails/engine/commands.rb | 35 +------ railties/lib/rails/engine/commands_tasks.rb | 116 +++++++++++++++++++++ railties/lib/rails/tasks/engine.rake | 4 +- railties/test/generators/plugin_generator_test.rb | 2 +- .../test/generators/scaffold_generator_test.rb | 8 +- 6 files changed, 127 insertions(+), 40 deletions(-) create mode 100644 railties/lib/rails/engine/commands_tasks.rb diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 38916f7376..f4200e96b7 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -40,7 +40,7 @@ module ActiveRecord task :load_config do ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration - if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) + if defined?(ENGINE_ROOT) && engine = Rails::Engine.find(ENGINE_ROOT) if engine.paths['db/migrate'].existent ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a end diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb index a6d87b78e4..7bbd9ef744 100644 --- a/railties/lib/rails/engine/commands.rb +++ b/railties/lib/rails/engine/commands.rb @@ -1,3 +1,5 @@ +require 'rails/engine/commands_tasks' + ARGV << '--help' if ARGV.empty? aliases = { @@ -9,35 +11,4 @@ aliases = { command = ARGV.shift command = aliases[command] || command -require ENGINE_PATH -engine = ::Rails::Engine.find(ENGINE_ROOT) - -case command -when 'generate', 'destroy', 'test' - require 'rails/generators' - Rails::Generators.namespace = engine.railtie_namespace - engine.load_generators - require "rails/commands/#{command}" - -when '--version', '-v' - ARGV.unshift '--version' - require 'rails/commands/application' - -else - puts "Error: Command not recognized" unless %w(-h --help).include?(command) - puts <<-EOT -Usage: rails COMMAND [ARGS] - -The common Rails commands available for engines are: - generate Generate new code (short-cut alias: "g") - destroy Undo code generated with "generate" (short-cut alias: "d") - test Run tests (short-cut alias: "t") - -All commands can be run with -h for more information. - -If you want to run any commands that need to be run in context -of the application, like `rails server` or `rails console`, -you should do it from application's directory (typically test/dummy). - EOT - exit(1) -end +Rails::Engine::CommandsTasks.new(ARGV).run_command!(command) diff --git a/railties/lib/rails/engine/commands_tasks.rb b/railties/lib/rails/engine/commands_tasks.rb new file mode 100644 index 0000000000..fa3ee59b7d --- /dev/null +++ b/railties/lib/rails/engine/commands_tasks.rb @@ -0,0 +1,116 @@ +require 'rails/commands/rake_proxy' + +module Rails + class Engine + class CommandsTasks # :nodoc: + include Rails::RakeProxy + + attr_reader :argv + + HELP_MESSAGE = <<-EOT +Usage: rails COMMAND [ARGS] + +The common Rails commands available for engines are: + generate Generate new code (short-cut alias: "g") + destroy Undo code generated with "generate" (short-cut alias: "d") + test Run tests (short-cut alias: "t") + +All commands can be run with -h for more information. + +If you want to run any commands that need to be run in context +of the application, like `rails server` or `rails console`, +you should do it from application's directory (typically test/dummy). + +In addition to those commands, there are: + EOT + + COMMAND_WHITELIST = %w(generate destroy version help test) + + def initialize(argv) + @argv = argv + end + + def run_command!(command) + command = parse_command(command) + + if COMMAND_WHITELIST.include?(command) + send(command) + else + run_rake_task(command) + end + end + + def generate + generate_or_destroy(:generate) + end + + def destroy + generate_or_destroy(:destroy) + end + + def test + require_command!("test") + end + + def version + argv.unshift '--version' + require_command!("application") + end + + def help + write_help_message + write_commands(formatted_rake_tasks) + end + + private + + def require_command!(command) + require "rails/commands/#{command}" + end + + def generate_or_destroy(command) + load_generators + require_command!(command) + end + + def load_generators + require 'rails/generators' + require ENGINE_PATH + + engine = ::Rails::Engine.find(ENGINE_ROOT) + Rails::Generators.namespace = engine.railtie_namespace + engine.load_generators + end + + def write_help_message + puts HELP_MESSAGE + end + + def write_commands(commands) + width = commands.map { |name, _| name.size }.max || 10 + commands.each { |command| printf(" %-#{width}s %s\n", *command) } + end + + def parse_command(command) + case command + when '--version', '-v' + 'version' + when '--help', '-h' + 'help' + else + command + end + end + + def rake_tasks + return @rake_tasks if defined?(@rake_tasks) + + load_generators + Rake::TaskManager.record_task_metadata = true + Rake.application.init('rails') + Rake.application.load_rakefile + @rake_tasks = Rake.application.tasks.select(&:comment) + end + end + end +end diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake index c51524f8f6..e678103f63 100644 --- a/railties/lib/rails/tasks/engine.rake +++ b/railties/lib/rails/tasks/engine.rake @@ -4,8 +4,8 @@ task "load_app" do end task :environment => "app:environment" - if !defined?(ENGINE_PATH) || !ENGINE_PATH - ENGINE_PATH = find_engine_path(APP_RAKEFILE) + if !defined?(ENGINE_ROOT) || !ENGINE_ROOT + ENGINE_ROOT = find_engine_path(APP_RAKEFILE) end end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index ef33cd4ff7..0fd1d34131 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -240,7 +240,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--mountable"] FileUtils.cd destination_root quietly { system 'bundle install' } - output = `bundle exec rake db:migrate 2>&1` + output = `bin/rails db:migrate 2>&1` assert $?.success?, "Command failed: #{output}" end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 6f7a83cae0..5e45120704 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -486,7 +486,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase Dir.chdir(engine_path) do quietly do `bin/rails g scaffold User name:string age:integer; - bundle exec rake db:migrate` + bin/rails db:migrate` end assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end @@ -500,7 +500,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase Dir.chdir(engine_path) do quietly do `bin/rails g scaffold User name:string age:integer; - bundle exec rake db:migrate` + bin/rails db:migrate` end assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end @@ -514,7 +514,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase Dir.chdir(engine_path) do quietly do `bin/rails g scaffold User name:string age:integer; - bundle exec rake db:migrate` + bin/rails db:migrate` end assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end @@ -528,7 +528,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase Dir.chdir(engine_path) do quietly do `bin/rails g scaffold User name:string age:integer; - bundle exec rake db:migrate` + bin/rails db:migrate` end assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end -- cgit v1.2.3 From 896950a605c509f19f3e8cbde11e23ca87036ca3 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Sat, 30 Jan 2016 15:41:14 -0500 Subject: Add task to create precompiled action_cable.js and reorganize to accommodate --- actioncable/.gitignore | 1 + actioncable/Rakefile | 29 ++++++++ actioncable/actioncable.gemspec | 7 +- .../lib/assets/javascripts/action_cable.coffee.erb | 23 ------ .../javascripts/action_cable/connection.coffee | 81 ---------------------- .../action_cable/connection_monitor.coffee | 79 --------------------- .../javascripts/action_cable/consumer.coffee | 25 ------- .../lib/assets/javascripts/action_cable/index.js | 1 + .../action_cable/source/connection.coffee | 81 ++++++++++++++++++++++ .../action_cable/source/connection_monitor.coffee | 79 +++++++++++++++++++++ .../action_cable/source/consumer.coffee | 25 +++++++ .../action_cable/source/index.coffee.erb | 23 ++++++ .../action_cable/source/subscription.coffee | 68 ++++++++++++++++++ .../action_cable/source/subscriptions.coffee | 64 +++++++++++++++++ .../javascripts/action_cable/subscription.coffee | 68 ------------------ .../javascripts/action_cable/subscriptions.coffee | 64 ----------------- 16 files changed, 376 insertions(+), 342 deletions(-) create mode 100644 actioncable/.gitignore delete mode 100644 actioncable/lib/assets/javascripts/action_cable.coffee.erb delete mode 100644 actioncable/lib/assets/javascripts/action_cable/connection.coffee delete mode 100644 actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee delete mode 100644 actioncable/lib/assets/javascripts/action_cable/consumer.coffee create mode 100644 actioncable/lib/assets/javascripts/action_cable/index.js create mode 100644 actioncable/lib/assets/javascripts/action_cable/source/connection.coffee create mode 100644 actioncable/lib/assets/javascripts/action_cable/source/connection_monitor.coffee create mode 100644 actioncable/lib/assets/javascripts/action_cable/source/consumer.coffee create mode 100644 actioncable/lib/assets/javascripts/action_cable/source/index.coffee.erb create mode 100644 actioncable/lib/assets/javascripts/action_cable/source/subscription.coffee create mode 100644 actioncable/lib/assets/javascripts/action_cable/source/subscriptions.coffee delete mode 100644 actioncable/lib/assets/javascripts/action_cable/subscription.coffee delete mode 100644 actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee diff --git a/actioncable/.gitignore b/actioncable/.gitignore new file mode 100644 index 0000000000..ceeb05b410 --- /dev/null +++ b/actioncable/.gitignore @@ -0,0 +1 @@ +/tmp diff --git a/actioncable/Rakefile b/actioncable/Rakefile index b6c56e9195..9ba431f8a9 100644 --- a/actioncable/Rakefile +++ b/actioncable/Rakefile @@ -1,4 +1,8 @@ require 'rake/testtask' +require 'pathname' +require 'sprockets' +require 'coffee-script' +require 'action_cable' dir = File.dirname(__FILE__) @@ -11,3 +15,28 @@ Rake::TestTask.new do |t| t.verbose = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end + +namespace :assets do + desc "Compile dist/action_cable.js" + task :compile do + asset_mapping = { "source.js" => "action_cable.js" } + + root_path = Pathname.new(dir) + load_path = root_path.join("lib/assets/javascripts/action_cable") + + compile_path = root_path.join("tmp/sprockets") + compile_path.rmtree if compile_path.exist? + compile_path.mkpath + + environment = Sprockets::Environment.new + environment.append_path(load_path) + + manifest = Sprockets::Manifest.new(environment.index, compile_path) + manifest.compile(asset_mapping.keys) + + asset_mapping.each do |logical_path, dist_path| + fingerprint_path = manifest.assets[logical_path] + FileUtils.cp(compile_path.join(fingerprint_path), load_path.join("dist/#{dist_path}")) + end + end +end diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec index 14f968f1ef..0976895ef7 100644 --- a/actioncable/actioncable.gemspec +++ b/actioncable/actioncable.gemspec @@ -24,9 +24,12 @@ Gem::Specification.new do |s| s.add_dependency 'nio4r', '~> 1.2' s.add_dependency 'websocket-driver', '~> 0.6.1' - s.add_development_dependency 'em-hiredis', '~> 0.3.0' + s.add_development_dependency 'coffee-script', '~> 2.4.1' + s.add_development_dependency 'coffee-script-source', '~> 1.10.0' + s.add_development_dependency 'em-hiredis', '~> 0.3.0' s.add_development_dependency 'mocha' s.add_development_dependency 'pg' s.add_development_dependency 'puma' - s.add_development_dependency 'redis', '~> 3.0' + s.add_development_dependency 'redis', '~> 3.0' + s.add_development_dependency 'sprockets', '~> 3.5.2' end diff --git a/actioncable/lib/assets/javascripts/action_cable.coffee.erb b/actioncable/lib/assets/javascripts/action_cable.coffee.erb deleted file mode 100644 index 7daea4ebcd..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable.coffee.erb +++ /dev/null @@ -1,23 +0,0 @@ -#= require_self -#= require action_cable/consumer - -@ActionCable = - INTERNAL: <%= ActionCable::INTERNAL.to_json %> - - createConsumer: (url = @getConfig("url")) -> - new ActionCable.Consumer @createWebSocketURL(url) - - getConfig: (name) -> - element = document.head.querySelector("meta[name='action-cable-#{name}']") - element?.getAttribute("content") - - createWebSocketURL: (url) -> - if url and not /^wss?:/i.test(url) - a = document.createElement("a") - a.href = url - # Fix populating Location properties in IE. Otherwise, protocol will be blank. - a.href = a.href - a.protocol = a.protocol.replace("http", "ws") - a.href - else - url diff --git a/actioncable/lib/assets/javascripts/action_cable/connection.coffee b/actioncable/lib/assets/javascripts/action_cable/connection.coffee deleted file mode 100644 index fbd7dbd35b..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable/connection.coffee +++ /dev/null @@ -1,81 +0,0 @@ -# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation. - -{message_types} = ActionCable.INTERNAL - -class ActionCable.Connection - @reopenDelay: 500 - - constructor: (@consumer) -> - @open() - - send: (data) -> - if @isOpen() - @webSocket.send(JSON.stringify(data)) - true - else - false - - open: => - if @webSocket and not @isState("closed") - throw new Error("Existing connection must be closed before opening") - else - @webSocket = new WebSocket(@consumer.url) - @installEventHandlers() - true - - close: -> - @webSocket?.close() - - reopen: -> - if @isState("closed") - @open() - else - try - @close() - finally - setTimeout(@open, @constructor.reopenDelay) - - isOpen: -> - @isState("open") - - # Private - - isState: (states...) -> - @getState() in states - - getState: -> - return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState - null - - installEventHandlers: -> - for eventName of @events - handler = @events[eventName].bind(this) - @webSocket["on#{eventName}"] = handler - return - - events: - message: (event) -> - {identifier, message, type} = JSON.parse(event.data) - - switch type - when message_types.confirmation - @consumer.subscriptions.notify(identifier, "connected") - when message_types.rejection - @consumer.subscriptions.reject(identifier) - else - @consumer.subscriptions.notify(identifier, "received", message) - - open: -> - @disconnected = false - @consumer.subscriptions.reload() - - close: -> - @disconnect() - - error: -> - @disconnect() - - disconnect: -> - return if @disconnected - @disconnected = true - @consumer.subscriptions.notifyAll("disconnected") diff --git a/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee b/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee deleted file mode 100644 index 99b9a1c6d5..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee +++ /dev/null @@ -1,79 +0,0 @@ -# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting -# revival reconnections if things go astray. Internal class, not intended for direct user manipulation. -class ActionCable.ConnectionMonitor - @pollInterval: - min: 3 - max: 30 - - @staleThreshold: 6 # Server::Connections::BEAT_INTERVAL * 2 (missed two pings) - - identifier: ActionCable.INTERNAL.identifiers.ping - - constructor: (@consumer) -> - @consumer.subscriptions.add(this) - @start() - - connected: -> - @reset() - @pingedAt = now() - delete @disconnectedAt - - disconnected: -> - @disconnectedAt = now() - - received: -> - @pingedAt = now() - - reset: -> - @reconnectAttempts = 0 - - start: -> - @reset() - delete @stoppedAt - @startedAt = now() - @poll() - document.addEventListener("visibilitychange", @visibilityDidChange) - - stop: -> - @stoppedAt = now() - document.removeEventListener("visibilitychange", @visibilityDidChange) - - poll: -> - setTimeout => - unless @stoppedAt - @reconnectIfStale() - @poll() - , @getInterval() - - getInterval: -> - {min, max} = @constructor.pollInterval - interval = 5 * Math.log(@reconnectAttempts + 1) - clamp(interval, min, max) * 1000 - - reconnectIfStale: -> - if @connectionIsStale() - @reconnectAttempts++ - unless @disconnectedRecently() - @consumer.connection.reopen() - - connectionIsStale: -> - secondsSince(@pingedAt ? @startedAt) > @constructor.staleThreshold - - disconnectedRecently: -> - @disconnectedAt and secondsSince(@disconnectedAt) < @constructor.staleThreshold - - visibilityDidChange: => - if document.visibilityState is "visible" - setTimeout => - if @connectionIsStale() or not @consumer.connection.isOpen() - @consumer.connection.reopen() - , 200 - - now = -> - new Date().getTime() - - secondsSince = (time) -> - (now() - time) / 1000 - - clamp = (number, min, max) -> - Math.max(min, Math.min(max, number)) diff --git a/actioncable/lib/assets/javascripts/action_cable/consumer.coffee b/actioncable/lib/assets/javascripts/action_cable/consumer.coffee deleted file mode 100644 index fcd8d0fb6c..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable/consumer.coffee +++ /dev/null @@ -1,25 +0,0 @@ -#= require action_cable/connection -#= require action_cable/connection_monitor -#= require action_cable/subscriptions -#= require action_cable/subscription - -# The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established, -# the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates. -# The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription -# method. -# -# The following example shows how this can be setup: -# -# @App = {} -# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1" -# App.appearance = App.cable.subscriptions.create "AppearanceChannel" -# -# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. -class ActionCable.Consumer - constructor: (@url) -> - @subscriptions = new ActionCable.Subscriptions this - @connection = new ActionCable.Connection this - @connectionMonitor = new ActionCable.ConnectionMonitor this - - send: (data) -> - @connection.send(data) diff --git a/actioncable/lib/assets/javascripts/action_cable/index.js b/actioncable/lib/assets/javascripts/action_cable/index.js new file mode 100644 index 0000000000..6c69e42337 --- /dev/null +++ b/actioncable/lib/assets/javascripts/action_cable/index.js @@ -0,0 +1 @@ +//= require ./dist/action_cable diff --git a/actioncable/lib/assets/javascripts/action_cable/source/connection.coffee b/actioncable/lib/assets/javascripts/action_cable/source/connection.coffee new file mode 100644 index 0000000000..fbd7dbd35b --- /dev/null +++ b/actioncable/lib/assets/javascripts/action_cable/source/connection.coffee @@ -0,0 +1,81 @@ +# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation. + +{message_types} = ActionCable.INTERNAL + +class ActionCable.Connection + @reopenDelay: 500 + + constructor: (@consumer) -> + @open() + + send: (data) -> + if @isOpen() + @webSocket.send(JSON.stringify(data)) + true + else + false + + open: => + if @webSocket and not @isState("closed") + throw new Error("Existing connection must be closed before opening") + else + @webSocket = new WebSocket(@consumer.url) + @installEventHandlers() + true + + close: -> + @webSocket?.close() + + reopen: -> + if @isState("closed") + @open() + else + try + @close() + finally + setTimeout(@open, @constructor.reopenDelay) + + isOpen: -> + @isState("open") + + # Private + + isState: (states...) -> + @getState() in states + + getState: -> + return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState + null + + installEventHandlers: -> + for eventName of @events + handler = @events[eventName].bind(this) + @webSocket["on#{eventName}"] = handler + return + + events: + message: (event) -> + {identifier, message, type} = JSON.parse(event.data) + + switch type + when message_types.confirmation + @consumer.subscriptions.notify(identifier, "connected") + when message_types.rejection + @consumer.subscriptions.reject(identifier) + else + @consumer.subscriptions.notify(identifier, "received", message) + + open: -> + @disconnected = false + @consumer.subscriptions.reload() + + close: -> + @disconnect() + + error: -> + @disconnect() + + disconnect: -> + return if @disconnected + @disconnected = true + @consumer.subscriptions.notifyAll("disconnected") diff --git a/actioncable/lib/assets/javascripts/action_cable/source/connection_monitor.coffee b/actioncable/lib/assets/javascripts/action_cable/source/connection_monitor.coffee new file mode 100644 index 0000000000..99b9a1c6d5 --- /dev/null +++ b/actioncable/lib/assets/javascripts/action_cable/source/connection_monitor.coffee @@ -0,0 +1,79 @@ +# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting +# revival reconnections if things go astray. Internal class, not intended for direct user manipulation. +class ActionCable.ConnectionMonitor + @pollInterval: + min: 3 + max: 30 + + @staleThreshold: 6 # Server::Connections::BEAT_INTERVAL * 2 (missed two pings) + + identifier: ActionCable.INTERNAL.identifiers.ping + + constructor: (@consumer) -> + @consumer.subscriptions.add(this) + @start() + + connected: -> + @reset() + @pingedAt = now() + delete @disconnectedAt + + disconnected: -> + @disconnectedAt = now() + + received: -> + @pingedAt = now() + + reset: -> + @reconnectAttempts = 0 + + start: -> + @reset() + delete @stoppedAt + @startedAt = now() + @poll() + document.addEventListener("visibilitychange", @visibilityDidChange) + + stop: -> + @stoppedAt = now() + document.removeEventListener("visibilitychange", @visibilityDidChange) + + poll: -> + setTimeout => + unless @stoppedAt + @reconnectIfStale() + @poll() + , @getInterval() + + getInterval: -> + {min, max} = @constructor.pollInterval + interval = 5 * Math.log(@reconnectAttempts + 1) + clamp(interval, min, max) * 1000 + + reconnectIfStale: -> + if @connectionIsStale() + @reconnectAttempts++ + unless @disconnectedRecently() + @consumer.connection.reopen() + + connectionIsStale: -> + secondsSince(@pingedAt ? @startedAt) > @constructor.staleThreshold + + disconnectedRecently: -> + @disconnectedAt and secondsSince(@disconnectedAt) < @constructor.staleThreshold + + visibilityDidChange: => + if document.visibilityState is "visible" + setTimeout => + if @connectionIsStale() or not @consumer.connection.isOpen() + @consumer.connection.reopen() + , 200 + + now = -> + new Date().getTime() + + secondsSince = (time) -> + (now() - time) / 1000 + + clamp = (number, min, max) -> + Math.max(min, Math.min(max, number)) diff --git a/actioncable/lib/assets/javascripts/action_cable/source/consumer.coffee b/actioncable/lib/assets/javascripts/action_cable/source/consumer.coffee new file mode 100644 index 0000000000..717c0641a9 --- /dev/null +++ b/actioncable/lib/assets/javascripts/action_cable/source/consumer.coffee @@ -0,0 +1,25 @@ +#= require ./connection +#= require ./connection_monitor +#= require ./subscriptions +#= require ./subscription + +# The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established, +# the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates. +# The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription +# method. +# +# The following example shows how this can be setup: +# +# @App = {} +# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1" +# App.appearance = App.cable.subscriptions.create "AppearanceChannel" +# +# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. +class ActionCable.Consumer + constructor: (@url) -> + @subscriptions = new ActionCable.Subscriptions this + @connection = new ActionCable.Connection this + @connectionMonitor = new ActionCable.ConnectionMonitor this + + send: (data) -> + @connection.send(data) diff --git a/actioncable/lib/assets/javascripts/action_cable/source/index.coffee.erb b/actioncable/lib/assets/javascripts/action_cable/source/index.coffee.erb new file mode 100644 index 0000000000..f4615b7502 --- /dev/null +++ b/actioncable/lib/assets/javascripts/action_cable/source/index.coffee.erb @@ -0,0 +1,23 @@ +#= require_self +#= require ./consumer + +@ActionCable = + INTERNAL: <%= ActionCable::INTERNAL.to_json %> + + createConsumer: (url = @getConfig("url")) -> + new ActionCable.Consumer @createWebSocketURL(url) + + getConfig: (name) -> + element = document.head.querySelector("meta[name='action-cable-#{name}']") + element?.getAttribute("content") + + createWebSocketURL: (url) -> + if url and not /^wss?:/i.test(url) + a = document.createElement("a") + a.href = url + # Fix populating Location properties in IE. Otherwise, protocol will be blank. + a.href = a.href + a.protocol = a.protocol.replace("http", "ws") + a.href + else + url diff --git a/actioncable/lib/assets/javascripts/action_cable/source/subscription.coffee b/actioncable/lib/assets/javascripts/action_cable/source/subscription.coffee new file mode 100644 index 0000000000..339d676933 --- /dev/null +++ b/actioncable/lib/assets/javascripts/action_cable/source/subscription.coffee @@ -0,0 +1,68 @@ +# A new subscription is created through the ActionCable.Subscriptions instance available on the consumer. +# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding +# Channel instance on the server side. +# +# An example demonstrates the basic functionality: +# +# App.appearance = App.cable.subscriptions.create "AppearanceChannel", +# connected: -> +# # Called once the subscription has been successfully completed +# +# appear: -> +# @perform 'appear', appearing_on: @appearingOn() +# +# away: -> +# @perform 'away' +# +# appearingOn: -> +# $('main').data 'appearing-on' +# +# The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server +# by calling the `@perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away). +# The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter. +# +# This is how the server component would look: +# +# class AppearanceChannel < ApplicationActionCable::Channel +# def subscribed +# current_user.appear +# end +# +# def unsubscribed +# current_user.disappear +# end +# +# def appear(data) +# current_user.appear on: data['appearing_on'] +# end +# +# def away +# current_user.away +# end +# end +# +# The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name. +# The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method. +class ActionCable.Subscription + constructor: (@subscriptions, params = {}, mixin) -> + @identifier = JSON.stringify(params) + extend(this, mixin) + @subscriptions.add(this) + @consumer = @subscriptions.consumer + + # Perform a channel action with the optional data passed as an attribute + perform: (action, data = {}) -> + data.action = action + @send(data) + + send: (data) -> + @consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) + + unsubscribe: -> + @subscriptions.remove(this) + + extend = (object, properties) -> + if properties? + for key, value of properties + object[key] = value + object diff --git a/actioncable/lib/assets/javascripts/action_cable/source/subscriptions.coffee b/actioncable/lib/assets/javascripts/action_cable/source/subscriptions.coffee new file mode 100644 index 0000000000..ae041ffa2b --- /dev/null +++ b/actioncable/lib/assets/javascripts/action_cable/source/subscriptions.coffee @@ -0,0 +1,64 @@ +# Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user +# us ActionCable.Subscriptions#create, and it should be called through the consumer like so: +# +# @App = {} +# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1" +# App.appearance = App.cable.subscriptions.create "AppearanceChannel" +# +# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. +class ActionCable.Subscriptions + constructor: (@consumer) -> + @subscriptions = [] + + create: (channelName, mixin) -> + channel = channelName + params = if typeof channel is "object" then channel else {channel} + new ActionCable.Subscription this, params, mixin + + # Private + + add: (subscription) -> + @subscriptions.push(subscription) + @notify(subscription, "initialized") + @sendCommand(subscription, "subscribe") + + remove: (subscription) -> + @forget(subscription) + + unless @findAll(subscription.identifier).length + @sendCommand(subscription, "unsubscribe") + + reject: (identifier) -> + for subscription in @findAll(identifier) + @forget(subscription) + @notify(subscription, "rejected") + + forget: (subscription) -> + @subscriptions = (s for s in @subscriptions when s isnt subscription) + + findAll: (identifier) -> + s for s in @subscriptions when s.identifier is identifier + + reload: -> + for subscription in @subscriptions + @sendCommand(subscription, "subscribe") + + notifyAll: (callbackName, args...) -> + for subscription in @subscriptions + @notify(subscription, callbackName, args...) + + notify: (subscription, callbackName, args...) -> + if typeof subscription is "string" + subscriptions = @findAll(subscription) + else + subscriptions = [subscription] + + for subscription in subscriptions + subscription[callbackName]?(args...) + + sendCommand: (subscription, command) -> + {identifier} = subscription + if identifier is ActionCable.INTERNAL.identifiers.ping + @consumer.connection.isOpen() + else + @consumer.send({command, identifier}) diff --git a/actioncable/lib/assets/javascripts/action_cable/subscription.coffee b/actioncable/lib/assets/javascripts/action_cable/subscription.coffee deleted file mode 100644 index 339d676933..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable/subscription.coffee +++ /dev/null @@ -1,68 +0,0 @@ -# A new subscription is created through the ActionCable.Subscriptions instance available on the consumer. -# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding -# Channel instance on the server side. -# -# An example demonstrates the basic functionality: -# -# App.appearance = App.cable.subscriptions.create "AppearanceChannel", -# connected: -> -# # Called once the subscription has been successfully completed -# -# appear: -> -# @perform 'appear', appearing_on: @appearingOn() -# -# away: -> -# @perform 'away' -# -# appearingOn: -> -# $('main').data 'appearing-on' -# -# The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server -# by calling the `@perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away). -# The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter. -# -# This is how the server component would look: -# -# class AppearanceChannel < ApplicationActionCable::Channel -# def subscribed -# current_user.appear -# end -# -# def unsubscribed -# current_user.disappear -# end -# -# def appear(data) -# current_user.appear on: data['appearing_on'] -# end -# -# def away -# current_user.away -# end -# end -# -# The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name. -# The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method. -class ActionCable.Subscription - constructor: (@subscriptions, params = {}, mixin) -> - @identifier = JSON.stringify(params) - extend(this, mixin) - @subscriptions.add(this) - @consumer = @subscriptions.consumer - - # Perform a channel action with the optional data passed as an attribute - perform: (action, data = {}) -> - data.action = action - @send(data) - - send: (data) -> - @consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) - - unsubscribe: -> - @subscriptions.remove(this) - - extend = (object, properties) -> - if properties? - for key, value of properties - object[key] = value - object diff --git a/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee b/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee deleted file mode 100644 index ae041ffa2b..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee +++ /dev/null @@ -1,64 +0,0 @@ -# Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user -# us ActionCable.Subscriptions#create, and it should be called through the consumer like so: -# -# @App = {} -# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1" -# App.appearance = App.cable.subscriptions.create "AppearanceChannel" -# -# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. -class ActionCable.Subscriptions - constructor: (@consumer) -> - @subscriptions = [] - - create: (channelName, mixin) -> - channel = channelName - params = if typeof channel is "object" then channel else {channel} - new ActionCable.Subscription this, params, mixin - - # Private - - add: (subscription) -> - @subscriptions.push(subscription) - @notify(subscription, "initialized") - @sendCommand(subscription, "subscribe") - - remove: (subscription) -> - @forget(subscription) - - unless @findAll(subscription.identifier).length - @sendCommand(subscription, "unsubscribe") - - reject: (identifier) -> - for subscription in @findAll(identifier) - @forget(subscription) - @notify(subscription, "rejected") - - forget: (subscription) -> - @subscriptions = (s for s in @subscriptions when s isnt subscription) - - findAll: (identifier) -> - s for s in @subscriptions when s.identifier is identifier - - reload: -> - for subscription in @subscriptions - @sendCommand(subscription, "subscribe") - - notifyAll: (callbackName, args...) -> - for subscription in @subscriptions - @notify(subscription, callbackName, args...) - - notify: (subscription, callbackName, args...) -> - if typeof subscription is "string" - subscriptions = @findAll(subscription) - else - subscriptions = [subscription] - - for subscription in subscriptions - subscription[callbackName]?(args...) - - sendCommand: (subscription, command) -> - {identifier} = subscription - if identifier is ActionCable.INTERNAL.identifiers.ping - @consumer.connection.isOpen() - else - @consumer.send({command, identifier}) -- cgit v1.2.3 From 09a706065952d58d515420b19a55df619eb7f53d Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Sat, 30 Jan 2016 20:39:22 -0500 Subject: Improvements and reorganization of assets --- actioncable/.gitignore | 1 + actioncable/Rakefile | 10 ++- .../app/assets/javascripts/action_cable/index.js | 1 + .../action_cable/source/connection.coffee | 81 ++++++++++++++++++++++ .../action_cable/source/connection_monitor.coffee | 79 +++++++++++++++++++++ .../action_cable/source/consumer.coffee | 25 +++++++ .../action_cable/source/index.coffee.erb | 23 ++++++ .../action_cable/source/subscription.coffee | 68 ++++++++++++++++++ .../action_cable/source/subscriptions.coffee | 64 +++++++++++++++++ .../lib/assets/javascripts/action_cable/index.js | 1 - .../action_cable/source/connection.coffee | 81 ---------------------- .../action_cable/source/connection_monitor.coffee | 79 --------------------- .../action_cable/source/consumer.coffee | 25 ------- .../action_cable/source/index.coffee.erb | 23 ------ .../action_cable/source/subscription.coffee | 68 ------------------ .../action_cable/source/subscriptions.coffee | 64 ----------------- 16 files changed, 350 insertions(+), 343 deletions(-) create mode 100644 actioncable/app/assets/javascripts/action_cable/index.js create mode 100644 actioncable/app/assets/javascripts/action_cable/source/connection.coffee create mode 100644 actioncable/app/assets/javascripts/action_cable/source/connection_monitor.coffee create mode 100644 actioncable/app/assets/javascripts/action_cable/source/consumer.coffee create mode 100644 actioncable/app/assets/javascripts/action_cable/source/index.coffee.erb create mode 100644 actioncable/app/assets/javascripts/action_cable/source/subscription.coffee create mode 100644 actioncable/app/assets/javascripts/action_cable/source/subscriptions.coffee delete mode 100644 actioncable/lib/assets/javascripts/action_cable/index.js delete mode 100644 actioncable/lib/assets/javascripts/action_cable/source/connection.coffee delete mode 100644 actioncable/lib/assets/javascripts/action_cable/source/connection_monitor.coffee delete mode 100644 actioncable/lib/assets/javascripts/action_cable/source/consumer.coffee delete mode 100644 actioncable/lib/assets/javascripts/action_cable/source/index.coffee.erb delete mode 100644 actioncable/lib/assets/javascripts/action_cable/source/subscription.coffee delete mode 100644 actioncable/lib/assets/javascripts/action_cable/source/subscriptions.coffee diff --git a/actioncable/.gitignore b/actioncable/.gitignore index ceeb05b410..8ded114548 100644 --- a/actioncable/.gitignore +++ b/actioncable/.gitignore @@ -1 +1,2 @@ +/lib/assets/javascripts/action_cable.js /tmp diff --git a/actioncable/Rakefile b/actioncable/Rakefile index 9ba431f8a9..0a036e3e3d 100644 --- a/actioncable/Rakefile +++ b/actioncable/Rakefile @@ -19,10 +19,13 @@ end namespace :assets do desc "Compile dist/action_cable.js" task :compile do + puts 'Compiling Action Cable assets...' + asset_mapping = { "source.js" => "action_cable.js" } root_path = Pathname.new(dir) - load_path = root_path.join("lib/assets/javascripts/action_cable") + load_path = root_path.join("app/assets/javascripts/action_cable") + destination_path = root_path.join("lib/assets/javascripts") compile_path = root_path.join("tmp/sprockets") compile_path.rmtree if compile_path.exist? @@ -36,7 +39,10 @@ namespace :assets do asset_mapping.each do |logical_path, dist_path| fingerprint_path = manifest.assets[logical_path] - FileUtils.cp(compile_path.join(fingerprint_path), load_path.join("dist/#{dist_path}")) + FileUtils.cp(compile_path.join(fingerprint_path), destination_path.join(dist_path)) end + + puts '======' + puts 'Action Cable assets compiled successfully!' end end diff --git a/actioncable/app/assets/javascripts/action_cable/index.js b/actioncable/app/assets/javascripts/action_cable/index.js new file mode 100644 index 0000000000..e97870c3b0 --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable/index.js @@ -0,0 +1 @@ +//= require_tree ./source diff --git a/actioncable/app/assets/javascripts/action_cable/source/connection.coffee b/actioncable/app/assets/javascripts/action_cable/source/connection.coffee new file mode 100644 index 0000000000..fbd7dbd35b --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable/source/connection.coffee @@ -0,0 +1,81 @@ +# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation. + +{message_types} = ActionCable.INTERNAL + +class ActionCable.Connection + @reopenDelay: 500 + + constructor: (@consumer) -> + @open() + + send: (data) -> + if @isOpen() + @webSocket.send(JSON.stringify(data)) + true + else + false + + open: => + if @webSocket and not @isState("closed") + throw new Error("Existing connection must be closed before opening") + else + @webSocket = new WebSocket(@consumer.url) + @installEventHandlers() + true + + close: -> + @webSocket?.close() + + reopen: -> + if @isState("closed") + @open() + else + try + @close() + finally + setTimeout(@open, @constructor.reopenDelay) + + isOpen: -> + @isState("open") + + # Private + + isState: (states...) -> + @getState() in states + + getState: -> + return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState + null + + installEventHandlers: -> + for eventName of @events + handler = @events[eventName].bind(this) + @webSocket["on#{eventName}"] = handler + return + + events: + message: (event) -> + {identifier, message, type} = JSON.parse(event.data) + + switch type + when message_types.confirmation + @consumer.subscriptions.notify(identifier, "connected") + when message_types.rejection + @consumer.subscriptions.reject(identifier) + else + @consumer.subscriptions.notify(identifier, "received", message) + + open: -> + @disconnected = false + @consumer.subscriptions.reload() + + close: -> + @disconnect() + + error: -> + @disconnect() + + disconnect: -> + return if @disconnected + @disconnected = true + @consumer.subscriptions.notifyAll("disconnected") diff --git a/actioncable/app/assets/javascripts/action_cable/source/connection_monitor.coffee b/actioncable/app/assets/javascripts/action_cable/source/connection_monitor.coffee new file mode 100644 index 0000000000..99b9a1c6d5 --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable/source/connection_monitor.coffee @@ -0,0 +1,79 @@ +# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting +# revival reconnections if things go astray. Internal class, not intended for direct user manipulation. +class ActionCable.ConnectionMonitor + @pollInterval: + min: 3 + max: 30 + + @staleThreshold: 6 # Server::Connections::BEAT_INTERVAL * 2 (missed two pings) + + identifier: ActionCable.INTERNAL.identifiers.ping + + constructor: (@consumer) -> + @consumer.subscriptions.add(this) + @start() + + connected: -> + @reset() + @pingedAt = now() + delete @disconnectedAt + + disconnected: -> + @disconnectedAt = now() + + received: -> + @pingedAt = now() + + reset: -> + @reconnectAttempts = 0 + + start: -> + @reset() + delete @stoppedAt + @startedAt = now() + @poll() + document.addEventListener("visibilitychange", @visibilityDidChange) + + stop: -> + @stoppedAt = now() + document.removeEventListener("visibilitychange", @visibilityDidChange) + + poll: -> + setTimeout => + unless @stoppedAt + @reconnectIfStale() + @poll() + , @getInterval() + + getInterval: -> + {min, max} = @constructor.pollInterval + interval = 5 * Math.log(@reconnectAttempts + 1) + clamp(interval, min, max) * 1000 + + reconnectIfStale: -> + if @connectionIsStale() + @reconnectAttempts++ + unless @disconnectedRecently() + @consumer.connection.reopen() + + connectionIsStale: -> + secondsSince(@pingedAt ? @startedAt) > @constructor.staleThreshold + + disconnectedRecently: -> + @disconnectedAt and secondsSince(@disconnectedAt) < @constructor.staleThreshold + + visibilityDidChange: => + if document.visibilityState is "visible" + setTimeout => + if @connectionIsStale() or not @consumer.connection.isOpen() + @consumer.connection.reopen() + , 200 + + now = -> + new Date().getTime() + + secondsSince = (time) -> + (now() - time) / 1000 + + clamp = (number, min, max) -> + Math.max(min, Math.min(max, number)) diff --git a/actioncable/app/assets/javascripts/action_cable/source/consumer.coffee b/actioncable/app/assets/javascripts/action_cable/source/consumer.coffee new file mode 100644 index 0000000000..717c0641a9 --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable/source/consumer.coffee @@ -0,0 +1,25 @@ +#= require ./connection +#= require ./connection_monitor +#= require ./subscriptions +#= require ./subscription + +# The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established, +# the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates. +# The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription +# method. +# +# The following example shows how this can be setup: +# +# @App = {} +# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1" +# App.appearance = App.cable.subscriptions.create "AppearanceChannel" +# +# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. +class ActionCable.Consumer + constructor: (@url) -> + @subscriptions = new ActionCable.Subscriptions this + @connection = new ActionCable.Connection this + @connectionMonitor = new ActionCable.ConnectionMonitor this + + send: (data) -> + @connection.send(data) diff --git a/actioncable/app/assets/javascripts/action_cable/source/index.coffee.erb b/actioncable/app/assets/javascripts/action_cable/source/index.coffee.erb new file mode 100644 index 0000000000..f4615b7502 --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable/source/index.coffee.erb @@ -0,0 +1,23 @@ +#= require_self +#= require ./consumer + +@ActionCable = + INTERNAL: <%= ActionCable::INTERNAL.to_json %> + + createConsumer: (url = @getConfig("url")) -> + new ActionCable.Consumer @createWebSocketURL(url) + + getConfig: (name) -> + element = document.head.querySelector("meta[name='action-cable-#{name}']") + element?.getAttribute("content") + + createWebSocketURL: (url) -> + if url and not /^wss?:/i.test(url) + a = document.createElement("a") + a.href = url + # Fix populating Location properties in IE. Otherwise, protocol will be blank. + a.href = a.href + a.protocol = a.protocol.replace("http", "ws") + a.href + else + url diff --git a/actioncable/app/assets/javascripts/action_cable/source/subscription.coffee b/actioncable/app/assets/javascripts/action_cable/source/subscription.coffee new file mode 100644 index 0000000000..339d676933 --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable/source/subscription.coffee @@ -0,0 +1,68 @@ +# A new subscription is created through the ActionCable.Subscriptions instance available on the consumer. +# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding +# Channel instance on the server side. +# +# An example demonstrates the basic functionality: +# +# App.appearance = App.cable.subscriptions.create "AppearanceChannel", +# connected: -> +# # Called once the subscription has been successfully completed +# +# appear: -> +# @perform 'appear', appearing_on: @appearingOn() +# +# away: -> +# @perform 'away' +# +# appearingOn: -> +# $('main').data 'appearing-on' +# +# The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server +# by calling the `@perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away). +# The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter. +# +# This is how the server component would look: +# +# class AppearanceChannel < ApplicationActionCable::Channel +# def subscribed +# current_user.appear +# end +# +# def unsubscribed +# current_user.disappear +# end +# +# def appear(data) +# current_user.appear on: data['appearing_on'] +# end +# +# def away +# current_user.away +# end +# end +# +# The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name. +# The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method. +class ActionCable.Subscription + constructor: (@subscriptions, params = {}, mixin) -> + @identifier = JSON.stringify(params) + extend(this, mixin) + @subscriptions.add(this) + @consumer = @subscriptions.consumer + + # Perform a channel action with the optional data passed as an attribute + perform: (action, data = {}) -> + data.action = action + @send(data) + + send: (data) -> + @consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) + + unsubscribe: -> + @subscriptions.remove(this) + + extend = (object, properties) -> + if properties? + for key, value of properties + object[key] = value + object diff --git a/actioncable/app/assets/javascripts/action_cable/source/subscriptions.coffee b/actioncable/app/assets/javascripts/action_cable/source/subscriptions.coffee new file mode 100644 index 0000000000..ae041ffa2b --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable/source/subscriptions.coffee @@ -0,0 +1,64 @@ +# Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user +# us ActionCable.Subscriptions#create, and it should be called through the consumer like so: +# +# @App = {} +# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1" +# App.appearance = App.cable.subscriptions.create "AppearanceChannel" +# +# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. +class ActionCable.Subscriptions + constructor: (@consumer) -> + @subscriptions = [] + + create: (channelName, mixin) -> + channel = channelName + params = if typeof channel is "object" then channel else {channel} + new ActionCable.Subscription this, params, mixin + + # Private + + add: (subscription) -> + @subscriptions.push(subscription) + @notify(subscription, "initialized") + @sendCommand(subscription, "subscribe") + + remove: (subscription) -> + @forget(subscription) + + unless @findAll(subscription.identifier).length + @sendCommand(subscription, "unsubscribe") + + reject: (identifier) -> + for subscription in @findAll(identifier) + @forget(subscription) + @notify(subscription, "rejected") + + forget: (subscription) -> + @subscriptions = (s for s in @subscriptions when s isnt subscription) + + findAll: (identifier) -> + s for s in @subscriptions when s.identifier is identifier + + reload: -> + for subscription in @subscriptions + @sendCommand(subscription, "subscribe") + + notifyAll: (callbackName, args...) -> + for subscription in @subscriptions + @notify(subscription, callbackName, args...) + + notify: (subscription, callbackName, args...) -> + if typeof subscription is "string" + subscriptions = @findAll(subscription) + else + subscriptions = [subscription] + + for subscription in subscriptions + subscription[callbackName]?(args...) + + sendCommand: (subscription, command) -> + {identifier} = subscription + if identifier is ActionCable.INTERNAL.identifiers.ping + @consumer.connection.isOpen() + else + @consumer.send({command, identifier}) diff --git a/actioncable/lib/assets/javascripts/action_cable/index.js b/actioncable/lib/assets/javascripts/action_cable/index.js deleted file mode 100644 index 6c69e42337..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable/index.js +++ /dev/null @@ -1 +0,0 @@ -//= require ./dist/action_cable diff --git a/actioncable/lib/assets/javascripts/action_cable/source/connection.coffee b/actioncable/lib/assets/javascripts/action_cable/source/connection.coffee deleted file mode 100644 index fbd7dbd35b..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable/source/connection.coffee +++ /dev/null @@ -1,81 +0,0 @@ -# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation. - -{message_types} = ActionCable.INTERNAL - -class ActionCable.Connection - @reopenDelay: 500 - - constructor: (@consumer) -> - @open() - - send: (data) -> - if @isOpen() - @webSocket.send(JSON.stringify(data)) - true - else - false - - open: => - if @webSocket and not @isState("closed") - throw new Error("Existing connection must be closed before opening") - else - @webSocket = new WebSocket(@consumer.url) - @installEventHandlers() - true - - close: -> - @webSocket?.close() - - reopen: -> - if @isState("closed") - @open() - else - try - @close() - finally - setTimeout(@open, @constructor.reopenDelay) - - isOpen: -> - @isState("open") - - # Private - - isState: (states...) -> - @getState() in states - - getState: -> - return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState - null - - installEventHandlers: -> - for eventName of @events - handler = @events[eventName].bind(this) - @webSocket["on#{eventName}"] = handler - return - - events: - message: (event) -> - {identifier, message, type} = JSON.parse(event.data) - - switch type - when message_types.confirmation - @consumer.subscriptions.notify(identifier, "connected") - when message_types.rejection - @consumer.subscriptions.reject(identifier) - else - @consumer.subscriptions.notify(identifier, "received", message) - - open: -> - @disconnected = false - @consumer.subscriptions.reload() - - close: -> - @disconnect() - - error: -> - @disconnect() - - disconnect: -> - return if @disconnected - @disconnected = true - @consumer.subscriptions.notifyAll("disconnected") diff --git a/actioncable/lib/assets/javascripts/action_cable/source/connection_monitor.coffee b/actioncable/lib/assets/javascripts/action_cable/source/connection_monitor.coffee deleted file mode 100644 index 99b9a1c6d5..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable/source/connection_monitor.coffee +++ /dev/null @@ -1,79 +0,0 @@ -# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting -# revival reconnections if things go astray. Internal class, not intended for direct user manipulation. -class ActionCable.ConnectionMonitor - @pollInterval: - min: 3 - max: 30 - - @staleThreshold: 6 # Server::Connections::BEAT_INTERVAL * 2 (missed two pings) - - identifier: ActionCable.INTERNAL.identifiers.ping - - constructor: (@consumer) -> - @consumer.subscriptions.add(this) - @start() - - connected: -> - @reset() - @pingedAt = now() - delete @disconnectedAt - - disconnected: -> - @disconnectedAt = now() - - received: -> - @pingedAt = now() - - reset: -> - @reconnectAttempts = 0 - - start: -> - @reset() - delete @stoppedAt - @startedAt = now() - @poll() - document.addEventListener("visibilitychange", @visibilityDidChange) - - stop: -> - @stoppedAt = now() - document.removeEventListener("visibilitychange", @visibilityDidChange) - - poll: -> - setTimeout => - unless @stoppedAt - @reconnectIfStale() - @poll() - , @getInterval() - - getInterval: -> - {min, max} = @constructor.pollInterval - interval = 5 * Math.log(@reconnectAttempts + 1) - clamp(interval, min, max) * 1000 - - reconnectIfStale: -> - if @connectionIsStale() - @reconnectAttempts++ - unless @disconnectedRecently() - @consumer.connection.reopen() - - connectionIsStale: -> - secondsSince(@pingedAt ? @startedAt) > @constructor.staleThreshold - - disconnectedRecently: -> - @disconnectedAt and secondsSince(@disconnectedAt) < @constructor.staleThreshold - - visibilityDidChange: => - if document.visibilityState is "visible" - setTimeout => - if @connectionIsStale() or not @consumer.connection.isOpen() - @consumer.connection.reopen() - , 200 - - now = -> - new Date().getTime() - - secondsSince = (time) -> - (now() - time) / 1000 - - clamp = (number, min, max) -> - Math.max(min, Math.min(max, number)) diff --git a/actioncable/lib/assets/javascripts/action_cable/source/consumer.coffee b/actioncable/lib/assets/javascripts/action_cable/source/consumer.coffee deleted file mode 100644 index 717c0641a9..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable/source/consumer.coffee +++ /dev/null @@ -1,25 +0,0 @@ -#= require ./connection -#= require ./connection_monitor -#= require ./subscriptions -#= require ./subscription - -# The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established, -# the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates. -# The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription -# method. -# -# The following example shows how this can be setup: -# -# @App = {} -# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1" -# App.appearance = App.cable.subscriptions.create "AppearanceChannel" -# -# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. -class ActionCable.Consumer - constructor: (@url) -> - @subscriptions = new ActionCable.Subscriptions this - @connection = new ActionCable.Connection this - @connectionMonitor = new ActionCable.ConnectionMonitor this - - send: (data) -> - @connection.send(data) diff --git a/actioncable/lib/assets/javascripts/action_cable/source/index.coffee.erb b/actioncable/lib/assets/javascripts/action_cable/source/index.coffee.erb deleted file mode 100644 index f4615b7502..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable/source/index.coffee.erb +++ /dev/null @@ -1,23 +0,0 @@ -#= require_self -#= require ./consumer - -@ActionCable = - INTERNAL: <%= ActionCable::INTERNAL.to_json %> - - createConsumer: (url = @getConfig("url")) -> - new ActionCable.Consumer @createWebSocketURL(url) - - getConfig: (name) -> - element = document.head.querySelector("meta[name='action-cable-#{name}']") - element?.getAttribute("content") - - createWebSocketURL: (url) -> - if url and not /^wss?:/i.test(url) - a = document.createElement("a") - a.href = url - # Fix populating Location properties in IE. Otherwise, protocol will be blank. - a.href = a.href - a.protocol = a.protocol.replace("http", "ws") - a.href - else - url diff --git a/actioncable/lib/assets/javascripts/action_cable/source/subscription.coffee b/actioncable/lib/assets/javascripts/action_cable/source/subscription.coffee deleted file mode 100644 index 339d676933..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable/source/subscription.coffee +++ /dev/null @@ -1,68 +0,0 @@ -# A new subscription is created through the ActionCable.Subscriptions instance available on the consumer. -# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding -# Channel instance on the server side. -# -# An example demonstrates the basic functionality: -# -# App.appearance = App.cable.subscriptions.create "AppearanceChannel", -# connected: -> -# # Called once the subscription has been successfully completed -# -# appear: -> -# @perform 'appear', appearing_on: @appearingOn() -# -# away: -> -# @perform 'away' -# -# appearingOn: -> -# $('main').data 'appearing-on' -# -# The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server -# by calling the `@perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away). -# The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter. -# -# This is how the server component would look: -# -# class AppearanceChannel < ApplicationActionCable::Channel -# def subscribed -# current_user.appear -# end -# -# def unsubscribed -# current_user.disappear -# end -# -# def appear(data) -# current_user.appear on: data['appearing_on'] -# end -# -# def away -# current_user.away -# end -# end -# -# The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name. -# The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method. -class ActionCable.Subscription - constructor: (@subscriptions, params = {}, mixin) -> - @identifier = JSON.stringify(params) - extend(this, mixin) - @subscriptions.add(this) - @consumer = @subscriptions.consumer - - # Perform a channel action with the optional data passed as an attribute - perform: (action, data = {}) -> - data.action = action - @send(data) - - send: (data) -> - @consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) - - unsubscribe: -> - @subscriptions.remove(this) - - extend = (object, properties) -> - if properties? - for key, value of properties - object[key] = value - object diff --git a/actioncable/lib/assets/javascripts/action_cable/source/subscriptions.coffee b/actioncable/lib/assets/javascripts/action_cable/source/subscriptions.coffee deleted file mode 100644 index ae041ffa2b..0000000000 --- a/actioncable/lib/assets/javascripts/action_cable/source/subscriptions.coffee +++ /dev/null @@ -1,64 +0,0 @@ -# Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user -# us ActionCable.Subscriptions#create, and it should be called through the consumer like so: -# -# @App = {} -# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1" -# App.appearance = App.cable.subscriptions.create "AppearanceChannel" -# -# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. -class ActionCable.Subscriptions - constructor: (@consumer) -> - @subscriptions = [] - - create: (channelName, mixin) -> - channel = channelName - params = if typeof channel is "object" then channel else {channel} - new ActionCable.Subscription this, params, mixin - - # Private - - add: (subscription) -> - @subscriptions.push(subscription) - @notify(subscription, "initialized") - @sendCommand(subscription, "subscribe") - - remove: (subscription) -> - @forget(subscription) - - unless @findAll(subscription.identifier).length - @sendCommand(subscription, "unsubscribe") - - reject: (identifier) -> - for subscription in @findAll(identifier) - @forget(subscription) - @notify(subscription, "rejected") - - forget: (subscription) -> - @subscriptions = (s for s in @subscriptions when s isnt subscription) - - findAll: (identifier) -> - s for s in @subscriptions when s.identifier is identifier - - reload: -> - for subscription in @subscriptions - @sendCommand(subscription, "subscribe") - - notifyAll: (callbackName, args...) -> - for subscription in @subscriptions - @notify(subscription, callbackName, args...) - - notify: (subscription, callbackName, args...) -> - if typeof subscription is "string" - subscriptions = @findAll(subscription) - else - subscriptions = [subscription] - - for subscription in subscriptions - subscription[callbackName]?(args...) - - sendCommand: (subscription, command) -> - {identifier} = subscription - if identifier is ActionCable.INTERNAL.identifiers.ping - @consumer.connection.isOpen() - else - @consumer.send({command, identifier}) -- cgit v1.2.3 From cb040aa0e54b8659c895cff3bf7302716f2b81ed Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Sat, 30 Jan 2016 20:51:18 -0500 Subject: Add Action Cable asset building as release step --- tasks/release.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks/release.rb b/tasks/release.rb index c7704aa865..25ba91cb49 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -48,6 +48,7 @@ directory "pkg" task gem => %w(update_versions pkg) do cmd = "" cmd << "cd #{framework} && " unless framework == "rails" + cmd << "bundle exec rake assets:compile && " if framework == "actioncable" cmd << "gem build #{gemspec} && mv #{framework}-#{version}.gem #{root}/pkg/" sh cmd end -- cgit v1.2.3 From 9d426d3fe38f58b6157e60223fff01ba575712f6 Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Sat, 30 Jan 2016 21:04:00 -0500 Subject: Move Action Cable back to the main build --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 869507a9fd..ae38617b99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,6 @@ rvm: matrix: allow_failures: - rvm: ruby-head - - env: "GEM=ac" fast_finish: true notifications: email: false -- cgit v1.2.3 From 7d67c318f61f4ea60e1b66367a869b788ea50e6a Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 31 Jan 2016 16:43:52 +0900 Subject: Remove odd ` [ci skip] --- guides/source/active_record_basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index 061ad975fc..0932cc4829 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -474,6 +474,6 @@ end ``` That's all there is to it! When configuring your database via the -`config/database.yml` file, or connecting manually in your model`, +`config/database.yml` file, or connecting manually in your model, connecting to your database and making changes is easy when using Active Record. -- cgit v1.2.3 From 8e083e15026019b69af9f2a9d2214e75458592f6 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 31 Jan 2016 17:36:04 +0900 Subject: Use `t.index` in `create_table` instead of `add_index` in test schema For reduce bootstrap queries in tests. --- activerecord/test/schema/mysql2_specific_schema.rb | 9 ++++----- activerecord/test/schema/schema.rb | 11 +++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index 16ff6de24d..101e657982 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -21,16 +21,15 @@ ActiveRecord::Schema.define do t.index :var_binary end - create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t| + create_table :key_tests, force: true, options: 'ENGINE=MyISAM' do |t| t.string :awesome t.string :pizza t.string :snacks + t.index :awesome, type: :fulltext, name: 'index_key_tests_on_awesome' + t.index :pizza, using: :btree, name: 'index_key_tests_on_pizza' + t.index :snacks, name: 'index_key_tests_on_snack' end - add_index :key_tests, :awesome, :type => :fulltext, :name => 'index_key_tests_on_awesome' - add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza' - add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack' - create_table :collation_tests, id: false, force: true do |t| t.string :string_cs_column, limit: 1, collation: 'utf8_bin' t.string :string_ci_column, limit: 1, collation: 'utf8_general_ci' diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index fcc4eee79c..b9e0706d60 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -202,12 +202,11 @@ ActiveRecord::Schema.define do t.integer :rating, default: 1 t.integer :account_id t.string :description, default: "" + t.index [:firm_id, :type, :rating], name: "company_index" + t.index [:firm_id, :type], name: "company_partial_index", where: "rating > 10" + t.index :name, name: 'company_name_index', using: :btree end - add_index :companies, [:firm_id, :type, :rating], name: "company_index" - add_index :companies, [:firm_id, :type], name: "company_partial_index", where: "rating > 10" - add_index :companies, :name, name: 'company_name_index', using: :btree - create_table :content, force: true do |t| t.string :title end @@ -304,8 +303,8 @@ ActiveRecord::Schema.define do create_table :edges, force: true, id: false do |t| t.column :source_id, :integer, null: false t.column :sink_id, :integer, null: false + t.index [:source_id, :sink_id], unique: true, name: 'unique_edge_index' end - add_index :edges, [:source_id, :sink_id], unique: true, name: 'unique_edge_index' create_table :engines, force: true do |t| t.integer :car_id @@ -782,8 +781,8 @@ ActiveRecord::Schema.define do t.string :nick, null: false t.string :name t.column :books_count, :integer, null: false, default: 0 + t.index :nick, unique: true end - add_index :subscribers, :nick, unique: true create_table :subscriptions, force: true do |t| t.string :subscriber_id -- cgit v1.2.3 From 6e2302ee59e3eebf87f35440cb6b3879ae23c1de Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Sun, 31 Jan 2016 14:41:38 +0530 Subject: Added test for backward compatibility of null constraints on timestamp columns --- .../test/cases/migration/compatibility_test.rb | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb index 6a9cdd9d29..6d5b6243db 100644 --- a/activerecord/test/cases/migration/compatibility_test.rb +++ b/activerecord/test/cases/migration/compatibility_test.rb @@ -71,6 +71,36 @@ module ActiveRecord ensure connection.drop_table :more_testings rescue nil end + + def test_timestamps_have_null_constraints_if_not_present_in_migration_of_create_table + migration = Class.new(ActiveRecord::Migration) { + def migrate(x) + create_table :more_testings do |t| + t.timestamps + end + end + }.new + + ActiveRecord::Migrator.new(:up, [migration]).migrate + + assert connection.columns(:more_testings).find { |c| c.name == 'created_at' }.null + assert connection.columns(:more_testings).find { |c| c.name == 'updated_at' }.null + ensure + connection.drop_table :more_testings rescue nil + end + + def test_timestamps_have_null_constraints_if_not_present_in_migration_for_adding_timestamps_to_existing_table + migration = Class.new(ActiveRecord::Migration) { + def migrate(x) + add_timestamps :testings + end + }.new + + ActiveRecord::Migrator.new(:up, [migration]).migrate + + assert connection.columns(:testings).find { |c| c.name == 'created_at' }.null + assert connection.columns(:testings).find { |c| c.name == 'updated_at' }.null + end end end end -- cgit v1.2.3 From 7e4b2abeeeae10a734793f284cfec9f9ca4b166e Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sun, 31 Jan 2016 22:51:36 +0900 Subject: Each concrete classes have responsibility to return `association_class` --- activerecord/lib/active_record/reflection.rb | 47 +++++++++++++++------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 320ced5afa..cbb5b99a05 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -483,28 +483,7 @@ module ActiveRecord # Returns +true+ if +self+ is a +has_one+ reflection. def has_one?; false; end - def association_class - case macro - when :belongs_to - if polymorphic? - Associations::BelongsToPolymorphicAssociation - else - Associations::BelongsToAssociation - end - when :has_many - if options[:through] - Associations::HasManyThroughAssociation - else - Associations::HasManyAssociation - end - when :has_one - if options[:through] - Associations::HasOneThroughAssociation - else - Associations::HasOneAssociation - end - end - end + def association_class; raise NotImplementedError; end def polymorphic? options[:polymorphic] @@ -629,6 +608,14 @@ module ActiveRecord def macro; :has_many; end def collection?; true; end + + def association_class + if options[:through] + Associations::HasManyThroughAssociation + else + Associations::HasManyAssociation + end + end end class HasOneReflection < AssociationReflection # :nodoc: @@ -639,6 +626,14 @@ module ActiveRecord def macro; :has_one; end def has_one?; true; end + + def association_class + if options[:through] + Associations::HasOneThroughAssociation + else + Associations::HasOneAssociation + end + end end class BelongsToReflection < AssociationReflection # :nodoc: @@ -650,6 +645,14 @@ module ActiveRecord def belongs_to?; true; end + def association_class + if polymorphic? + Associations::BelongsToPolymorphicAssociation + else + Associations::BelongsToAssociation + end + end + def join_keys(association_klass) key = polymorphic? ? association_primary_key(association_klass) : association_primary_key JoinKeys.new(key, foreign_key) -- cgit v1.2.3 From e77368637e17e6a33db2713f651e85a09456c645 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 1 Feb 2016 01:30:00 +1030 Subject: Switch the default redis adapter to a single-stream model This new adapter does get a little more intimate with the redis-rb gem's implementation than I would like, but it's the least bad of the approaches I've come up with. --- Gemfile | 1 + Gemfile.lock | 1 + .../subscription_adapter/evented_redis.rb | 67 +++++++++ .../lib/action_cable/subscription_adapter/redis.rb | 156 +++++++++++++++++---- .../subscription_adapter/evented_redis_test.rb | 10 ++ .../test/subscription_adapter/redis_test.rb | 8 +- railties/lib/rails/generators/app_base.rb | 1 - railties/test/generators/app_generator_test.rb | 3 - 8 files changed, 212 insertions(+), 35 deletions(-) create mode 100644 actioncable/lib/action_cable/subscription_adapter/evented_redis.rb create mode 100644 actioncable/test/subscription_adapter/evented_redis_test.rb diff --git a/Gemfile b/Gemfile index f8df08ee43..78f9853bed 100644 --- a/Gemfile +++ b/Gemfile @@ -65,6 +65,7 @@ group :cable do gem 'puma', require: false gem 'em-hiredis', require: false + gem 'hiredis', require: false gem 'redis', require: false gem 'faye-websocket', require: false diff --git a/Gemfile.lock b/Gemfile.lock index d2d616b39d..b15d3498a8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -286,6 +286,7 @@ DEPENDENCIES delayed_job_active_record em-hiredis faye-websocket + hiredis jquery-rails json kindlerb (= 0.1.1) diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb new file mode 100644 index 0000000000..d697548cbd --- /dev/null +++ b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb @@ -0,0 +1,67 @@ +require 'thread' + +gem 'em-hiredis', '~> 0.3.0' +gem 'redis', '~> 3.0' +require 'em-hiredis' +require 'redis' + +EventMachine.epoll if EventMachine.epoll? +EventMachine.kqueue if EventMachine.kqueue? + +module ActionCable + module SubscriptionAdapter + class EventedRedis < Base # :nodoc: + @@mutex = Mutex.new + + def initialize(*) + super + @redis_connection_for_broadcasts = @redis_connection_for_subscriptions = nil + end + + def broadcast(channel, payload) + redis_connection_for_broadcasts.publish(channel, payload) + end + + def subscribe(channel, message_callback, success_callback = nil) + redis_connection_for_subscriptions.pubsub.subscribe(channel, &message_callback).tap do |result| + result.callback { |reply| success_callback.call } if success_callback + end + end + + def unsubscribe(channel, message_callback) + redis_connection_for_subscriptions.pubsub.unsubscribe_proc(channel, message_callback) + end + + def shutdown + redis_connection_for_subscriptions.pubsub.close_connection + @redis_connection_for_subscriptions = nil + end + + private + def redis_connection_for_subscriptions + ensure_reactor_running + @redis_connection_for_subscriptions || @server.mutex.synchronize do + @redis_connection_for_subscriptions ||= EM::Hiredis.connect(@server.config.cable[:url]).tap do |redis| + redis.on(:reconnect_failed) do + @logger.info "[ActionCable] Redis reconnect failed." + end + end + end + end + + def redis_connection_for_broadcasts + @redis_connection_for_broadcasts || @server.mutex.synchronize do + @redis_connection_for_broadcasts ||= ::Redis.new(@server.config.cable) + end + end + + def ensure_reactor_running + return if EventMachine.reactor_running? + @@mutex.synchronize do + Thread.new { EventMachine.run } unless EventMachine.reactor_running? + Thread.pass until EventMachine.reactor_running? + end + end + end + end +end diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index 560b79df16..7076383efe 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -1,52 +1,40 @@ require 'thread' -gem 'em-hiredis', '~> 0.3.0' gem 'redis', '~> 3.0' -require 'em-hiredis' require 'redis' -EventMachine.epoll if EventMachine.epoll? -EventMachine.kqueue if EventMachine.kqueue? - module ActionCable module SubscriptionAdapter class Redis < Base # :nodoc: - @@mutex = Mutex.new - def initialize(*) super - @redis_connection_for_broadcasts = @redis_connection_for_subscriptions = nil + @listener = nil + @redis_connection_for_broadcasts = nil end def broadcast(channel, payload) redis_connection_for_broadcasts.publish(channel, payload) end - def subscribe(channel, message_callback, success_callback = nil) - redis_connection_for_subscriptions.pubsub.subscribe(channel, &message_callback).tap do |result| - result.callback { |reply| success_callback.call } if success_callback - end + def subscribe(channel, callback, success_callback = nil) + listener.add_subscriber(channel, callback, success_callback) end - def unsubscribe(channel, message_callback) - redis_connection_for_subscriptions.pubsub.unsubscribe_proc(channel, message_callback) + def unsubscribe(channel, callback) + listener.remove_subscriber(channel, callback) end def shutdown - redis_connection_for_subscriptions.pubsub.close_connection - @redis_connection_for_subscriptions = nil + @listener.shutdown if @listener + end + + def redis_connection_for_subscriptions + ::Redis.new(@server.config.cable) end private - def redis_connection_for_subscriptions - ensure_reactor_running - @redis_connection_for_subscriptions || @server.mutex.synchronize do - @redis_connection_for_subscriptions ||= EM::Hiredis.connect(@server.config.cable[:url]).tap do |redis| - redis.on(:reconnect_failed) do - @logger.info "[ActionCable] Redis reconnect failed." - end - end - end + def listener + @listener || @server.mutex.synchronize { @listener ||= Listener.new(self) } end def redis_connection_for_broadcasts @@ -55,12 +43,120 @@ module ActionCable end end - def ensure_reactor_running - return if EventMachine.reactor_running? - @@mutex.synchronize do - Thread.new { EventMachine.run } unless EventMachine.reactor_running? - Thread.pass until EventMachine.reactor_running? + class Listener < SubscriberMap + def initialize(adapter) + super() + + @adapter = adapter + + @subscribe_callbacks = Hash.new { |h, k| h[k] = [] } + @subscription_lock = Mutex.new + + @raw_client = nil + + @when_connected = [] + + @thread = nil + end + + def listen(conn) + conn.without_reconnect do + original_client = conn.client + + conn.subscribe('_action_cable_internal') do |on| + on.subscribe do |chan, count| + @subscription_lock.synchronize do + if count == 1 + @raw_client = original_client + + until @when_connected.empty? + @when_connected.shift.call + end + end + + if callbacks = @subscribe_callbacks[chan] + next_callback = callbacks.shift + Concurrent.global_io_executor << next_callback if next_callback + @subscribe_callbacks.delete(chan) if callbacks.empty? + end + end + end + + on.message do |chan, message| + broadcast(chan, message) + end + + on.unsubscribe do |chan, count| + if count == 0 + @subscription_lock.synchronize do + @raw_client = nil + end + end + end + end + end + end + + def shutdown + @subscription_lock.synchronize do + return if @thread.nil? + + when_connected do + send_command('unsubscribe') + @raw_client = nil + end + end + + Thread.pass while @thread.alive? + end + + def add_channel(channel, on_success) + @subscription_lock.synchronize do + ensure_listener_running + @subscribe_callbacks[channel] << on_success + when_connected { send_command('subscribe', channel) } + end + end + + def remove_channel(channel) + @subscription_lock.synchronize do + when_connected { send_command('unsubscribe', channel) } + end + end + + def invoke_callback(*) + Concurrent.global_io_executor.post { super } end + + private + def ensure_listener_running + @thread ||= Thread.new do + Thread.current.abort_on_exception = true + + conn = @adapter.redis_connection_for_subscriptions + listen conn + end + end + + def when_connected(&block) + if @raw_client + block.call + else + @when_connected << block + end + end + + def send_command(*command) + @raw_client.write(command) + + very_raw_connection = + @raw_client.connection.instance_variable_defined?(:@connection) && + @raw_client.connection.instance_variable_get(:@connection) + + if very_raw_connection && very_raw_connection.respond_to?(:flush) + very_raw_connection.flush + end + end end end end diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb new file mode 100644 index 0000000000..70333e51bd --- /dev/null +++ b/actioncable/test/subscription_adapter/evented_redis_test.rb @@ -0,0 +1,10 @@ +require 'test_helper' +require_relative './common' + +class EventedRedisAdapterTest < ActionCable::TestCase + include CommonSubscriptionAdapterTest + + def cable_config + { adapter: 'evented_redis', url: 'redis://127.0.0.1:6379/12' } + end +end diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb index 8d52832c87..4f34dd86c9 100644 --- a/actioncable/test/subscription_adapter/redis_test.rb +++ b/actioncable/test/subscription_adapter/redis_test.rb @@ -5,6 +5,12 @@ class RedisAdapterTest < ActionCable::TestCase include CommonSubscriptionAdapterTest def cable_config - { adapter: 'redis', url: 'redis://127.0.0.1:6379/12' } + { adapter: 'redis', driver: 'ruby', url: 'redis://127.0.0.1:6379/12' } + end +end + +class RedisAdapterTest::Hiredis < RedisAdapterTest + def cable_config + super.merge(driver: 'hiredis') end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index c629459d95..51394de824 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -344,7 +344,6 @@ module Rails return [] if options[:skip_action_cable] comment = 'Action Cable dependencies for the Redis adapter' gems = [] - gems << GemfileEntry.new("em-hiredis", '~> 0.3.0', comment) gems << GemfileEntry.new("redis", '~> 3.0', comment) gems end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 53f8ed6af0..49536c068f 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -400,7 +400,6 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_match(/action_cable_meta_tag/, content) end assert_file "Gemfile" do |content| - assert_no_match(/em-hiredis/, content) assert_no_match(/redis/, content) end end @@ -412,14 +411,12 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_file "app/assets/javascripts/cable.coffee" assert_no_file "app/channels" assert_file "Gemfile" do |content| - assert_no_match(/em-hiredis/, content) assert_no_match(/redis/, content) end end def test_action_cable_redis_gems run_generator - assert_gem 'em-hiredis' assert_gem 'redis' end -- cgit v1.2.3 From 4c38319cc25bb248947a089072442e843761e46d Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 1 Feb 2016 03:00:35 +1030 Subject: Wait for EventMachine to finish starting --- actioncable/test/client_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index d7eecfa322..3c79d61569 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -32,6 +32,7 @@ class ClientTest < ActionCable::TestCase server.config.channel_load_paths = [File.expand_path('client', __dir__)] Thread.new { EventMachine.run } unless EventMachine.reactor_running? + Thread.pass until EventMachine.reactor_running? # faye-websocket is warning-rich @previous_verbose, $VERBOSE = $VERBOSE, nil -- cgit v1.2.3 From d6f2000a67cc63aa67414c75ce77de671824ec52 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 1 Feb 2016 04:31:03 +1030 Subject: Wrangle the asset build into something that sounds more general --- actioncable/.gitignore | 2 +- actioncable/Rakefile | 35 ++++++---- .../app/assets/javascripts/action_cable.coffee.erb | 23 ++++++ .../javascripts/action_cable/connection.coffee | 81 ++++++++++++++++++++++ .../action_cable/connection_monitor.coffee | 79 +++++++++++++++++++++ .../javascripts/action_cable/consumer.coffee | 25 +++++++ .../app/assets/javascripts/action_cable/index.js | 1 - .../action_cable/source/connection.coffee | 81 ---------------------- .../action_cable/source/connection_monitor.coffee | 79 --------------------- .../action_cable/source/consumer.coffee | 25 ------- .../action_cable/source/index.coffee.erb | 23 ------ .../action_cable/source/subscription.coffee | 68 ------------------ .../action_cable/source/subscriptions.coffee | 64 ----------------- .../javascripts/action_cable/subscription.coffee | 68 ++++++++++++++++++ .../javascripts/action_cable/subscriptions.coffee | 64 +++++++++++++++++ actionmailer/Rakefile | 3 + actionpack/Rakefile | 3 + actionview/Rakefile | 3 + activejob/Rakefile | 3 + activemodel/Rakefile | 3 + activerecord/Rakefile | 3 + activesupport/Rakefile | 4 ++ railties/Rakefile | 3 + tasks/release.rb | 3 +- 24 files changed, 390 insertions(+), 356 deletions(-) create mode 100644 actioncable/app/assets/javascripts/action_cable.coffee.erb create mode 100644 actioncable/app/assets/javascripts/action_cable/connection.coffee create mode 100644 actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee create mode 100644 actioncable/app/assets/javascripts/action_cable/consumer.coffee delete mode 100644 actioncable/app/assets/javascripts/action_cable/index.js delete mode 100644 actioncable/app/assets/javascripts/action_cable/source/connection.coffee delete mode 100644 actioncable/app/assets/javascripts/action_cable/source/connection_monitor.coffee delete mode 100644 actioncable/app/assets/javascripts/action_cable/source/consumer.coffee delete mode 100644 actioncable/app/assets/javascripts/action_cable/source/index.coffee.erb delete mode 100644 actioncable/app/assets/javascripts/action_cable/source/subscription.coffee delete mode 100644 actioncable/app/assets/javascripts/action_cable/source/subscriptions.coffee create mode 100644 actioncable/app/assets/javascripts/action_cable/subscription.coffee create mode 100644 actioncable/app/assets/javascripts/action_cable/subscriptions.coffee diff --git a/actioncable/.gitignore b/actioncable/.gitignore index 8ded114548..0a04b29786 100644 --- a/actioncable/.gitignore +++ b/actioncable/.gitignore @@ -1,2 +1,2 @@ -/lib/assets/javascripts/action_cable.js +/lib/assets/compiled /tmp diff --git a/actioncable/Rakefile b/actioncable/Rakefile index 0a036e3e3d..1d77fc7067 100644 --- a/actioncable/Rakefile +++ b/actioncable/Rakefile @@ -8,6 +8,9 @@ dir = File.dirname(__FILE__) task :default => :test +task :package => "assets:compile" +task "package:clean" => "assets:clean" + Rake::TestTask.new do |t| t.libs << "test" t.test_files = Dir.glob("#{dir}/test/**/*_test.rb") @@ -17,32 +20,38 @@ Rake::TestTask.new do |t| end namespace :assets do + root_path = Pathname.new(dir) + destination_path = root_path.join("lib/assets/compiled") + desc "Compile dist/action_cable.js" task :compile do puts 'Compiling Action Cable assets...' - asset_mapping = { "source.js" => "action_cable.js" } + precompile_list = %w(action_cable.js) - root_path = Pathname.new(dir) - load_path = root_path.join("app/assets/javascripts/action_cable") - destination_path = root_path.join("lib/assets/javascripts") + environment = Sprockets::Environment.new + environment.gzip = false + Pathname.glob(root_path.join("app/assets/*/")) do |subdir| + environment.append_path subdir + end compile_path = root_path.join("tmp/sprockets") compile_path.rmtree if compile_path.exist? compile_path.mkpath - environment = Sprockets::Environment.new - environment.append_path(load_path) - manifest = Sprockets::Manifest.new(environment.index, compile_path) - manifest.compile(asset_mapping.keys) + manifest.compile(precompile_list) - asset_mapping.each do |logical_path, dist_path| - fingerprint_path = manifest.assets[logical_path] - FileUtils.cp(compile_path.join(fingerprint_path), destination_path.join(dist_path)) + destination_path.rmtree if destination_path.exist? + manifest.assets.each do |path, fingerprint_path| + destination_path.join(path).dirname.mkpath + FileUtils.cp(compile_path.join(fingerprint_path), destination_path.join(path)) end - puts '======' - puts 'Action Cable assets compiled successfully!' + puts 'Done' + end + + task :clean do + destination_path.rmtree if destination_path.exist? end end diff --git a/actioncable/app/assets/javascripts/action_cable.coffee.erb b/actioncable/app/assets/javascripts/action_cable.coffee.erb new file mode 100644 index 0000000000..18a48c0610 --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable.coffee.erb @@ -0,0 +1,23 @@ +#= require_self +#= require ./action_cable/consumer + +@ActionCable = + INTERNAL: <%= ActionCable::INTERNAL.to_json %> + + createConsumer: (url = @getConfig("url")) -> + new ActionCable.Consumer @createWebSocketURL(url) + + getConfig: (name) -> + element = document.head.querySelector("meta[name='action-cable-#{name}']") + element?.getAttribute("content") + + createWebSocketURL: (url) -> + if url and not /^wss?:/i.test(url) + a = document.createElement("a") + a.href = url + # Fix populating Location properties in IE. Otherwise, protocol will be blank. + a.href = a.href + a.protocol = a.protocol.replace("http", "ws") + a.href + else + url diff --git a/actioncable/app/assets/javascripts/action_cable/connection.coffee b/actioncable/app/assets/javascripts/action_cable/connection.coffee new file mode 100644 index 0000000000..fbd7dbd35b --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable/connection.coffee @@ -0,0 +1,81 @@ +# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation. + +{message_types} = ActionCable.INTERNAL + +class ActionCable.Connection + @reopenDelay: 500 + + constructor: (@consumer) -> + @open() + + send: (data) -> + if @isOpen() + @webSocket.send(JSON.stringify(data)) + true + else + false + + open: => + if @webSocket and not @isState("closed") + throw new Error("Existing connection must be closed before opening") + else + @webSocket = new WebSocket(@consumer.url) + @installEventHandlers() + true + + close: -> + @webSocket?.close() + + reopen: -> + if @isState("closed") + @open() + else + try + @close() + finally + setTimeout(@open, @constructor.reopenDelay) + + isOpen: -> + @isState("open") + + # Private + + isState: (states...) -> + @getState() in states + + getState: -> + return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState + null + + installEventHandlers: -> + for eventName of @events + handler = @events[eventName].bind(this) + @webSocket["on#{eventName}"] = handler + return + + events: + message: (event) -> + {identifier, message, type} = JSON.parse(event.data) + + switch type + when message_types.confirmation + @consumer.subscriptions.notify(identifier, "connected") + when message_types.rejection + @consumer.subscriptions.reject(identifier) + else + @consumer.subscriptions.notify(identifier, "received", message) + + open: -> + @disconnected = false + @consumer.subscriptions.reload() + + close: -> + @disconnect() + + error: -> + @disconnect() + + disconnect: -> + return if @disconnected + @disconnected = true + @consumer.subscriptions.notifyAll("disconnected") diff --git a/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee b/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee new file mode 100644 index 0000000000..99b9a1c6d5 --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee @@ -0,0 +1,79 @@ +# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting +# revival reconnections if things go astray. Internal class, not intended for direct user manipulation. +class ActionCable.ConnectionMonitor + @pollInterval: + min: 3 + max: 30 + + @staleThreshold: 6 # Server::Connections::BEAT_INTERVAL * 2 (missed two pings) + + identifier: ActionCable.INTERNAL.identifiers.ping + + constructor: (@consumer) -> + @consumer.subscriptions.add(this) + @start() + + connected: -> + @reset() + @pingedAt = now() + delete @disconnectedAt + + disconnected: -> + @disconnectedAt = now() + + received: -> + @pingedAt = now() + + reset: -> + @reconnectAttempts = 0 + + start: -> + @reset() + delete @stoppedAt + @startedAt = now() + @poll() + document.addEventListener("visibilitychange", @visibilityDidChange) + + stop: -> + @stoppedAt = now() + document.removeEventListener("visibilitychange", @visibilityDidChange) + + poll: -> + setTimeout => + unless @stoppedAt + @reconnectIfStale() + @poll() + , @getInterval() + + getInterval: -> + {min, max} = @constructor.pollInterval + interval = 5 * Math.log(@reconnectAttempts + 1) + clamp(interval, min, max) * 1000 + + reconnectIfStale: -> + if @connectionIsStale() + @reconnectAttempts++ + unless @disconnectedRecently() + @consumer.connection.reopen() + + connectionIsStale: -> + secondsSince(@pingedAt ? @startedAt) > @constructor.staleThreshold + + disconnectedRecently: -> + @disconnectedAt and secondsSince(@disconnectedAt) < @constructor.staleThreshold + + visibilityDidChange: => + if document.visibilityState is "visible" + setTimeout => + if @connectionIsStale() or not @consumer.connection.isOpen() + @consumer.connection.reopen() + , 200 + + now = -> + new Date().getTime() + + secondsSince = (time) -> + (now() - time) / 1000 + + clamp = (number, min, max) -> + Math.max(min, Math.min(max, number)) diff --git a/actioncable/app/assets/javascripts/action_cable/consumer.coffee b/actioncable/app/assets/javascripts/action_cable/consumer.coffee new file mode 100644 index 0000000000..717c0641a9 --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable/consumer.coffee @@ -0,0 +1,25 @@ +#= require ./connection +#= require ./connection_monitor +#= require ./subscriptions +#= require ./subscription + +# The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established, +# the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates. +# The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription +# method. +# +# The following example shows how this can be setup: +# +# @App = {} +# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1" +# App.appearance = App.cable.subscriptions.create "AppearanceChannel" +# +# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. +class ActionCable.Consumer + constructor: (@url) -> + @subscriptions = new ActionCable.Subscriptions this + @connection = new ActionCable.Connection this + @connectionMonitor = new ActionCable.ConnectionMonitor this + + send: (data) -> + @connection.send(data) diff --git a/actioncable/app/assets/javascripts/action_cable/index.js b/actioncable/app/assets/javascripts/action_cable/index.js deleted file mode 100644 index e97870c3b0..0000000000 --- a/actioncable/app/assets/javascripts/action_cable/index.js +++ /dev/null @@ -1 +0,0 @@ -//= require_tree ./source diff --git a/actioncable/app/assets/javascripts/action_cable/source/connection.coffee b/actioncable/app/assets/javascripts/action_cable/source/connection.coffee deleted file mode 100644 index fbd7dbd35b..0000000000 --- a/actioncable/app/assets/javascripts/action_cable/source/connection.coffee +++ /dev/null @@ -1,81 +0,0 @@ -# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation. - -{message_types} = ActionCable.INTERNAL - -class ActionCable.Connection - @reopenDelay: 500 - - constructor: (@consumer) -> - @open() - - send: (data) -> - if @isOpen() - @webSocket.send(JSON.stringify(data)) - true - else - false - - open: => - if @webSocket and not @isState("closed") - throw new Error("Existing connection must be closed before opening") - else - @webSocket = new WebSocket(@consumer.url) - @installEventHandlers() - true - - close: -> - @webSocket?.close() - - reopen: -> - if @isState("closed") - @open() - else - try - @close() - finally - setTimeout(@open, @constructor.reopenDelay) - - isOpen: -> - @isState("open") - - # Private - - isState: (states...) -> - @getState() in states - - getState: -> - return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState - null - - installEventHandlers: -> - for eventName of @events - handler = @events[eventName].bind(this) - @webSocket["on#{eventName}"] = handler - return - - events: - message: (event) -> - {identifier, message, type} = JSON.parse(event.data) - - switch type - when message_types.confirmation - @consumer.subscriptions.notify(identifier, "connected") - when message_types.rejection - @consumer.subscriptions.reject(identifier) - else - @consumer.subscriptions.notify(identifier, "received", message) - - open: -> - @disconnected = false - @consumer.subscriptions.reload() - - close: -> - @disconnect() - - error: -> - @disconnect() - - disconnect: -> - return if @disconnected - @disconnected = true - @consumer.subscriptions.notifyAll("disconnected") diff --git a/actioncable/app/assets/javascripts/action_cable/source/connection_monitor.coffee b/actioncable/app/assets/javascripts/action_cable/source/connection_monitor.coffee deleted file mode 100644 index 99b9a1c6d5..0000000000 --- a/actioncable/app/assets/javascripts/action_cable/source/connection_monitor.coffee +++ /dev/null @@ -1,79 +0,0 @@ -# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting -# revival reconnections if things go astray. Internal class, not intended for direct user manipulation. -class ActionCable.ConnectionMonitor - @pollInterval: - min: 3 - max: 30 - - @staleThreshold: 6 # Server::Connections::BEAT_INTERVAL * 2 (missed two pings) - - identifier: ActionCable.INTERNAL.identifiers.ping - - constructor: (@consumer) -> - @consumer.subscriptions.add(this) - @start() - - connected: -> - @reset() - @pingedAt = now() - delete @disconnectedAt - - disconnected: -> - @disconnectedAt = now() - - received: -> - @pingedAt = now() - - reset: -> - @reconnectAttempts = 0 - - start: -> - @reset() - delete @stoppedAt - @startedAt = now() - @poll() - document.addEventListener("visibilitychange", @visibilityDidChange) - - stop: -> - @stoppedAt = now() - document.removeEventListener("visibilitychange", @visibilityDidChange) - - poll: -> - setTimeout => - unless @stoppedAt - @reconnectIfStale() - @poll() - , @getInterval() - - getInterval: -> - {min, max} = @constructor.pollInterval - interval = 5 * Math.log(@reconnectAttempts + 1) - clamp(interval, min, max) * 1000 - - reconnectIfStale: -> - if @connectionIsStale() - @reconnectAttempts++ - unless @disconnectedRecently() - @consumer.connection.reopen() - - connectionIsStale: -> - secondsSince(@pingedAt ? @startedAt) > @constructor.staleThreshold - - disconnectedRecently: -> - @disconnectedAt and secondsSince(@disconnectedAt) < @constructor.staleThreshold - - visibilityDidChange: => - if document.visibilityState is "visible" - setTimeout => - if @connectionIsStale() or not @consumer.connection.isOpen() - @consumer.connection.reopen() - , 200 - - now = -> - new Date().getTime() - - secondsSince = (time) -> - (now() - time) / 1000 - - clamp = (number, min, max) -> - Math.max(min, Math.min(max, number)) diff --git a/actioncable/app/assets/javascripts/action_cable/source/consumer.coffee b/actioncable/app/assets/javascripts/action_cable/source/consumer.coffee deleted file mode 100644 index 717c0641a9..0000000000 --- a/actioncable/app/assets/javascripts/action_cable/source/consumer.coffee +++ /dev/null @@ -1,25 +0,0 @@ -#= require ./connection -#= require ./connection_monitor -#= require ./subscriptions -#= require ./subscription - -# The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established, -# the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates. -# The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription -# method. -# -# The following example shows how this can be setup: -# -# @App = {} -# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1" -# App.appearance = App.cable.subscriptions.create "AppearanceChannel" -# -# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. -class ActionCable.Consumer - constructor: (@url) -> - @subscriptions = new ActionCable.Subscriptions this - @connection = new ActionCable.Connection this - @connectionMonitor = new ActionCable.ConnectionMonitor this - - send: (data) -> - @connection.send(data) diff --git a/actioncable/app/assets/javascripts/action_cable/source/index.coffee.erb b/actioncable/app/assets/javascripts/action_cable/source/index.coffee.erb deleted file mode 100644 index f4615b7502..0000000000 --- a/actioncable/app/assets/javascripts/action_cable/source/index.coffee.erb +++ /dev/null @@ -1,23 +0,0 @@ -#= require_self -#= require ./consumer - -@ActionCable = - INTERNAL: <%= ActionCable::INTERNAL.to_json %> - - createConsumer: (url = @getConfig("url")) -> - new ActionCable.Consumer @createWebSocketURL(url) - - getConfig: (name) -> - element = document.head.querySelector("meta[name='action-cable-#{name}']") - element?.getAttribute("content") - - createWebSocketURL: (url) -> - if url and not /^wss?:/i.test(url) - a = document.createElement("a") - a.href = url - # Fix populating Location properties in IE. Otherwise, protocol will be blank. - a.href = a.href - a.protocol = a.protocol.replace("http", "ws") - a.href - else - url diff --git a/actioncable/app/assets/javascripts/action_cable/source/subscription.coffee b/actioncable/app/assets/javascripts/action_cable/source/subscription.coffee deleted file mode 100644 index 339d676933..0000000000 --- a/actioncable/app/assets/javascripts/action_cable/source/subscription.coffee +++ /dev/null @@ -1,68 +0,0 @@ -# A new subscription is created through the ActionCable.Subscriptions instance available on the consumer. -# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding -# Channel instance on the server side. -# -# An example demonstrates the basic functionality: -# -# App.appearance = App.cable.subscriptions.create "AppearanceChannel", -# connected: -> -# # Called once the subscription has been successfully completed -# -# appear: -> -# @perform 'appear', appearing_on: @appearingOn() -# -# away: -> -# @perform 'away' -# -# appearingOn: -> -# $('main').data 'appearing-on' -# -# The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server -# by calling the `@perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away). -# The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter. -# -# This is how the server component would look: -# -# class AppearanceChannel < ApplicationActionCable::Channel -# def subscribed -# current_user.appear -# end -# -# def unsubscribed -# current_user.disappear -# end -# -# def appear(data) -# current_user.appear on: data['appearing_on'] -# end -# -# def away -# current_user.away -# end -# end -# -# The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name. -# The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method. -class ActionCable.Subscription - constructor: (@subscriptions, params = {}, mixin) -> - @identifier = JSON.stringify(params) - extend(this, mixin) - @subscriptions.add(this) - @consumer = @subscriptions.consumer - - # Perform a channel action with the optional data passed as an attribute - perform: (action, data = {}) -> - data.action = action - @send(data) - - send: (data) -> - @consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) - - unsubscribe: -> - @subscriptions.remove(this) - - extend = (object, properties) -> - if properties? - for key, value of properties - object[key] = value - object diff --git a/actioncable/app/assets/javascripts/action_cable/source/subscriptions.coffee b/actioncable/app/assets/javascripts/action_cable/source/subscriptions.coffee deleted file mode 100644 index ae041ffa2b..0000000000 --- a/actioncable/app/assets/javascripts/action_cable/source/subscriptions.coffee +++ /dev/null @@ -1,64 +0,0 @@ -# Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user -# us ActionCable.Subscriptions#create, and it should be called through the consumer like so: -# -# @App = {} -# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1" -# App.appearance = App.cable.subscriptions.create "AppearanceChannel" -# -# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. -class ActionCable.Subscriptions - constructor: (@consumer) -> - @subscriptions = [] - - create: (channelName, mixin) -> - channel = channelName - params = if typeof channel is "object" then channel else {channel} - new ActionCable.Subscription this, params, mixin - - # Private - - add: (subscription) -> - @subscriptions.push(subscription) - @notify(subscription, "initialized") - @sendCommand(subscription, "subscribe") - - remove: (subscription) -> - @forget(subscription) - - unless @findAll(subscription.identifier).length - @sendCommand(subscription, "unsubscribe") - - reject: (identifier) -> - for subscription in @findAll(identifier) - @forget(subscription) - @notify(subscription, "rejected") - - forget: (subscription) -> - @subscriptions = (s for s in @subscriptions when s isnt subscription) - - findAll: (identifier) -> - s for s in @subscriptions when s.identifier is identifier - - reload: -> - for subscription in @subscriptions - @sendCommand(subscription, "subscribe") - - notifyAll: (callbackName, args...) -> - for subscription in @subscriptions - @notify(subscription, callbackName, args...) - - notify: (subscription, callbackName, args...) -> - if typeof subscription is "string" - subscriptions = @findAll(subscription) - else - subscriptions = [subscription] - - for subscription in subscriptions - subscription[callbackName]?(args...) - - sendCommand: (subscription, command) -> - {identifier} = subscription - if identifier is ActionCable.INTERNAL.identifiers.ping - @consumer.connection.isOpen() - else - @consumer.send({command, identifier}) diff --git a/actioncable/app/assets/javascripts/action_cable/subscription.coffee b/actioncable/app/assets/javascripts/action_cable/subscription.coffee new file mode 100644 index 0000000000..339d676933 --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable/subscription.coffee @@ -0,0 +1,68 @@ +# A new subscription is created through the ActionCable.Subscriptions instance available on the consumer. +# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding +# Channel instance on the server side. +# +# An example demonstrates the basic functionality: +# +# App.appearance = App.cable.subscriptions.create "AppearanceChannel", +# connected: -> +# # Called once the subscription has been successfully completed +# +# appear: -> +# @perform 'appear', appearing_on: @appearingOn() +# +# away: -> +# @perform 'away' +# +# appearingOn: -> +# $('main').data 'appearing-on' +# +# The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server +# by calling the `@perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away). +# The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter. +# +# This is how the server component would look: +# +# class AppearanceChannel < ApplicationActionCable::Channel +# def subscribed +# current_user.appear +# end +# +# def unsubscribed +# current_user.disappear +# end +# +# def appear(data) +# current_user.appear on: data['appearing_on'] +# end +# +# def away +# current_user.away +# end +# end +# +# The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name. +# The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method. +class ActionCable.Subscription + constructor: (@subscriptions, params = {}, mixin) -> + @identifier = JSON.stringify(params) + extend(this, mixin) + @subscriptions.add(this) + @consumer = @subscriptions.consumer + + # Perform a channel action with the optional data passed as an attribute + perform: (action, data = {}) -> + data.action = action + @send(data) + + send: (data) -> + @consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) + + unsubscribe: -> + @subscriptions.remove(this) + + extend = (object, properties) -> + if properties? + for key, value of properties + object[key] = value + object diff --git a/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee b/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee new file mode 100644 index 0000000000..ae041ffa2b --- /dev/null +++ b/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee @@ -0,0 +1,64 @@ +# Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user +# us ActionCable.Subscriptions#create, and it should be called through the consumer like so: +# +# @App = {} +# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1" +# App.appearance = App.cable.subscriptions.create "AppearanceChannel" +# +# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription. +class ActionCable.Subscriptions + constructor: (@consumer) -> + @subscriptions = [] + + create: (channelName, mixin) -> + channel = channelName + params = if typeof channel is "object" then channel else {channel} + new ActionCable.Subscription this, params, mixin + + # Private + + add: (subscription) -> + @subscriptions.push(subscription) + @notify(subscription, "initialized") + @sendCommand(subscription, "subscribe") + + remove: (subscription) -> + @forget(subscription) + + unless @findAll(subscription.identifier).length + @sendCommand(subscription, "unsubscribe") + + reject: (identifier) -> + for subscription in @findAll(identifier) + @forget(subscription) + @notify(subscription, "rejected") + + forget: (subscription) -> + @subscriptions = (s for s in @subscriptions when s isnt subscription) + + findAll: (identifier) -> + s for s in @subscriptions when s.identifier is identifier + + reload: -> + for subscription in @subscriptions + @sendCommand(subscription, "subscribe") + + notifyAll: (callbackName, args...) -> + for subscription in @subscriptions + @notify(subscription, callbackName, args...) + + notify: (subscription, callbackName, args...) -> + if typeof subscription is "string" + subscriptions = @findAll(subscription) + else + subscriptions = [subscription] + + for subscription in subscriptions + subscription[callbackName]?(args...) + + sendCommand: (subscription, command) -> + {identifier} = subscription + if identifier is ActionCable.INTERNAL.identifiers.ping + @consumer.connection.isOpen() + else + @consumer.send({command, identifier}) diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index 7197ea5e27..54e6cff48d 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -3,6 +3,9 @@ require 'rake/testtask' desc "Default Task" task default: [ :test ] +task :package +task "package:clean" + # Run the unit tests Rake::TestTask.new { |t| t.libs << "test" diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 601263bfac..37a269cffd 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -5,6 +5,9 @@ test_files = Dir.glob('test/**/*_test.rb') desc "Default Task" task :default => :test +task :package +task "package:clean" + # Run the unit tests Rake::TestTask.new do |t| t.libs << 'test' diff --git a/actionview/Rakefile b/actionview/Rakefile index 93be50721d..d41030c650 100644 --- a/actionview/Rakefile +++ b/actionview/Rakefile @@ -3,6 +3,9 @@ require 'rake/testtask' desc "Default Task" task :default => :test +task :package +task "package:clean" + # Run the unit tests desc "Run all unit tests" diff --git a/activejob/Rakefile b/activejob/Rakefile index d9648a7f16..2a853b4b6b 100644 --- a/activejob/Rakefile +++ b/activejob/Rakefile @@ -6,6 +6,9 @@ ACTIVEJOB_ADAPTERS -= %w(queue_classic) if defined?(JRUBY_VERSION) task default: :test task test: 'test:default' +task :package +task "package:clean" + namespace :test do desc 'Run all adapter tests' task :default do diff --git a/activemodel/Rakefile b/activemodel/Rakefile index 5a67f0a151..9982d49bcb 100644 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -4,6 +4,9 @@ dir = File.dirname(__FILE__) task :default => :test +task :package +task "package:clean" + Rake::TestTask.new do |t| t.libs << "test" t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb") diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 0564dca94a..46df733cfe 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -20,6 +20,9 @@ end desc 'Run mysql2, sqlite, and postgresql tests by default' task :default => :test +task :package +task "package:clean" + desc 'Run mysql2, sqlite, and postgresql tests' task :test do tasks = defined?(JRUBY_VERSION) ? diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 81c242d4b1..33ee62aa1b 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -1,6 +1,10 @@ require 'rake/testtask' task :default => :test + +task :package +task "package:clean" + Rake::TestTask.new do |t| t.libs << 'test' t.pattern = 'test/**/*_test.rb' diff --git a/railties/Rakefile b/railties/Rakefile index cf130a5f14..3421d9b464 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -2,6 +2,9 @@ require 'rake/testtask' task :default => :test +task :package +task "package:clean" + desc "Run all unit tests" task :test => 'test:isolated' diff --git a/tasks/release.rb b/tasks/release.rb index 25ba91cb49..de9c51a140 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -13,6 +13,7 @@ directory "pkg" task :clean do rm_f gem + sh "cd #{framework} && bundle exec rake package:clean" unless framework == "rails" end task :update_versions do @@ -48,7 +49,7 @@ directory "pkg" task gem => %w(update_versions pkg) do cmd = "" cmd << "cd #{framework} && " unless framework == "rails" - cmd << "bundle exec rake assets:compile && " if framework == "actioncable" + cmd << "bundle exec rake package && " unless framework == "rails" cmd << "gem build #{gemspec} && mv #{framework}-#{version}.gem #{root}/pkg/" sh cmd end -- cgit v1.2.3 From 93abf58787396661230f31c7a2f58c18f30dbec9 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 1 Feb 2016 05:14:02 +1030 Subject: Drop the runtime dependency on coffee-rails --- actioncable/actioncable.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec index 0976895ef7..8d35d819cf 100644 --- a/actioncable/actioncable.gemspec +++ b/actioncable/actioncable.gemspec @@ -20,7 +20,6 @@ Gem::Specification.new do |s| s.add_dependency 'actionpack', version - s.add_dependency 'coffee-rails', '~> 4.1.0' s.add_dependency 'nio4r', '~> 1.2' s.add_dependency 'websocket-driver', '~> 0.6.1' -- cgit v1.2.3 From 082a0b5b828fd537d328f76bb69dd82148b85222 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 1 Feb 2016 05:19:44 +1030 Subject: Use the in-process subscription adapter for development & test --- railties/lib/rails/generators/rails/app/templates/config/cable.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/railties/lib/rails/generators/rails/app/templates/config/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/cable.yml index 004adb7b3c..aa4e832748 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/cable.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/cable.yml @@ -4,9 +4,7 @@ production: url: redis://localhost:6379/1 development: - adapter: redis - url: redis://localhost:6379/2 + adapter: async test: - adapter: redis - url: redis://localhost:6379/3 + adapter: async -- cgit v1.2.3 From 6cdc36a026ef4a4c66c1c8dfcf319b99993322d3 Mon Sep 17 00:00:00 2001 From: Bart de Water Date: Wed, 30 Dec 2015 20:54:36 +0100 Subject: Update middleware docs regarding ActionDispatch::LoadInterlock [ci skip] --- guides/source/api_app.md | 5 +---- guides/source/command_line.md | 2 +- guides/source/configuring.md | 2 +- guides/source/rails_on_rack.md | 10 +++++++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 86baa9ee84..70810d450a 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -192,7 +192,7 @@ An API application comes with the following middlewares by default: - `Rack::Sendfile` - `ActionDispatch::Static` -- `Rack::Lock` +- `ActionDispatch::LoadInterlock` - `ActiveSupport::Cache::Strategy::LocalCache::Middleware` - `ActionDispatch::RequestId` - `Rails::Rack::Logger` @@ -262,9 +262,6 @@ subsequent inbound requests for the same URL. Think of it as page caching using HTTP semantics. -NOTE: This middleware is always outside of the `Rack::Lock` mutex, even in -single-threaded applications. - ### Using Rack::Sendfile When you use the `send_file` method inside a Rails controller, it sets the diff --git a/guides/source/command_line.md b/guides/source/command_line.md index eb9bf3fa18..cd5e4e6484 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -412,7 +412,7 @@ Ruby version 2.2.2 (x86_64-linux) RubyGems version 2.4.6 Rack version 1.6 JavaScript Runtime Node.js (V8) -Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag +Middleware Rack::Sendfile, ActionDispatch::Static, ActionDispatch::LoadInterlock, #, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag Application root /home/foobar/commandsapp Environment development Database adapter sqlite3 diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 14ba343520..9db1f3415b 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -200,7 +200,7 @@ Every Rails application comes with a standard set of middleware which it uses in * `ActionDispatch::SSL` forces every request to be served using HTTPS. Enabled if `config.force_ssl` is set to `true`. Options passed to this can be configured by setting `config.ssl_options`. * `ActionDispatch::Static` is used to serve static assets. Disabled if `config.public_file_server.enabled` is `false`. Set `config.public_file_server.index_name` if you need to serve a static directory index file that is not named `index`. For example, to serve `main.html` instead of `index.html` for directory requests, set `config.public_file_server.index_name` to `"main"`. -* `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`. +* `ActionDispatch::LoadInterlock` allows thread safe code reloading. Disabled if `config.allow_concurrency` is `false`, which causes `Rack::Lock` to be loaded. `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. * `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread. * `Rack::Runtime` sets an `X-Runtime` header, containing the time (in seconds) taken to execute the request. * `Rails::Rack::Logger` notifies the logs that the request has begun. After request is complete, flushes all the logs. diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index 273fbc08e2..cd08b9a586 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -104,7 +104,7 @@ For a freshly generated Rails application, this might produce something like: ```ruby use Rack::Sendfile use ActionDispatch::Static -use Rack::Lock +use ActionDispatch::LoadInterlock use # use Rack::Runtime use Rack::MethodOverride @@ -171,10 +171,10 @@ Add the following lines to your application configuration: ```ruby # config/application.rb -config.middleware.delete Rack::Lock +config.middleware.delete Rack::Runtime ``` -And now if you inspect the middleware stack, you'll find that `Rack::Lock` is +And now if you inspect the middleware stack, you'll find that `Rack::Runtime` is not a part of it. ```bash @@ -219,6 +219,10 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Sets `env["rack.multithread"]` flag to `false` and wraps the application within a Mutex. +**`ActionDispatch::LoadInterlock`** + +* Used for thread safe code reloading during development. + **`ActiveSupport::Cache::Strategy::LocalCache::Middleware`** * Used for memory caching. This cache is not thread safe. -- cgit v1.2.3 From ddd84f6193ee44cdf33db60db469463940ea76cd Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Sat, 30 Jan 2016 21:39:05 -0500 Subject: Remove unused method --- actioncable/test/client_test.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index d7eecfa322..28086337c3 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -145,15 +145,6 @@ class ClientTest < ActionCable::TestCase @ws.close @closed.wait(WAIT_WHEN_EXPECTING_EVENT) end - - def close! - sock = BasicSocket.for_fd(@ws.instance_variable_get(:@stream).detach) - - # Force a TCP reset - sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack('ii')) - - sock.close - end end def faye_client(port) -- cgit v1.2.3 From 8d25a8aa8c1f67603ad9aaff397bca729afeed1e Mon Sep 17 00:00:00 2001 From: Tony Miller Date: Mon, 1 Feb 2016 06:29:00 +0900 Subject: clarify the touch true option does not trigger after_save/update [ci skip] I've gotten tripped up more than a few times on this, thinking that using `belongs_to` with `touch: true` would trigger my after_save or after_update callbacks. The same text is in the documentation for the touch method itself, but I think its helpful to repeat it again here. It might save people some time. --- activerecord/lib/active_record/associations.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 70f29debbc..03c8b4c97b 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1591,6 +1591,8 @@ module ActiveRecord # If true, the associated object will be touched (the updated_at/on attributes set to current time) # when this record is either saved or destroyed. If you specify a symbol, that attribute # will be updated with the current time in addition to the updated_at/on attribute. + # Please note that with touching no validation is performed and only the +after_touch+, + # +after_commit+ and +after_rollback+ callbacks are executed. # [:inverse_of] # Specifies the name of the #has_one or #has_many association on the associated # object that is the inverse of this #belongs_to association. Does not work in -- cgit v1.2.3 From 615dcadba0c18e332a497c515b66215c446dfe1d Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sun, 31 Jan 2016 23:07:27 +0900 Subject: Remove `case macro` from `calculate_constructable` Rails has abstract Reflection classes (`MacroReflection`, `AssociationReflection` etc.) and concrete Reflection classes (e.g. `HasManyReflection`, `HasOneReflection` etc.). In many case `calculate_constructable` returns `true`, so change `calculate_constructable` to always return `true` and override this method if necessary. --- activerecord/lib/active_record/reflection.rb | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index cbb5b99a05..823ca1f54f 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -501,14 +501,7 @@ module ActiveRecord private def calculate_constructable(macro, options) - case macro - when :belongs_to - !polymorphic? - when :has_one - !options[:through] - else - true - end + true end # Attempts to find the inverse association name automatically. @@ -634,6 +627,12 @@ module ActiveRecord Associations::HasOneAssociation end end + + private + + def calculate_constructable(macro, options) + !options[:through] + end end class BelongsToReflection < AssociationReflection # :nodoc: @@ -661,6 +660,12 @@ module ActiveRecord def join_id_for(owner) # :nodoc: owner[foreign_key] end + + private + + def calculate_constructable(macro, options) + !polymorphic? + end end class HasAndBelongsToManyReflection < AssociationReflection # :nodoc: -- cgit v1.2.3 From ad9ed7e36cee575011604b7d5558ee4f49ed55a9 Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Mon, 1 Feb 2016 06:18:04 +0530 Subject: Update Gemfile.lock - Leftover from https://github.com/rails/rails/commit/93abf58787396661230f31c7a2f58c18f30dbec9. --- Gemfile.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d2d616b39d..1da3926dd0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -31,7 +31,6 @@ PATH specs: actioncable (5.0.0.beta1.1) actionpack (= 5.0.0.beta1.1) - coffee-rails (~> 4.1.0) nio4r (~> 1.2) websocket-driver (~> 0.6.1) actionmailer (5.0.0.beta1.1) -- cgit v1.2.3 From 87e2f7e336b0576f2628cf56dc19655dc1c3eebc Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Mon, 1 Feb 2016 10:33:56 +0900 Subject: :speak_no_evil: :warning: --- .../test/cases/adapters/postgresql/postgresql_adapter_test.rb | 4 +++- activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index f1995b243a..e8daedac53 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -55,7 +55,9 @@ module ActiveRecord def test_composite_primary_key with_example_table 'id serial, number serial, PRIMARY KEY (id, number)' do - assert_nil @connection.primary_key('ex') + silence_warnings do + assert_nil @connection.primary_key('ex') + end end end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 038d9e2d0f..4cb2f02fe2 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -402,7 +402,9 @@ module ActiveRecord def test_composite_primary_key with_example_table 'id integer, number integer, foo integer, PRIMARY KEY (id, number)' do - assert_nil @conn.primary_key('ex') + silence_warnings do + assert_nil @conn.primary_key('ex') + end end end -- cgit v1.2.3 From 351e39c27a882ccd1269265b757b960b0dcab3c3 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 1 Feb 2016 11:12:56 +0900 Subject: Extract `ExplainPrettyPrinter` to appropriate files --- .../connection_adapters/abstract_mysql_adapter.rb | 68 +-------------------- .../mysql/explain_pretty_printer.rb | 70 ++++++++++++++++++++++ .../postgresql/database_statements.rb | 39 +----------- .../postgresql/explain_pretty_printer.rb | 42 +++++++++++++ .../connection_adapters/postgresql_adapter.rb | 1 + .../sqlite3/explain_pretty_printer.rb | 19 ++++++ .../connection_adapters/sqlite3_adapter.rb | 17 +----- 7 files changed, 137 insertions(+), 119 deletions(-) create mode 100644 activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb create mode 100644 activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb create mode 100644 activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb 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 db09170d33..4dc7aa0c22 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -1,5 +1,6 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_record/connection_adapters/mysql/column' +require 'active_record/connection_adapters/mysql/explain_pretty_printer' require 'active_record/connection_adapters/mysql/schema_creation' require 'active_record/connection_adapters/mysql/schema_definitions' require 'active_record/connection_adapters/mysql/schema_dumper' @@ -232,72 +233,7 @@ module ActiveRecord result = exec_query(sql, 'EXPLAIN', binds) elapsed = Time.now - start - ExplainPrettyPrinter.new.pp(result, elapsed) - end - - class ExplainPrettyPrinter # :nodoc: - # Pretty prints the result of an EXPLAIN in a way that resembles the output of the - # MySQL shell: - # - # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ - # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | - # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ - # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | - # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | - # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ - # 2 rows in set (0.00 sec) - # - # This is an exercise in Ruby hyperrealism :). - def pp(result, elapsed) - widths = compute_column_widths(result) - separator = build_separator(widths) - - pp = [] - - pp << separator - pp << build_cells(result.columns, widths) - pp << separator - - result.rows.each do |row| - pp << build_cells(row, widths) - end - - pp << separator - pp << build_footer(result.rows.length, elapsed) - - pp.join("\n") + "\n" - end - - private - - def compute_column_widths(result) - [].tap do |widths| - result.columns.each_with_index do |column, i| - cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s} - widths << cells_in_column.map(&:length).max - end - end - end - - def build_separator(widths) - padding = 1 - '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+' - end - - def build_cells(items, widths) - cells = [] - items.each_with_index do |item, i| - item = 'NULL' if item.nil? - justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust' - cells << item.to_s.send(justifier, widths[i]) - end - '| ' + cells.join(' | ') + ' |' - end - - def build_footer(nrows, elapsed) - rows_label = nrows == 1 ? 'row' : 'rows' - "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed - end + MySQL::ExplainPrettyPrinter.new.pp(result, elapsed) end def clear_cache! diff --git a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb new file mode 100644 index 0000000000..1820853196 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb @@ -0,0 +1,70 @@ +module ActiveRecord + module ConnectionAdapters + module MySQL + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of an EXPLAIN in a way that resembles the output of the + # MySQL shell: + # + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | + # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # 2 rows in set (0.00 sec) + # + # This is an exercise in Ruby hyperrealism :). + def pp(result, elapsed) + widths = compute_column_widths(result) + separator = build_separator(widths) + + pp = [] + + pp << separator + pp << build_cells(result.columns, widths) + pp << separator + + result.rows.each do |row| + pp << build_cells(row, widths) + end + + pp << separator + pp << build_footer(result.rows.length, elapsed) + + pp.join("\n") + "\n" + end + + private + + def compute_column_widths(result) + [].tap do |widths| + result.columns.each_with_index do |column, i| + cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s} + widths << cells_in_column.map(&:length).max + end + end + end + + def build_separator(widths) + padding = 1 + '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+' + end + + def build_cells(items, widths) + cells = [] + items.each_with_index do |item, i| + item = 'NULL' if item.nil? + justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust' + cells << item.to_s.send(justifier, widths[i]) + end + '| ' + cells.join(' | ') + ' |' + end + + def build_footer(nrows, elapsed) + rows_label = nrows == 1 ? 'row' : 'rows' + "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed + end + end + end + end +end 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 8c7cfae7c1..6aa264d766 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -4,44 +4,7 @@ module ActiveRecord module DatabaseStatements def explain(arel, binds = []) sql = "EXPLAIN #{to_sql(arel, binds)}" - ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds)) - end - - class ExplainPrettyPrinter # :nodoc: - # Pretty prints the result of an EXPLAIN in a way that resembles the output of the - # PostgreSQL shell: - # - # QUERY PLAN - # ------------------------------------------------------------------------------ - # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0) - # Join Filter: (posts.user_id = users.id) - # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4) - # Index Cond: (id = 1) - # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4) - # Filter: (posts.user_id = 1) - # (6 rows) - # - def pp(result) - header = result.columns.first - lines = result.rows.map(&:first) - - # We add 2 because there's one char of padding at both sides, note - # the extra hyphens in the example above. - width = [header, *lines].map(&:length).max + 2 - - pp = [] - - pp << header.center(width).rstrip - pp << '-' * width - - pp += lines.map {|line| " #{line}"} - - nrows = result.rows.length - rows_label = nrows == 1 ? 'row' : 'rows' - pp << "(#{nrows} #{rows_label})" - - pp.join("\n") + "\n" - end + PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds)) end def select_value(arel, name = nil, binds = []) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb new file mode 100644 index 0000000000..789b88912c --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb @@ -0,0 +1,42 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of an EXPLAIN in a way that resembles the output of the + # PostgreSQL shell: + # + # QUERY PLAN + # ------------------------------------------------------------------------------ + # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0) + # Join Filter: (posts.user_id = users.id) + # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4) + # Index Cond: (id = 1) + # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4) + # Filter: (posts.user_id = 1) + # (6 rows) + # + def pp(result) + header = result.columns.first + lines = result.rows.map(&:first) + + # We add 2 because there's one char of padding at both sides, note + # the extra hyphens in the example above. + width = [header, *lines].map(&:length).max + 2 + + pp = [] + + pp << header.center(width).rstrip + pp << '-' * width + + pp += lines.map {|line| " #{line}"} + + nrows = result.rows.length + rows_label = nrows == 1 ? 'row' : 'rows' + pp << "(#{nrows} #{rows_label})" + + pp.join("\n") + "\n" + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 820ee3565c..d69c2e186b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -5,6 +5,7 @@ require 'pg' require "active_record/connection_adapters/abstract_adapter" require "active_record/connection_adapters/postgresql/column" require "active_record/connection_adapters/postgresql/database_statements" +require "active_record/connection_adapters/postgresql/explain_pretty_printer" require "active_record/connection_adapters/postgresql/oid" require "active_record/connection_adapters/postgresql/quoting" require "active_record/connection_adapters/postgresql/referential_integrity" diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb new file mode 100644 index 0000000000..a946f5ebd0 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb @@ -0,0 +1,19 @@ +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles + # the output of the SQLite shell: + # + # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) + # 0|1|1|SCAN TABLE posts (~100000 rows) + # + def pp(result) + result.rows.map do |row| + row.join('|') + end.join("\n") + "\n" + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index d1893f35f5..a5cbbf0c69 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -1,5 +1,6 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_record/connection_adapters/statement_pool' +require 'active_record/connection_adapters/sqlite3/explain_pretty_printer' require 'active_record/connection_adapters/sqlite3/schema_creation' gem 'sqlite3', '~> 1.3.6' @@ -218,21 +219,7 @@ module ActiveRecord def explain(arel, binds = []) sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" - ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', [])) - end - - class ExplainPrettyPrinter - # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles - # the output of the SQLite shell: - # - # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) - # 0|1|1|SCAN TABLE posts (~100000 rows) - # - def pp(result) # :nodoc: - result.rows.map do |row| - row.join('|') - end.join("\n") + "\n" - end + SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', [])) end def exec_query(sql, name = nil, binds = [], prepare: false) -- cgit v1.2.3 From f3eccd942aba88d18097644bbb580b257a006c94 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 31 Jan 2016 17:45:12 +0900 Subject: Remove duplicated composite primary key tests --- .../test/cases/adapters/postgresql/postgresql_adapter_test.rb | 8 -------- activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb | 8 -------- activerecord/test/cases/primary_keys_test.rb | 2 +- 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index e8daedac53..31e87722d9 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -53,14 +53,6 @@ module ActiveRecord end end - def test_composite_primary_key - with_example_table 'id serial, number serial, PRIMARY KEY (id, number)' do - silence_warnings do - assert_nil @connection.primary_key('ex') - end - end - end - def test_primary_key_raises_error_if_table_not_found assert_raises(ActiveRecord::StatementInvalid) do @connection.primary_key('unobtainium') diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 4cb2f02fe2..02c3358ba6 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -400,14 +400,6 @@ module ActiveRecord end end - def test_composite_primary_key - with_example_table 'id integer, number integer, foo integer, PRIMARY KEY (id, number)' do - silence_warnings do - assert_nil @conn.primary_key('ex') - end - end - end - def test_supports_extensions assert_not @conn.supports_extensions?, 'does not support extensions' end diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 155210cce9..b918b36b94 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -262,7 +262,7 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase def test_primary_key_issues_warning warning = capture(:stderr) do - @connection.primary_key("barcodes") + assert_nil @connection.primary_key("barcodes") end assert_match(/WARNING: Rails does not support composite primary key\./, warning) end -- cgit v1.2.3 From c2079f86f84e2ea0594156bcb85d990af644ecdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 1 Feb 2016 00:23:43 -0200 Subject: Revert "Merge pull request #23366 from maclover7/add-configuation-ar-docs" This reverts commit 96355e87cba247246234386b0af9273cc5d59db9, reversing changes made to a00c36feea6c0271b5ad48a949ef294514fdef52. See https://github.com/rails/rails/pull/23366#issuecomment-177714429 --- guides/source/active_record_basics.md | 102 ---------------------------------- 1 file changed, 102 deletions(-) diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index 0932cc4829..fba89f9d13 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -375,105 +375,3 @@ and to roll it back, `rails db:rollback`. Note that the above code is database-agnostic: it will run in MySQL, PostgreSQL, Oracle and others. You can learn more about migrations in the [Active Record Migrations guide](migrations.html). - -Connecting to the Database ----------------------- - -### `config/database.yml` - -When managing connections to the database, the `config/database.yml` file is -your best friend. This file helps to keep track of the adapter and -authentication parameters you are using for every database -environment in your application. This file is automatically -generated in all new Rails applications that have Active Record -enabled. - -Here's an example of what this file looks like: - -```yaml -default: &default - adapter: sqlite3 - pool: 5 - timeout: 5000 - -development: - <<: *default - database: db/development.sqlite3 - -test: - <<: *default - database: db/test.sqlite3 - -production: - <<: *default - database: db/production.sqlite3 -``` - -As you can see, there are 3 different database configurations listed -above. One for each of the Rails environments for this application --- development, test, and production. As well, all three -environments are sharing the same adapter, pool, and timeout -settings, so this was extracted out to the "default" group. This -extraction helps to keep your `config/database.yml` file DRY and easy -to read. - -A small side note before the next topic -- the test database -configured above will be deleted and restored both before and after -every test run, so make sure you keep your environments' databases -separated and siloed. - -### Connecting Manually - -In some special cases, you may want to establish connections for you -Active Record models directly inside the model file itself. For this -purpose, the `establish_connection` function was created. Say for -example you have a `Message` model, like below: - -```ruby -class Message < ActiveRecord::Base -end -``` - -Also, say you want to have this `Message` model connect to a -special "msg" database, instead of the one that the rest of the -application is using. In this case, you would do as follows: - -Step 1: Add the `establish_connection` helper method to your model -file: - -```ruby -class Message < ActiveRecord::Base - establish_connection() -end -``` - -Step 2: Add the configuration for this new database to your -`config/database.yml` file, under the `msg` (or whichever name you -choose) database name. - -Step 3: Turn this database name into a symbol. Remember, using string -keys here is not supported! In this case, `msg` -converts simply to `:msg`. If you are unsure of what the symbolized -database name would be, simply boot up either an `irb` or `rails c` -session, and type in the name of your database as a String, with a -`.to_sym` at the end, as follows: - -```bash -irb(main):001:0> "msg".to_sym -=> :msg -``` - -Step 4: The last and final step! Simply use this symbolized -database name as the sole parameter for the `establish_connection` -method. - -```ruby -class Message < ActiveRecord::Base - establish_connection(:msg) -end -``` - -That's all there is to it! When configuring your database via the -`config/database.yml` file, or connecting manually in your model, -connecting to your database and making changes is easy when using Active -Record. -- cgit v1.2.3 From 55e33667a6da92bea28eef8c3a435f45dbf6597b Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 1 Feb 2016 13:22:47 +1030 Subject: Remove development dependencies from actioncable.gemspec None of the other components use them, so we should be consistent. --- actioncable/actioncable.gemspec | 9 --------- 1 file changed, 9 deletions(-) diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec index 8d35d819cf..c65ff7871f 100644 --- a/actioncable/actioncable.gemspec +++ b/actioncable/actioncable.gemspec @@ -22,13 +22,4 @@ Gem::Specification.new do |s| s.add_dependency 'nio4r', '~> 1.2' s.add_dependency 'websocket-driver', '~> 0.6.1' - - s.add_development_dependency 'coffee-script', '~> 2.4.1' - s.add_development_dependency 'coffee-script-source', '~> 1.10.0' - s.add_development_dependency 'em-hiredis', '~> 0.3.0' - s.add_development_dependency 'mocha' - s.add_development_dependency 'pg' - s.add_development_dependency 'puma' - s.add_development_dependency 'redis', '~> 3.0' - s.add_development_dependency 'sprockets', '~> 3.5.2' end -- cgit v1.2.3 From 57604cf24ce40a23de0e8d75fc366c24c9fe3aac Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 11 Jan 2016 20:14:01 +0900 Subject: Avoid extra `show variables` in migration `initialize_schema_migrations_table` is called in every migrations. https://github.com/rails/rails/blob/v5.0.0.beta1/activerecord/lib/active_record/migration.rb#L1080 https://github.com/rails/rails/blob/v5.0.0.beta1/activerecord/lib/active_record/schema.rb#L51 This means that extra `show variables` is called regardless of the existence of `schema_migrations` table. This change is to avoid extra `show variables` if `schema_migrations` table exists. --- .../connection_adapters/abstract/schema_statements.rb | 6 +++++- .../connection_adapters/abstract_mysql_adapter.rb | 12 +++++------- activerecord/lib/active_record/internal_metadata.rb | 8 +++----- activerecord/lib/active_record/schema_migration.rb | 5 ++--- .../test/cases/adapters/mysql2/schema_migrations_test.rb | 5 ----- 5 files changed, 15 insertions(+), 21 deletions(-) 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 8db7f9172f..cc245587c1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -115,7 +115,7 @@ module ActiveRecord checks = [] checks << lambda { |c| c.name == column_name } checks << lambda { |c| c.type == type } if type - [:limit, :precision, :scale, :default, :null].each do |attr| + (migration_keys - [:name]).each do |attr| checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr) end @@ -981,6 +981,10 @@ module ActiveRecord ActiveRecord::InternalMetadata.create_table end + def internal_string_options_for_primary_key # :nodoc: + { primary_key: true } + end + def assume_migrated_upto_version(version, migrations_paths) migrations_paths = Array(migrations_paths) version = version.to_i 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 4dc7aa0c22..70d7956baa 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -67,14 +67,12 @@ module ActiveRecord end end - MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN = 191 CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32'] - def initialize_schema_migrations_table - if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) - ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN) - else - ActiveRecord::SchemaMigration.create_table - end + + def internal_string_options_for_primary_key # :nodoc: + super.tap { |options| + options[:collation] = collation.sub(/\A[^_]+/, 'utf8') if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) + } end def version diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb index 641b137851..cb4b1fc47c 100644 --- a/activerecord/lib/active_record/internal_metadata.rb +++ b/activerecord/lib/active_record/internal_metadata.rb @@ -5,10 +5,6 @@ module ActiveRecord # This class is used to create a table that keeps track of values and keys such # as which environment migrations were run in. class InternalMetadata < ActiveRecord::Base # :nodoc: - # Keys in mysql are limited to 191 characters, due to this no adapter can - # use a longer key - KEY_LIMIT = 191 - class << self def primary_key "key" @@ -33,8 +29,10 @@ module ActiveRecord # Creates an internal metadata table with columns +key+ and +value+ def create_table unless table_exists? + key_options = connection.internal_string_options_for_primary_key + connection.create_table(table_name, id: false) do |t| - t.string :key, primary_key: true, limit: KEY_LIMIT + t.string :key, key_options t.string :value t.timestamps end diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index a5b693c349..b6cb233e03 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -20,10 +20,9 @@ module ActiveRecord ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) } end - def create_table(limit=nil) + def create_table unless table_exists? - version_options = { primary_key: true } - version_options[:limit] = limit if limit + version_options = connection.internal_string_options_for_primary_key connection.create_table(table_name, id: false) do |t| t.string :version, version_options diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb index 9a9a4fed42..7c89fda582 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb @@ -33,11 +33,6 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase end end - def test_key_limit_max_matches_max - assert_equal ActiveRecord::InternalMetadata::KEY_LIMIT ,ActiveRecord::ConnectionAdapters::Mysql2Adapter::MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN - end - - private def with_encoding_utf8mb4 -- cgit v1.2.3 From 138c1db83e13cc01c54ac9657c8388aa46324582 Mon Sep 17 00:00:00 2001 From: Ernst Rullmann Date: Sun, 31 Jan 2016 22:50:20 -0500 Subject: Added references option to join tables Fixes issue #22960 When creating join tables with the command rails g migration CreateJoinTableShowroomUser showroom:references user:references The migration will use references to create the joins and output: class CreateJoinTableShowroomUser < ActiveRecord::Migration def change create_join_table :showrooms, :users do |t| t.references :showroom, index: true, foreign_key: true t.references :user, index: true, foreign_key: true end end end This allows for proper refrences with indexes and foreign keys to be easily used when adding join tables. Without `:refrences` the normal output is generated: class CreateJoinTableShowroomUser < ActiveRecord::Migration[5.0] def change create_join_table :showrooms, :users do |t| # t.index [:showroom_id, :user_id] # t.index [:user_id, :showroom_id] end end end --- .../rails/generators/active_record/migration/templates/migration.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb index 107f107dc4..481c70201b 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb @@ -19,7 +19,11 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi def change create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t| <%- attributes.each do |attribute| -%> + <%- if attribute.reference? -%> + t.references :<%= attribute.name %><%= attribute.inject_options %> + <%- else -%> <%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> <%- end -%> end end -- cgit v1.2.3 From 407baa2b4cc462e42c9aaccc9850adad4462ea7e Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Mon, 1 Feb 2016 16:08:11 +0900 Subject: tabenai (typo) --- .../cases/adapters/postgresql/extension_migration_test.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb index b2a805333c..b56c226763 100644 --- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb +++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb @@ -24,9 +24,9 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase return skip("no extension support") end - @old_schema_migration_tabel_name = ActiveRecord::SchemaMigration.table_name - @old_tabel_name_prefix = ActiveRecord::Base.table_name_prefix - @old_tabel_name_suffix = ActiveRecord::Base.table_name_suffix + @old_schema_migration_table_name = ActiveRecord::SchemaMigration.table_name + @old_table_name_prefix = ActiveRecord::Base.table_name_prefix + @old_table_name_suffix = ActiveRecord::Base.table_name_suffix ActiveRecord::Base.table_name_prefix = "p_" ActiveRecord::Base.table_name_suffix = "_s" @@ -36,11 +36,11 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase end def teardown - ActiveRecord::Base.table_name_prefix = @old_tabel_name_prefix - ActiveRecord::Base.table_name_suffix = @old_tabel_name_suffix + ActiveRecord::Base.table_name_prefix = @old_table_name_prefix + ActiveRecord::Base.table_name_suffix = @old_table_name_suffix ActiveRecord::SchemaMigration.delete_all rescue nil ActiveRecord::Migration.verbose = true - ActiveRecord::SchemaMigration.table_name = @old_schema_migration_tabel_name + ActiveRecord::SchemaMigration.table_name = @old_schema_migration_table_name super end -- cgit v1.2.3 From 437fa98e633e044e925c1fc7502a0a851b8bf718 Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Sun, 31 Jan 2016 16:31:24 +0530 Subject: Fix documentation related to `config.assets.cache_store` [ci skip] - sprockets-rails no longer supports customizing cache store after rails/sprockets-rails@ecaeb27 using `config.assets.cache_store`. - Instead we need to configure it using block syntax. - Fixes #19835. --- guides/source/asset_pipeline.md | 17 ++++++----------- guides/source/configuring.md | 2 -- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 5bdaf600ad..439f2bef3a 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -1177,19 +1177,14 @@ TIP: For further details have a look at the docs of your production web server: Assets Cache Store ------------------ -The default Rails cache store will be used by Sprockets to cache assets in -development and production. This can be changed by setting -`config.assets.cache_store`: +By default, Sprockets caches assets in `tmp/cache/assets` in development +and production environments. This can be changed as follows: ```ruby -config.assets.cache_store = :memory_store -``` - -The options accepted by the assets cache store are the same as the application's -cache store. - -```ruby -config.assets.cache_store = :memory_store, { size: 32.megabytes } +config.assets.configure do |env| + env.cache = ActiveSupport::Cache.lookup_store(:memory_store, + { size: 32.megabytes }) +end ``` To disable the assets cache store: diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 8a21d4062a..dfed46dd7d 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -159,8 +159,6 @@ pipeline is enabled. It is set to true by default. * `config.assets.debug` disables the concatenation and compression of assets. Set to `true` by default in `development.rb`. -* `config.assets.cache_store` defines the cache store that Sprockets will use. The default is the Rails file store. - * `config.assets.compile` is a boolean that can be used to turn on live Sprockets compilation in production. * `config.assets.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to the same configured at `config.logger`. Setting `config.assets.logger` to false will turn off served assets logging. -- cgit v1.2.3 From c3639458af5e307191ff9d5674f8144caf6a2833 Mon Sep 17 00:00:00 2001 From: Pierre Schambacher Date: Mon, 1 Feb 2016 11:28:25 +0000 Subject: Duplicate assert_generates options before modifying it --- actionpack/lib/action_dispatch/testing/assertions/routing.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 78ef860548..e7af27463c 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -86,6 +86,7 @@ module ActionDispatch end # Load routes.rb if it hasn't been loaded. + options = options.clone generated_path, query_string_keys = @routes.generate_extras(options, defaults) found_extras = options.reject { |k, _| ! query_string_keys.include? k } -- cgit v1.2.3 From a374af8b5788506acc28b755c0cde62ee89fa9d3 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 12 Jan 2016 01:51:54 +0000 Subject: Shorten ActiveRecord::InternalMetadata.table_name to ar_internal_metadata to support Oracle database which only supports 30 byte identifier length --- activerecord/lib/active_record/model_schema.rb | 4 ++-- activerecord/test/cases/migration_test.rb | 10 +++++----- activerecord/test/cases/schema_dumper_test.rb | 8 ++++---- railties/test/application/bin_setup_test.rb | 2 +- railties/test/application/rake/dbs_test.rb | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 722d7b5fce..ee52c3ae02 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -44,9 +44,9 @@ module ActiveRecord ## # :singleton-method: - # Accessor for the name of the internal metadata table. By default, the value is "active_record_internal_metadatas" + # Accessor for the name of the internal metadata table. By default, the value is "ar_internal_metadata" class_attribute :internal_metadata_table_name, instance_accessor: false - self.internal_metadata_table_name = "active_record_internal_metadatas" + self.internal_metadata_table_name = "ar_internal_metadata" ## # :singleton-method: diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index f51e366b1d..e292ae5fe5 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -357,14 +357,14 @@ class MigrationTest < ActiveRecord::TestCase def test_internal_metadata_table_name original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name - assert_equal "active_record_internal_metadatas", ActiveRecord::InternalMetadata.table_name - ActiveRecord::Base.table_name_prefix = "prefix_" - ActiveRecord::Base.table_name_suffix = "_suffix" + assert_equal "ar_internal_metadata", ActiveRecord::InternalMetadata.table_name + ActiveRecord::Base.table_name_prefix = "p_" + ActiveRecord::Base.table_name_suffix = "_s" Reminder.reset_table_name - assert_equal "prefix_active_record_internal_metadatas_suffix", ActiveRecord::InternalMetadata.table_name + assert_equal "p_ar_internal_metadata_s", ActiveRecord::InternalMetadata.table_name ActiveRecord::Base.internal_metadata_table_name = "changed" Reminder.reset_table_name - assert_equal "prefix_changed_suffix", ActiveRecord::InternalMetadata.table_name + assert_equal "p_changed_s", ActiveRecord::InternalMetadata.table_name ActiveRecord::Base.table_name_prefix = "" ActiveRecord::Base.table_name_suffix = "" Reminder.reset_table_name diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 7b93d20e05..25f4a69ad1 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -38,7 +38,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output - assert_no_match %r{create_table "active_record_internal_metadatas"}, output + assert_no_match %r{create_table "ar_internal_metadata"}, output end def test_schema_dump_uses_force_cascade_on_create_table @@ -159,7 +159,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output - assert_no_match %r{create_table "active_record_internal_metadatas"}, output + assert_no_match %r{create_table "ar_internal_metadata"}, output end def test_schema_dump_with_regexp_ignored_table @@ -167,7 +167,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output - assert_no_match %r{create_table "active_record_internal_metadatas"}, output + assert_no_match %r{create_table "ar_internal_metadata"}, output end def test_schema_dumps_index_columns_in_right_order @@ -345,7 +345,7 @@ class SchemaDumperTest < ActiveRecord::TestCase 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 "active_record_internal_metadatas"}, 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 diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb index 8c3ab65c51..a07c51a60f 100644 --- a/railties/test/application/bin_setup_test.rb +++ b/railties/test/application/bin_setup_test.rb @@ -28,7 +28,7 @@ module ApplicationTests assert_not File.exist?("tmp/restart.txt") `bin/setup 2>&1` assert_equal 0, File.size("log/test.log") - assert_equal '["articles", "schema_migrations", "active_record_internal_metadatas"]', list_tables.call + assert_equal '["articles", "schema_migrations", "ar_internal_metadata"]', list_tables.call assert File.exist?("tmp/restart.txt") end end diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index c000a70382..a229609e84 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -222,14 +222,14 @@ module ApplicationTests assert_equal '["posts"]', list_tables[] `bin/rails db:schema:load` - assert_equal '["posts", "comments", "schema_migrations", "active_record_internal_metadatas"]', list_tables[] + assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata"]', list_tables[] app_file 'db/structure.sql', <<-SQL CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); SQL `bin/rails db:structure:load` - assert_equal '["posts", "comments", "schema_migrations", "active_record_internal_metadatas", "users"]', list_tables[] + assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata", "users"]', list_tables[] end end -- cgit v1.2.3 From 407e0ab5e5cddf6a8b6b278b12f50772d13b4d86 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 27 Jan 2016 03:30:37 +0000 Subject: Rename `active_record_internal_metadatas` to `ar_internal_metadata` for those who already migrated to Rails 5.0.0 beta --- activerecord/lib/active_record/internal_metadata.rb | 13 +++++++++++++ activerecord/test/cases/migration_test.rb | 15 +++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb index cb4b1fc47c..81db96bffd 100644 --- a/activerecord/lib/active_record/internal_metadata.rb +++ b/activerecord/lib/active_record/internal_metadata.rb @@ -14,6 +14,10 @@ module ActiveRecord "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}" end + def original_table_name + "#{table_name_prefix}active_record_internal_metadatas#{table_name_suffix}" + end + def []=(key, value) first_or_initialize(key: key).update_attributes!(value: value) end @@ -26,8 +30,17 @@ module ActiveRecord ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) } end + def original_table_exists? + # This method will be removed in Rails 5.1 + # Since it is only necessary when `active_record_internal_metadatas` could exist + ActiveSupport::Deprecation.silence { connection.table_exists?(original_table_name) } + end + # Creates an internal metadata table with columns +key+ and +value+ def create_table + if original_table_exists? + connection.rename_table(original_table_name, table_name) + end unless table_exists? key_options = connection.internal_string_options_for_primary_key diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index e292ae5fe5..9b4394377f 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -426,6 +426,21 @@ class MigrationTest < ActiveRecord::TestCase ENV["RACK_ENV"] = original_rack_env end + def test_rename_internal_metadata_table + original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name + + ActiveRecord::Base.internal_metadata_table_name = "active_record_internal_metadatas" + Reminder.reset_table_name + + ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name + Reminder.reset_table_name + + assert_equal "ar_internal_metadata", ActiveRecord::InternalMetadata.table_name + ensure + ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name + Reminder.reset_table_name + end + def test_proper_table_name_on_migration reminder_class = new_isolated_reminder_class migration = ActiveRecord::Migration.new -- cgit v1.2.3 From 92203edbe6546f84921b6ccb6e79c3a01857a8b3 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 1 Feb 2016 19:42:14 +1030 Subject: Always obtain the lock and do the unload We mostly care about `reload_classes_only_on_change=true`, because that's the default... and there, we definitely need to wait for the lock when necessary. --- railties/lib/rails/application/finisher.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 404e3c3e23..411cdbad19 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -86,7 +86,7 @@ module Rails # added in the hook are taken into account. initializer :set_clear_dependencies_hook, group: :all do callback = lambda do - ActiveSupport::Dependencies.interlock.attempt_unloading do + ActiveSupport::Dependencies.interlock.unloading do ActiveSupport::DescendantsTracker.clear ActiveSupport::Dependencies.clear end -- cgit v1.2.3 From aeb58ab70470b7f395a1e77b10c9b7a73955dad8 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 1 Feb 2016 23:41:07 +1030 Subject: Block new share attempts if there's an exclusive waiter --- .../lib/active_support/concurrency/share_lock.rb | 20 ++++++++------ activesupport/test/share_lock_test.rb | 32 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb index ca48164c54..1537f2898f 100644 --- a/activesupport/lib/active_support/concurrency/share_lock.rb +++ b/activesupport/lib/active_support/concurrency/share_lock.rb @@ -48,14 +48,14 @@ module ActiveSupport def start_exclusive(purpose: nil, compatible: [], no_wait: false) synchronize do unless @exclusive_thread == Thread.current - if busy?(purpose) + if busy_for_exclusive?(purpose) return false if no_wait loose_shares = @sharing.delete(Thread.current) @waiting[Thread.current] = compatible if loose_shares begin - @cv.wait_while { busy?(purpose) } + @cv.wait_while { busy_for_exclusive?(purpose) } ensure @waiting.delete Thread.current @sharing[Thread.current] = loose_shares if loose_shares @@ -83,10 +83,10 @@ module ActiveSupport end end - def start_sharing + def start_sharing(purpose: :share) synchronize do - if @exclusive_thread && @exclusive_thread != Thread.current - @cv.wait_while { @exclusive_thread } + if busy_for_sharing?(purpose) + @cv.wait_while { busy_for_sharing?(purpose) } end @sharing[Thread.current] += 1 end @@ -132,11 +132,15 @@ module ActiveSupport private # Must be called within synchronize - def busy?(purpose) - (@exclusive_thread && @exclusive_thread != Thread.current) || - @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) } || + def busy_for_exclusive?(purpose) + busy_for_sharing?(purpose) || @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0) end + + def busy_for_sharing?(purpose) + (@exclusive_thread && @exclusive_thread != Thread.current) || + @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) } + end end end end diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb index 465a657308..0a5b074bee 100644 --- a/activesupport/test/share_lock_test.rb +++ b/activesupport/test/share_lock_test.rb @@ -114,14 +114,17 @@ class ShareLockTest < ActiveSupport::TestCase [true, false].each do |use_upgrading| with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| begin + together = Concurrent::CyclicBarrier.new(2) conflicting_exclusive_threads = [ Thread.new do @lock.send(use_upgrading ? :sharing : :tap) do + together.wait @lock.exclusive(purpose: :red, compatible: [:green, :purple]) {} end end, Thread.new do @lock.send(use_upgrading ? :sharing : :tap) do + together.wait @lock.exclusive(purpose: :blue, compatible: [:green]) {} end end @@ -183,11 +186,14 @@ class ShareLockTest < ActiveSupport::TestCase load_params = [:load, [:load]] unload_params = [:unload, [:unload, :load]] + all_sharing = Concurrent::CyclicBarrier.new(4) + [load_params, load_params, unload_params, unload_params].permutation do |thread_params| with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| threads = thread_params.map do |purpose, compatible| Thread.new do @lock.sharing do + all_sharing.wait @lock.exclusive(purpose: purpose, compatible: compatible) do scratch_pad_mutex.synchronize { scratch_pad << purpose } end @@ -209,6 +215,32 @@ class ShareLockTest < ActiveSupport::TestCase end end + def test_new_share_attempts_block_on_waiting_exclusive + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| + release_exclusive = Concurrent::CountDownLatch.new + + waiting_exclusive = Thread.new do + @lock.sharing do + @lock.exclusive do + release_exclusive.wait + end + end + end + assert_threads_stuck waiting_exclusive + + late_share_attempt = Thread.new do + @lock.sharing {} + end + assert_threads_stuck late_share_attempt + + sharing_thread_release_latch.count_down + assert_threads_stuck late_share_attempt + + release_exclusive.count_down + assert_threads_not_stuck late_share_attempt + end + end + def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads scratch_pad = [] scratch_pad_mutex = Mutex.new -- cgit v1.2.3 From f02bd2a92c67f0d4190853521d3580766e829044 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 1 Feb 2016 23:58:36 +1030 Subject: While new sharers are blocked, an existing sharer remains re-entrant --- .../lib/active_support/concurrency/share_lock.rb | 2 +- activesupport/test/share_lock_test.rb | 29 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb index 1537f2898f..fa5d9bfdd7 100644 --- a/activesupport/lib/active_support/concurrency/share_lock.rb +++ b/activesupport/lib/active_support/concurrency/share_lock.rb @@ -85,7 +85,7 @@ module ActiveSupport def start_sharing(purpose: :share) synchronize do - if busy_for_sharing?(purpose) + if @sharing[Thread.current] == 0 && @exclusive_thread != Thread.current && busy_for_sharing?(purpose) @cv.wait_while { busy_for_sharing?(purpose) } end @sharing[Thread.current] += 1 diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb index 0a5b074bee..3475ee94cd 100644 --- a/activesupport/test/share_lock_test.rb +++ b/activesupport/test/share_lock_test.rb @@ -241,6 +241,35 @@ class ShareLockTest < ActiveSupport::TestCase end end + def test_share_remains_reentrant_ignoring_a_waiting_exclusive + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| + ready = Concurrent::CyclicBarrier.new(2) + attempt_reentrancy = Concurrent::CountDownLatch.new + + sharer = Thread.new do + @lock.sharing do + ready.wait + attempt_reentrancy.wait + @lock.sharing {} + end + end + + exclusive = Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive {} + end + end + + assert_threads_stuck exclusive + + attempt_reentrancy.count_down + + assert_threads_not_stuck sharer + assert_threads_stuck exclusive + end + end + def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads scratch_pad = [] scratch_pad_mutex = Mutex.new -- cgit v1.2.3 From f836630f8cdf53a06259cc22ac842bbfa6376f65 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Tue, 2 Feb 2016 01:42:20 +1030 Subject: After completing a load, give other threads a chance too While we know no user code is running, we should do as much loading as we can. That way, all the threads will then be able to resume running user code together. Otherwise, only the last arriving thread would get to do its load, and would then return to userspace, leaving the others still blocked. --- .../lib/active_support/concurrency/share_lock.rb | 38 +++++++++++++++------- .../lib/active_support/dependencies/interlock.rb | 6 ++-- activesupport/test/share_lock_test.rb | 17 ++++++++++ 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb index fa5d9bfdd7..8e4ca272ba 100644 --- a/activesupport/lib/active_support/concurrency/share_lock.rb +++ b/activesupport/lib/active_support/concurrency/share_lock.rb @@ -51,14 +51,8 @@ module ActiveSupport if busy_for_exclusive?(purpose) return false if no_wait - loose_shares = @sharing.delete(Thread.current) - @waiting[Thread.current] = compatible if loose_shares - - begin + yield_shares(purpose, compatible) do @cv.wait_while { busy_for_exclusive?(purpose) } - ensure - @waiting.delete Thread.current - @sharing[Thread.current] = loose_shares if loose_shares end end @exclusive_thread = Thread.current @@ -71,14 +65,18 @@ module ActiveSupport # Relinquish the exclusive lock. Must only be called by the thread # that called start_exclusive (and currently holds the lock). - def stop_exclusive + def stop_exclusive(compatible: []) synchronize do raise "invalid unlock" if @exclusive_thread != Thread.current @exclusive_depth -= 1 if @exclusive_depth == 0 @exclusive_thread = nil - @cv.broadcast + + yield_shares(nil, compatible) do + @cv.broadcast + @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) } + end end end end @@ -109,12 +107,12 @@ module ActiveSupport # the block. # # See +start_exclusive+ for other options. - def exclusive(purpose: nil, compatible: [], no_wait: false) + def exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false) if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait) begin yield ensure - stop_exclusive + stop_exclusive(compatible: after_compatible) end end end @@ -139,7 +137,23 @@ module ActiveSupport def busy_for_sharing?(purpose) (@exclusive_thread && @exclusive_thread != Thread.current) || - @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) } + @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) } + end + + def eligible_waiters?(compatible) + @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } } + end + + def yield_shares(purpose, compatible) + loose_shares = @sharing.delete(Thread.current) + @waiting[Thread.current] = [purpose, compatible] if loose_shares + + begin + yield + ensure + @waiting.delete Thread.current + @sharing[Thread.current] = loose_shares if loose_shares + end end end end diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb index fbeb904684..b6a1b25eee 100644 --- a/activesupport/lib/active_support/dependencies/interlock.rb +++ b/activesupport/lib/active_support/dependencies/interlock.rb @@ -8,13 +8,13 @@ module ActiveSupport #:nodoc: end def loading - @lock.exclusive(purpose: :load, compatible: [:load]) do + @lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load]) do yield end end def unloading - @lock.exclusive(purpose: :unload, compatible: [:load, :unload]) do + @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload]) do yield end end @@ -24,7 +24,7 @@ module ActiveSupport #:nodoc: # concurrent activity, return immediately (without executing the # block) instead of waiting. def attempt_unloading - @lock.exclusive(purpose: :unload, compatible: [:load, :unload], no_wait: true) do + @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload], no_wait: true) do yield end end diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb index 3475ee94cd..12953d99a6 100644 --- a/activesupport/test/share_lock_test.rb +++ b/activesupport/test/share_lock_test.rb @@ -270,6 +270,23 @@ class ShareLockTest < ActiveSupport::TestCase end end + def test_compatible_exclusives_cooperate_to_both_proceed + ready = Concurrent::CyclicBarrier.new(2) + done = Concurrent::CyclicBarrier.new(2) + + threads = 2.times.map do + Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive(purpose: :x, compatible: [:x], after_compatible: [:x]) {} + done.wait + end + end + end + + assert_threads_not_stuck threads + end + def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads scratch_pad = [] scratch_pad_mutex = Mutex.new -- cgit v1.2.3 From 949925d6432b8238da8af2a71d568978655aad6e Mon Sep 17 00:00:00 2001 From: claudiob Date: Mon, 1 Feb 2016 10:00:05 -0800 Subject: [ci skip] Properly indent code in markdown [ci skip] --- activesupport/CHANGELOG.md | 58 +++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 7701e0a7a2..8cff374a47 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -42,44 +42,44 @@ Here's an example of a simple event tracking system where the object being tracked needs not pass a creator that it doesn't need itself along: - module Current - thread_mattr_accessor :account - thread_mattr_accessor :user + module Current + thread_mattr_accessor :account + thread_mattr_accessor :user - def self.reset() self.account = self.user = nil end - end + def self.reset() self.account = self.user = nil end + end - class ApplicationController < ActionController::Base - before_action :set_current - after_action { Current.reset } + class ApplicationController < ActionController::Base + before_action :set_current + after_action { Current.reset } - private - def set_current - Current.account = Account.find(params[:account_id]) - Current.user = Current.account.users.find(params[:user_id]) + private + def set_current + Current.account = Account.find(params[:account_id]) + Current.user = Current.account.users.find(params[:user_id]) + end end - end - class MessagesController < ApplicationController - def create - @message = Message.create!(message_params) - end - end + class MessagesController < ApplicationController + def create + @message = Message.create!(message_params) + end + end - class Message < ApplicationRecord - has_many :events - after_create :track_created + class Message < ApplicationRecord + has_many :events + after_create :track_created - private - def track_created - events.create! origin: self, action: :create + private + def track_created + events.create! origin: self, action: :create + end end - end - class Event < ApplicationRecord - belongs_to :creator, class_name: 'User' - before_validation { self.creator ||= Current.user } - end + class Event < ApplicationRecord + belongs_to :creator, class_name: 'User' + before_validation { self.creator ||= Current.user } + end *DHH* -- cgit v1.2.3 From b3427c662e337c2a3aa8d944b0f012641e67a4bd Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Wed, 20 Jan 2016 19:58:25 -0500 Subject: Add documentation for #17573 Fixes some parts of #23148. [ci skip] --- actionpack/lib/action_dispatch/http/cache.rb | 8 ++++++++ guides/source/5_0_release_notes.md | 3 +++ 2 files changed, 11 insertions(+) diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 0f7898a3f8..4bd727c14e 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -80,6 +80,14 @@ module ActionDispatch set_header DATE, utc_time.httpdate end + # This method allows you to set the ETag for cached content, which + # will be returned to the end user. + # + # By default, Action Dispatch sets all ETags to be weak. + # This ensures that if the content changes only semantically, + # the whole page doesn't have to be regenerated from scratch + # by the web server. With strong ETags, pages are compared + # byte by byte, and are regenerated only if they are not exactly equal. def etag=(etag) key = ActiveSupport::Cache.expand_cache_key(etag) super %(W/"#{Digest::MD5.hexdigest(key)}") diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 2650384df3..f45c8005da 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -253,6 +253,9 @@ Please refer to the [Changelog][action-pack] for detailed changes. `ActionDispatch::IntegrationTest` instead. ([commit](https://github.com/rails/rails/commit/4414c5d1795e815b102571425974a8b1d46d932d)) +* Rails will only generate "weak", instead of strong ETags. + ([Pull Request](https://github.com/rails/rails/pull/17573)) + Action View ------------- -- cgit v1.2.3 From 25cf3f0d7cbb0307839c748fa8ee416a07e5e0e8 Mon Sep 17 00:00:00 2001 From: Vishnu Ks Date: Tue, 2 Feb 2016 00:12:44 +0530 Subject: git protocol replaced with https --- guides/source/contributing_to_ruby_on_rails.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index f02f6a18ee..0ec6c99301 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -105,7 +105,7 @@ $ git checkout -b testing_branch Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to a topic branch "orange" located at https://github.com/JohnSmith/rails. ```bash -$ git remote add JohnSmith git://github.com/JohnSmith/rails.git +$ git remote add JohnSmith https://github.com/JohnSmith/rails $ git pull JohnSmith orange ``` @@ -204,7 +204,7 @@ In case you can't use the Rails development box, see [this other guide](developm To be able to contribute code, you need to clone the Rails repository: ```bash -$ git clone git://github.com/rails/rails.git +$ git clone https://github.com/rails/rails ``` and create a dedicated branch: @@ -506,7 +506,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 git@github.com:/rails.git +$ git remote add mine https://github.com:/rails ``` Push to your remote: @@ -520,7 +520,7 @@ You might have cloned your forked repository into your machine and might want to In the directory you cloned your fork: ```bash -$ git remote add rails git://github.com/rails/rails.git +$ git remote add rails https://github.com/rails/rails ``` Download new commits and branches from the official repository: @@ -605,7 +605,7 @@ Rails repository. This is useful anyway, but just in case you don't have it set up, make sure that you do this first: ```bash -$ git remote add upstream https://github.com/rails/rails.git +$ git remote add upstream https://github.com/rails/rails ``` You can call this remote whatever you'd like, but if you don't use `upstream`, -- cgit v1.2.3 From 8a436fdd98c63cc0a7a6d2c642c18d33421dc6ad Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Mon, 25 Jan 2016 01:32:44 +0530 Subject: Add options for rake routes task Add two options: `-c` and `-g`. `-g` option returns the urls name, verb and path fields that match the pattern. `-c` option returns the urls for specific controller. Fixes #18902, and Fixes #20420 [Anton Davydov & Vipul A M] --- actionpack/CHANGELOG.md | 10 ++++ actionpack/lib/action_dispatch/routing.rb | 3 +- .../lib/action_dispatch/routing/inspector.rb | 34 ++++++------ actionpack/test/dispatch/routing/inspector_test.rb | 34 +++++++++--- guides/source/routing.md | 17 ++++-- railties/lib/rails/tasks/routes.rake | 32 ++++++++++- railties/test/application/rake_test.rb | 63 +++++++++++++++++++++- 7 files changed, 163 insertions(+), 30 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index f6ffe45490..b2d9288eca 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,13 @@ +* Add `-g` and `-c` (short for _grep_ and _controller_ respectively) options + to `bin/rake routes`. These options return the url `name`, `verb` and + `path` field that match the pattern or match a specific controller. + + Deprecate `CONTROLLER` env variable in `bin/rake routes`. + + See #18902. + + *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 `fresh_when` and `stale?` are weak. diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index d00b2c3eb5..6cde5b2900 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -239,7 +239,8 @@ module ActionDispatch # # rails routes # - # Target specific controllers by prefixing the command with CONTROLLER=x. + # Target specific controllers by prefixing the command with --controller option + # - or its -c shorthand. # module Routing extend ActiveSupport::Autoload diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 69e6dd5215..1ca2a3b683 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -60,12 +60,11 @@ module ActionDispatch end def format(formatter, filter = nil) - routes_to_display = filter_routes(filter) - + filter_options = normalize_filter(filter) + routes_to_display = filter_routes(filter_options) routes = collect_routes(routes_to_display) - if routes.none? - formatter.no_routes(collect_routes(@routes), filter) + formatter.no_routes(collect_routes(@routes)) return formatter.result end @@ -82,10 +81,21 @@ module ActionDispatch private - def filter_routes(filter) - if filter - filter_name = filter.underscore.sub(/_controller$/, '') - @routes.select { |route| route.defaults[:controller] == filter_name } + def normalize_filter(filter) + if filter.is_a?(Hash) && filter[:controller] + {controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/} + elsif filter.is_a?(String) + {controller: /#{filter}/, action: /#{filter}/} + else + nil + end + end + + def filter_routes(filter_options) + if filter_options + @routes.select do |route| + filter_options.any? { |default, filter| route.defaults[default] =~ filter } + end else @routes end @@ -137,7 +147,7 @@ module ActionDispatch @buffer << draw_header(routes) end - def no_routes(routes, filter) + def no_routes(routes) @buffer << if routes.none? <<-MESSAGE.strip_heredoc @@ -145,8 +155,6 @@ module ActionDispatch Please add some routes in config/routes.rb. MESSAGE - elsif missing_controller?(filter) - "The controller #{filter} does not exist!" else "No routes were found for this controller" end @@ -154,10 +162,6 @@ module ActionDispatch end private - def missing_controller?(controller_name) - [ controller_name.camelize, "#{controller_name.camelize}Controller" ].none?(&:safe_constantize) - end - def draw_section(routes) header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length) name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max) diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index 7382c267c7..f72a87b994 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -17,10 +17,10 @@ module ActionDispatch @set = ActionDispatch::Routing::RouteSet.new end - def draw(options = {}, &block) + def draw(options = nil, &block) @set.draw(&block) inspector = ActionDispatch::Routing::RoutesInspector.new(@set.routes) - inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n") + inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options).split("\n") end def test_displaying_routes_for_engines @@ -297,7 +297,7 @@ module ActionDispatch end def test_routes_can_be_filtered - output = draw(filter: 'posts') do + output = draw('posts') do resources :articles resources :posts end @@ -313,6 +313,26 @@ module ActionDispatch " DELETE /posts/:id(.:format) posts#destroy"], output end + def test_routes_can_be_filtered_with_namespaced_controllers + output = draw('admin/posts') do + resources :articles + namespace :admin do + resources :posts + end + end + + assert_equal [" Prefix Verb URI Pattern Controller#Action", + " admin_posts GET /admin/posts(.:format) admin/posts#index", + " POST /admin/posts(.:format) admin/posts#create", + " new_admin_post GET /admin/posts/new(.:format) admin/posts#new", + "edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit", + " admin_post GET /admin/posts/:id(.:format) admin/posts#show", + " PATCH /admin/posts/:id(.:format) admin/posts#update", + " PUT /admin/posts/:id(.:format) admin/posts#update", + " DELETE /admin/posts/:id(.:format) admin/posts#destroy"], output + end + + def test_regression_route_with_controller_regexp output = draw do get ':controller(/:action)', controller: /api\/[^\/]+/, format: false @@ -336,18 +356,18 @@ module ActionDispatch end def test_routes_with_undefined_filter - output = draw(:filter => 'Rails::MissingController') do + output = draw(controller: 'Rails::MissingController') do get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ end assert_equal [ - "The controller Rails::MissingController does not exist!", + "No routes were found for this controller", "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." ], output end def test_no_routes_matched_filter - output = draw(:filter => 'rails/dummy') do + output = draw('rails/dummy') do get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ end @@ -358,7 +378,7 @@ module ActionDispatch end def test_no_routes_were_defined - output = draw(:filter => 'Rails::DummyController') { } + output = draw('Rails::DummyController') {} assert_equal [ "You don't have any routes defined!", diff --git a/guides/source/routing.md b/guides/source/routing.md index 5a745b10cd..d9e64d56ac 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -1136,10 +1136,21 @@ For example, here's a small section of the `rails routes` output for a RESTful r edit_user GET /users/:id/edit(.:format) users#edit ``` -You may restrict the listing to the routes that map to a particular controller setting the `CONTROLLER` environment variable: +You can search through your routes with the --grep option (-g for short). This outputs any routes that partially match the URL helper method name, the HTTP verb, or the URL path. -```bash -$ CONTROLLER=users bin/rails routes +``` +$ bin/rake routes --grep new_comment +$ bin/rake routes -g POST +$ bin/rake routes -g admin +``` + +If you only want to see the routes that map to a specific controller, there's the --controller option (-c for short). + +``` +$ bin/rake routes --controller users +$ bin/rake routes --controller admin/users +$ bin/rake routes -c Comments +$ bin/rake routes -c Articles::CommentsController ``` TIP: You'll find that the output from `rails routes` is much more readable if you widen your terminal window until the output lines don't wrap. diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index 1815c2fdc7..499c434ffa 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -1,7 +1,35 @@ -desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.' +require 'active_support/deprecation' +require 'active_support/core_ext/string/strip' # for strip_heredoc +require 'optparse' + +desc 'Print out all defined routes in match order, with names. Target specific controller with --controller option - or its -c shorthand.' task routes: :environment do all_routes = Rails.application.routes.routes require 'action_dispatch/routing/inspector' inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes) - puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, ENV['CONTROLLER']) + if ARGV.any?{ |argv| argv.start_with? 'CONTROLLER' } + puts <<-eow.strip_heredoc + Passing `CONTROLLER` to `bin/rake routes` is deprecated and will be removed in Rails 5.1. + Please use `bin/rake routes -c controller_name` instead. + eow + end + + routes_filter = nil + routes_filter = {controller: ENV['CONTROLLER']} if ENV['CONTROLLER'] + + OptionParser.new do |opts| + opts.banner = "Usage: rake routes [options]" + opts.on("-c", "--controller [CONTROLLER]") do |controller| + routes_filter = { controller: controller } + end + + opts.on("-g", "--grep [PATTERN]") do |pattern| + routes_filter = pattern + end + + end.parse!(ARGV.reject { |x| x == "routes" }) + + puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, routes_filter) + + exit 0 # ensure extra arguments aren't interpreted as Rake tasks end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index b979ad64d1..7171aa6e1a 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -141,8 +141,67 @@ module ApplicationTests end RUBY - ENV['CONTROLLER'] = 'cart' - output = Dir.chdir(app_path){ `bin/rails routes` } + output = Dir.chdir(app_path){ `bin/rake routes CONTROLLER=cart` } + assert_equal ["Passing `CONTROLLER` to `bin/rake routes` is deprecated and will be removed in Rails 5.1.", + "Please use `bin/rake routes -c controller_name` instead.", + "Prefix Verb URI Pattern Controller#Action", + " cart GET /cart(.:format) cart#show\n"].join("\n"), output + + output = Dir.chdir(app_path){ `bin/rails routes -c cart` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + end + + def test_rake_routes_with_namespaced_controller_environment + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + namespace :admin do + resource :post + end + end + RUBY + expected_output = [" Prefix Verb URI Pattern Controller#Action", + " admin_post POST /admin/post(.:format) admin/posts#create", + " new_admin_post GET /admin/post/new(.:format) admin/posts#new", + "edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit", + " GET /admin/post(.:format) admin/posts#show", + " PATCH /admin/post(.:format) admin/posts#update", + " PUT /admin/post(.:format) admin/posts#update", + " DELETE /admin/post(.:format) admin/posts#destroy\n"].join("\n") + + output = Dir.chdir(app_path){ `bin/rails routes -c Admin::PostController` } + assert_equal expected_output, output + + output = Dir.chdir(app_path){ `bin/rails routes -c PostController` } + assert_equal expected_output, output + end + + def test_rake_routes_with_global_search_key + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + get '/basketball', to: 'basketball#index' + end + RUBY + + output = Dir.chdir(app_path){ `bin/rake routes -g show` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + end + + def test_rake_routes_with_controller_search_key + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + get '/basketball', to: 'basketball#index' + end + RUBY + + output = Dir.chdir(app_path){ `bin/rake routes -c cart` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + + output = Dir.chdir(app_path){ `bin/rake routes -c Cart` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + + output = Dir.chdir(app_path){ `bin/rake routes -c CartController` } assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output end -- cgit v1.2.3 From b818fd3b45bff94865c0e9bbae3ec490a12ccf98 Mon Sep 17 00:00:00 2001 From: Vishnu Ks Date: Tue, 2 Feb 2016 00:33:46 +0530 Subject: .git added to https repo urls --- guides/source/contributing_to_ruby_on_rails.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 0ec6c99301..0f98d12217 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -105,7 +105,7 @@ $ git checkout -b testing_branch Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to a topic branch "orange" located at https://github.com/JohnSmith/rails. ```bash -$ git remote add JohnSmith https://github.com/JohnSmith/rails +$ git remote add JohnSmith https://github.com/JohnSmith/rails.git $ git pull JohnSmith orange ``` @@ -204,7 +204,7 @@ In case you can't use the Rails development box, see [this other guide](developm To be able to contribute code, you need to clone the Rails repository: ```bash -$ git clone https://github.com/rails/rails +$ git clone https://github.com/rails/rails.git ``` and create a dedicated branch: @@ -506,7 +506,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:/rails +$ git remote add mine https://github.com:/rails.git ``` Push to your remote: @@ -520,7 +520,7 @@ You might have cloned your forked repository into your machine and might want to In the directory you cloned your fork: ```bash -$ git remote add rails https://github.com/rails/rails +$ git remote add rails https://github.com/rails/rails.git ``` Download new commits and branches from the official repository: @@ -605,7 +605,7 @@ Rails repository. This is useful anyway, but just in case you don't have it set up, make sure that you do this first: ```bash -$ git remote add upstream https://github.com/rails/rails +$ git remote add upstream https://github.com/rails/rails.git ``` You can call this remote whatever you'd like, but if you don't use `upstream`, -- cgit v1.2.3 From 0cbcae59dd402ff27f6dbf76659b67a77776fe37 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 1 Feb 2016 14:03:12 -0700 Subject: Revert "Merge pull request #16400 from bogdan/last-with-sql" This reverts commit 9f3730a516f30beb0050caea9539f8d6b808e58a, reversing changes made to 2637fb75d82e1c69333855abd58c2470994995d3. There are additional issues with this commit that need to be addressed before this change is ready (see #23377). This is a temporary revert in order for us to have more time to address the issues with that PR, without blocking the release of beta2. --- activerecord/CHANGELOG.md | 20 ----------- .../lib/active_record/relation/finder_methods.rb | 39 ++++++++++++---------- activerecord/test/cases/finder_test.rb | 32 +++++------------- 3 files changed, 31 insertions(+), 60 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index faab0f9554..50a0b291b3 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,23 +1,3 @@ -* Rework `ActiveRecord::Relation#last` - - 1. Always find last with ruby if relation is loaded - 2. Always use SQL instead if relation is not loaded. - 3. Deprecated relation loading when SQL order can not be automatically reversed - - Topic.order("title").load.last(3) - # before: SELECT ... - # after: No SQL - - Topic.order("title").last - # before: SELECT * FROM `topics` - # after: SELECT * FROM `topics` ORDER BY `topics`.`title` DESC LIMIT 1 - - Topic.order("coalesce(author, title)").last - # before: SELECT * FROM `topics` - # after: Deprecation Warning for irreversible order - - *Bogdan Gusiev* - * `ActiveRecord::Relation#reverse_order` throws `ActiveRecord::IrreversibleOrderError` when the order can not be reversed using current trivial algorithm. Also raises the same error when `#reverse_order` is called on diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 5d4a045097..3f5d6de78a 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -145,23 +145,15 @@ module ActiveRecord # # [#, #, #] def last(limit = nil) - return find_last(limit) if loaded? - - result = order_values.empty? && primary_key ? order(arel_table[primary_key].desc) : reverse_order - result = result.limit!(limit || 1) - limit ? result.reverse : result.first - rescue ActiveRecord::IrreversibleOrderError - ActiveSupport::Deprecation.warn(<<-WARNING.squish) - Finding a last element by loading the relation when SQL ORDER - can not be reversed is deprecated. - Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case. - Please call `to_a.last` if you still want to load the relation. - WARNING - find_last(limit) - end - - def find_last(limit) - limit ? to_a.last(limit) : to_a.last + if limit + if order_values.empty? && primary_key + order(arel_table[primary_key].desc).limit(limit).reverse + else + to_a.last(limit) + end + else + find_last + end end # Same as #last but raises ActiveRecord::RecordNotFound if no record @@ -531,6 +523,19 @@ module ActiveRecord relation.limit(limit).to_a end + def find_last + if loaded? + @records.last + else + @last ||= + if limit_value + to_a.last + else + reverse_order.limit(1).to_a.first + end + end + end + private def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc: diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index cbeb37dd22..75a74c052d 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -506,7 +506,7 @@ class FinderTest < ActiveRecord::TestCase end end - def test_take_and_first_and_last_with_integer_should_use_sql + def test_take_and_first_and_last_with_integer_should_use_sql_limit assert_sql(/LIMIT|ROWNUM <=/) { Topic.take(3).entries } assert_sql(/LIMIT|ROWNUM <=/) { Topic.first(2).entries } assert_sql(/LIMIT|ROWNUM <=/) { Topic.last(5).entries } @@ -516,30 +516,16 @@ class FinderTest < ActiveRecord::TestCase assert_equal Topic.order("title").to_a.last(2), Topic.order("title").last(2) end - def test_last_with_integer_and_order_should_use_sql - relation = Topic.order("title") - assert_queries(1) { relation.last(5) } - assert !relation.loaded? + def test_last_with_integer_and_order_should_not_use_sql_limit + query = assert_sql { Topic.order("title").last(5).entries } + assert_equal 1, query.length + assert_no_match(/LIMIT/, query.first) end - def test_last_with_integer_and_reorder_should_use_sql - relation = Topic.reorder("title") - assert_queries(1) { relation.last(5) } - assert !relation.loaded? - end - - def test_last_on_loaded_relation_should_not_use_sql - relation = Topic.limit(10).load - assert_no_queries do - relation.last - relation.last(2) - end - end - - def test_last_with_irreversible_order - assert_deprecated do - Topic.order("coalesce(author_name, title)").last - end + def test_last_with_integer_and_reorder_should_not_use_sql_limit + query = assert_sql { Topic.reorder("title").last(5).entries } + assert_equal 1, query.length + assert_no_match(/LIMIT/, query.first) end def test_take_and_first_and_last_with_integer_should_return_an_array -- cgit v1.2.3 From 5966b801ced62bdfcf1c8189bc068911db90ac13 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Mon, 1 Feb 2016 22:11:52 +0100 Subject: Simplify filter normalization. Assume the filter is a string, if it wasn't a hash and isn't nil. Remove needless else and rely on Ruby's default nil return. Add spaces within hash braces. --- actionpack/lib/action_dispatch/routing/inspector.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 1ca2a3b683..115ec98ea9 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -83,11 +83,9 @@ module ActionDispatch def normalize_filter(filter) if filter.is_a?(Hash) && filter[:controller] - {controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/} - elsif filter.is_a?(String) - {controller: /#{filter}/, action: /#{filter}/} - else - nil + { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ } + elsif filter + { controller: /#{filter}/, action: /#{filter}/ } end end -- cgit v1.2.3 From 8fd123f7ebf938d21c1a097561950c6d7e1213bb Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Mon, 1 Feb 2016 12:20:44 -0700 Subject: Fix corrupt transaction state caused by `before_commit` exceptions When a `before_commit` callback raises, the database is rolled back but AR's record of the current transaction is not, leaving the connection in a perpetually broken state that affects all future users of the connection: subsequent requests, jobs, etc. They'll think a transaction is active when none is, so they won't BEGIN on their own. This manifests as missing `after_commit` callbacks and broken ROLLBACKs. This happens because `before_commit` callbacks fire before the current transaction is popped from the stack, but the exception-handling path they hit assumes that the current transaction was already popped. So the database ROLLBACK is issued, but the transaction stack is left intact. Common cause: deadlocked `#touch`, which is now implemented with `before_commit` callbacks. What's next: * We shouldn't allow active transaction state when checking in or out from the connection pool. Verify that conns are clean. * Closer review of txn manager sad paths. Are we missing other spots where we'd end up with incorrect txn state? What's the worst that can happen if txn state drifts? How can we guarantee it doesn't and contain the fallout if it does? Thanks for @tomafro for expert diagnosis! --- .../connection_adapters/abstract/transaction.rb | 9 ++++++-- .../test/cases/transaction_callbacks_test.rb | 26 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 14d04a6388..6ecdab6eb0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -167,8 +167,13 @@ module ActiveRecord def commit_transaction transaction = @stack.last - transaction.before_commit_records - @stack.pop + + begin + transaction.before_commit_records + ensure + @stack.pop + end + transaction.commit transaction.commit_records end diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 637f89196e..8a7f19293d 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -34,6 +34,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id" + before_commit { |record| record.do_before_commit(nil) } after_commit { |record| record.do_after_commit(nil) } after_create_commit { |record| record.do_after_commit(:create) } after_update_commit { |record| record.do_after_commit(:update) } @@ -47,6 +48,12 @@ class TransactionCallbacksTest < ActiveRecord::TestCase @history ||= [] end + def before_commit_block(on = nil, &block) + @before_commit ||= {} + @before_commit[on] ||= [] + @before_commit[on] << block + end + def after_commit_block(on = nil, &block) @after_commit ||= {} @after_commit[on] ||= [] @@ -59,6 +66,11 @@ class TransactionCallbacksTest < ActiveRecord::TestCase @after_rollback[on] << block end + def do_before_commit(on) + blocks = @before_commit[on] if defined?(@before_commit) + blocks.each{|b| b.call(self)} if blocks + end + def do_after_commit(on) blocks = @after_commit[on] if defined?(@after_commit) blocks.each{|b| b.call(self)} if blocks @@ -74,6 +86,20 @@ class TransactionCallbacksTest < ActiveRecord::TestCase @first = TopicWithCallbacks.find(1) end + # FIXME: Test behavior, not implementation. + def test_before_commit_exception_should_pop_transaction_stack + @first.before_commit_block { raise 'better pop this txn from the stack!' } + + original_txn = @first.class.connection.current_transaction + + begin + @first.save! + fail + rescue + assert_equal original_txn, @first.class.connection.current_transaction + end + end + def test_call_after_commit_after_transaction_commits @first.after_commit_block{|r| r.history << :after_commit} @first.after_rollback_block{|r| r.history << :after_rollback} -- cgit v1.2.3 From 84c3738c146c28790eeca99c1a20b2c6ba20e548 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Mon, 1 Feb 2016 22:15:21 +0100 Subject: Converge on filter. Some places were saying filter, while others said filter_options, spare the ambiguity and use filter throughout. This inlines a needless local variable and clarifies a route filter consists of defaults and values to match against. --- actionpack/lib/action_dispatch/routing/inspector.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 115ec98ea9..b806ee015b 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -60,8 +60,7 @@ module ActionDispatch end def format(formatter, filter = nil) - filter_options = normalize_filter(filter) - routes_to_display = filter_routes(filter_options) + routes_to_display = filter_routes(normalize_filter(filter)) routes = collect_routes(routes_to_display) if routes.none? formatter.no_routes(collect_routes(@routes)) @@ -89,10 +88,10 @@ module ActionDispatch end end - def filter_routes(filter_options) - if filter_options + def filter_routes(filter) + if filter @routes.select do |route| - filter_options.any? { |default, filter| route.defaults[default] =~ filter } + filter.any? { |default, value| route.defaults[default] =~ value } end else @routes -- cgit v1.2.3 From b801566972d3e23d5f299e914034bda2d44ce5f5 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Mon, 1 Feb 2016 22:18:19 +0100 Subject: Add spaces in the braces. Solves personal insanity, since I was down in these files. (Please don't submit PRs like this.) --- railties/lib/rails/tasks/routes.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index 499c434ffa..353b8b4e72 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -15,7 +15,7 @@ task routes: :environment do end routes_filter = nil - routes_filter = {controller: ENV['CONTROLLER']} if ENV['CONTROLLER'] + routes_filter = { controller: ENV['CONTROLLER'] } if ENV['CONTROLLER'] OptionParser.new do |opts| opts.banner = "Usage: rake routes [options]" -- cgit v1.2.3 From 49f6ce63f33b7817bcbd0cdf5f8881b63f40d9c9 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 1 Feb 2016 14:27:38 -0700 Subject: Preparing for Rails 5.0.0.beta2 --- Gemfile.lock | 68 ++++++++++++------------- RAILS_VERSION | 2 +- actioncable/CHANGELOG.md | 5 ++ actioncable/lib/action_cable/gem_version.rb | 2 +- actionmailer/CHANGELOG.md | 5 ++ actionmailer/lib/action_mailer/gem_version.rb | 2 +- actionpack/CHANGELOG.md | 7 ++- actionpack/lib/action_pack/gem_version.rb | 2 +- actionview/CHANGELOG.md | 5 ++ actionview/lib/action_view/gem_version.rb | 2 +- activejob/CHANGELOG.md | 5 ++ activejob/lib/active_job/gem_version.rb | 2 +- activemodel/CHANGELOG.md | 5 ++ activemodel/lib/active_model/gem_version.rb | 2 +- activerecord/CHANGELOG.md | 5 ++ activerecord/lib/active_record/gem_version.rb | 2 +- activesupport/CHANGELOG.md | 5 ++ activesupport/lib/active_support/gem_version.rb | 2 +- guides/CHANGELOG.md | 5 ++ railties/CHANGELOG.md | 5 ++ railties/lib/rails/gem_version.rb | 2 +- version.rb | 2 +- 22 files changed, 96 insertions(+), 46 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ba41d0705f..bd2e2c30b9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,60 +29,60 @@ GIT PATH remote: . specs: - actioncable (5.0.0.beta1.1) - actionpack (= 5.0.0.beta1.1) + actioncable (5.0.0.beta2) + actionpack (= 5.0.0.beta2) nio4r (~> 1.2) websocket-driver (~> 0.6.1) - actionmailer (5.0.0.beta1.1) - actionpack (= 5.0.0.beta1.1) - actionview (= 5.0.0.beta1.1) - activejob (= 5.0.0.beta1.1) + actionmailer (5.0.0.beta2) + actionpack (= 5.0.0.beta2) + actionview (= 5.0.0.beta2) + activejob (= 5.0.0.beta2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (5.0.0.beta1.1) - actionview (= 5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) + actionpack (5.0.0.beta2) + actionview (= 5.0.0.beta2) + activesupport (= 5.0.0.beta2) rack (~> 2.x) rack-test (~> 0.6.3) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) + actionview (5.0.0.beta2) + activesupport (= 5.0.0.beta2) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) + activejob (5.0.0.beta2) + activesupport (= 5.0.0.beta2) globalid (>= 0.3.6) - activemodel (5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) - activerecord (5.0.0.beta1.1) - activemodel (= 5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) + activemodel (5.0.0.beta2) + activesupport (= 5.0.0.beta2) + activerecord (5.0.0.beta2) + activemodel (= 5.0.0.beta2) + activesupport (= 5.0.0.beta2) arel (~> 7.0) - activesupport (5.0.0.beta1.1) + activesupport (5.0.0.beta2) concurrent-ruby (~> 1.0) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) method_source minitest (~> 5.1) tzinfo (~> 1.1) - rails (5.0.0.beta1.1) - actioncable (= 5.0.0.beta1.1) - actionmailer (= 5.0.0.beta1.1) - actionpack (= 5.0.0.beta1.1) - actionview (= 5.0.0.beta1.1) - activejob (= 5.0.0.beta1.1) - activemodel (= 5.0.0.beta1.1) - activerecord (= 5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) + rails (5.0.0.beta2) + actioncable (= 5.0.0.beta2) + actionmailer (= 5.0.0.beta2) + actionpack (= 5.0.0.beta2) + actionview (= 5.0.0.beta2) + activejob (= 5.0.0.beta2) + activemodel (= 5.0.0.beta2) + activerecord (= 5.0.0.beta2) + activesupport (= 5.0.0.beta2) bundler (>= 1.3.0, < 2.0) - railties (= 5.0.0.beta1.1) + railties (= 5.0.0.beta2) sprockets-rails (>= 2.0.0) - railties (5.0.0.beta1.1) - actionpack (= 5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) + railties (5.0.0.beta2) + actionpack (= 5.0.0.beta2) + activesupport (= 5.0.0.beta2) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) @@ -164,7 +164,7 @@ GEM mysql2 (0.4.2) mysql2 (0.4.2-x64-mingw32) mysql2 (0.4.2-x86-mingw32) - nio4r (1.2.0) + nio4r (1.2.1) nokogiri (1.6.7.2) mini_portile2 (~> 2.0.0.rc2) nokogiri (1.6.7.2-x64-mingw32) @@ -234,7 +234,7 @@ GEM sprockets (3.5.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.0.0) + sprockets-rails (3.0.1) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) diff --git a/RAILS_VERSION b/RAILS_VERSION index 8422265fdd..60b8d0bf66 100644 --- a/RAILS_VERSION +++ b/RAILS_VERSION @@ -1 +1 @@ -5.0.0.beta1.1 +5.0.0.beta2 diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index a33b7b6b4b..5c968a48fc 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + * Create notion of an `ActionCable::SubscriptionAdapter`. Separate out Redis functionality into `ActionCable::SubscriptionAdapter::Redis`, and add a diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb index c652fb91ae..a71603e61a 100644 --- a/actioncable/lib/action_cable/gem_version.rb +++ b/actioncable/lib/action_cable/gem_version.rb @@ -8,7 +8,7 @@ module ActionCable MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index e74e0bc20a..22e9bd12a1 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + ## Rails 5.0.0.beta1 (December 18, 2015) ## * `config.force_ssl = true` will set diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb index d28568b6a1..a1ee5fb238 100644 --- a/actionmailer/lib/action_mailer/gem_version.rb +++ b/actionmailer/lib/action_mailer/gem_version.rb @@ -8,7 +8,7 @@ module ActionMailer MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index b2d9288eca..2aa78d20c0 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + * Add `-g` and `-c` (short for _grep_ and _controller_ respectively) options to `bin/rake routes`. These options return the url `name`, `verb` and `path` field that match the pattern or match a specific controller. @@ -7,7 +12,7 @@ See #18902. *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 `fresh_when` and `stale?` are weak. diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb index 3a1410d492..778c5482d3 100644 --- a/actionpack/lib/action_pack/gem_version.rb +++ b/actionpack/lib/action_pack/gem_version.rb @@ -8,7 +8,7 @@ module ActionPack MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index d85681e0d1..630a6b1b04 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + * Fix stripping the digest from the automatically generated img tag alt attribute when assets are handled by Sprockets >=3.0. diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb index 23d5319579..bb5c96cb39 100644 --- a/actionview/lib/action_view/gem_version.rb +++ b/actionview/lib/action_view/gem_version.rb @@ -8,7 +8,7 @@ module ActionView MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 8687af5eba..981b6a20ec 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + ## Rails 5.0.0.beta1 (December 18, 2015) ## * Fixed serializing `:at` option for `assert_enqueued_with` diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb index 0bdeca76a8..bc88221027 100644 --- a/activejob/lib/active_job/gem_version.rb +++ b/activejob/lib/active_job/gem_version.rb @@ -8,7 +8,7 @@ module ActiveJob MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index e4769d2f40..318e507ff1 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + ## Rails 5.0.0.beta1 (December 18, 2015) ## * Validate multiple contexts on `valid?` and `invalid?` at once. diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb index 4563f860c3..94514a0657 100644 --- a/activemodel/lib/active_model/gem_version.rb +++ b/activemodel/lib/active_model/gem_version.rb @@ -8,7 +8,7 @@ module ActiveModel MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 50a0b291b3..63a7dd4968 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + * `ActiveRecord::Relation#reverse_order` throws `ActiveRecord::IrreversibleOrderError` when the order can not be reversed using current trivial algorithm. Also raises the same error when `#reverse_order` is called on diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb index b4f2f66e1c..aa1f5c4fb4 100644 --- a/activerecord/lib/active_record/gem_version.rb +++ b/activerecord/lib/active_record/gem_version.rb @@ -8,7 +8,7 @@ module ActiveRecord MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 8cff374a47..d20617a194 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + * Change number_to_currency behavior for checking negativity. Used `to_f.negative` instead of using `to_f.phase` for checking negativity diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index 3104d0e00f..fc08273b6d 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -8,7 +8,7 @@ module ActiveSupport MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index aae405d5ac..d58016053b 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + ## Rails 5.0.0.beta1 (December 18, 2015) ## * Add code of conduct to contributing guide diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index f94bb49135..e98180d126 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + * Add `after_bundle` callbacks in Rails plugin templates. Useful for allowing templates to perform actions that are dependent upon `bundle install`. diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb index f4d5b660dc..93e0151602 100644 --- a/railties/lib/rails/gem_version.rb +++ b/railties/lib/rails/gem_version.rb @@ -8,7 +8,7 @@ module Rails MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/version.rb b/version.rb index f4d5b660dc..93e0151602 100644 --- a/version.rb +++ b/version.rb @@ -8,7 +8,7 @@ module Rails MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end -- cgit v1.2.3 From 60b040e362086fa11f86d35938d515145241174e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 1 Feb 2016 19:57:31 -0200 Subject: Add some Action Cable CHANGELOG entries And improve changelongs. [ci skip] --- actioncable/CHANGELOG.md | 11 ++++++++++- actionpack/CHANGELOG.md | 3 --- actionview/CHANGELOG.md | 3 --- activerecord/CHANGELOG.md | 3 --- activesupport/CHANGELOG.md | 3 --- railties/CHANGELOG.md | 4 +--- 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index 5c968a48fc..e671a07563 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,7 +1,16 @@ ## Rails 5.0.0.beta2 (February 01, 2016) ## -* No changes. +* Support PostgreSQL pubsub adapter. + *Jon Moss* + +* Remove EventMachine dependency. + + *Matthew Draper* + +* Remove Celluloid dependency. + + *Mike Perham* * Create notion of an `ActionCable::SubscriptionAdapter`. Separate out Redis functionality into diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 2aa78d20c0..710751ff1b 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,8 +1,5 @@ ## Rails 5.0.0.beta2 (February 01, 2016) ## -* No changes. - - * Add `-g` and `-c` (short for _grep_ and _controller_ respectively) options to `bin/rake routes`. These options return the url `name`, `verb` and `path` field that match the pattern or match a specific controller. diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 630a6b1b04..256b90784a 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,8 +1,5 @@ ## Rails 5.0.0.beta2 (February 01, 2016) ## -* No changes. - - * Fix stripping the digest from the automatically generated img tag alt attribute when assets are handled by Sprockets >=3.0. diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 63a7dd4968..3b67c6f495 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,8 +1,5 @@ ## Rails 5.0.0.beta2 (February 01, 2016) ## -* No changes. - - * `ActiveRecord::Relation#reverse_order` throws `ActiveRecord::IrreversibleOrderError` when the order can not be reversed using current trivial algorithm. Also raises the same error when `#reverse_order` is called on diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index d20617a194..a35214bbe1 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,8 +1,5 @@ ## Rails 5.0.0.beta2 (February 01, 2016) ## -* No changes. - - * Change number_to_currency behavior for checking negativity. Used `to_f.negative` instead of using `to_f.phase` for checking negativity diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index e98180d126..96149acc08 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,8 +1,5 @@ ## Rails 5.0.0.beta2 (February 01, 2016) ## -* No changes. - - * Add `after_bundle` callbacks in Rails plugin templates. Useful for allowing templates to perform actions that are dependent upon `bundle install`. @@ -26,6 +23,7 @@ *Will Fisher* + ## Rails 5.0.0.beta1 (December 18, 2015) ## * Newly generated plugins get a `README.md` in Markdown. -- cgit v1.2.3 From c4ac23bfa68d07b58e0ff32dfca1fee2b3283542 Mon Sep 17 00:00:00 2001 From: Kang-Kyu Lee Date: Mon, 1 Feb 2016 14:21:34 -0800 Subject: Update CHANGELOG.md fix indentation to show it as code --- actionpack/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 710751ff1b..809b735deb 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -183,11 +183,11 @@ * Accessing mime types via constants like `Mime::HTML` is deprecated. Please change code like this: - Mime::HTML + Mime::HTML To this: - Mime[:html] + Mime[:html] This change is so that Rails will not manage a list of constants, and fixes an issue where if a type isn't registered you could possibly get the wrong -- cgit v1.2.3 From 96b9609e2b006d7b75d099a29729fe8d805f12bf Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 2 Feb 2016 09:04:10 +0900 Subject: :warning: assigned but unused variable - err --- railties/lib/rails/commands/runner.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index babb197ba1..5844e9037c 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -59,8 +59,8 @@ elsif File.exist?(code_or_file) Kernel.load code_or_file else begin - eval(code_or_file, binding, __FILE__, __LINE__) - rescue SyntaxError,NameError => err + 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." exit 1 -- cgit v1.2.3 From 538bce1f7c676f4a5b3d800ed0f68ec065776a7f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 1 Feb 2016 17:17:56 -0800 Subject: Generated engines should protect from forgery Generated engines should call `protect_from_forgery`. If this method isn't called, then the Engine could be susceptible to XSS attacks. Thanks @tomekr for reporting this to us! --- .../app/controllers/%namespaced_name%/application_controller.rb.tt | 1 + railties/test/generators/plugin_generator_test.rb | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt index 7fe4e5034d..83807f14b4 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt @@ -1,5 +1,6 @@ <%= wrap_in_modules <<-rb.strip_heredoc class ApplicationController < ActionController::#{api? ? "API" : "Base"} + #{ api? ? '# ' : '' }protect_from_forgery :with => :exception end rb %> diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 0fd1d34131..6e5132e849 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -335,7 +335,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Hyphenated::Name\n end\n end\nend/ assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/ assert_file "hyphenated-name/test/dummy/config/routes.rb", /mount Hyphenated::Name::Engine => "\/hyphenated-name"/ - assert_file "hyphenated-name/app/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/ + assert_file "hyphenated-name/app/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery :with => :exception\n end\n end\nend\n/ assert_file "hyphenated-name/app/models/hyphenated/name/application_record.rb", /module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/ assert_file "hyphenated-name/app/jobs/hyphenated/name/application_job.rb", /module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/ assert_file "hyphenated-name/app/mailers/hyphenated/name/application_mailer.rb", /module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\nend/ @@ -357,7 +357,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace MyHyphenated::Name\n end\n end\nend/ assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/ assert_file "my_hyphenated-name/test/dummy/config/routes.rb", /mount MyHyphenated::Name::Engine => "\/my_hyphenated-name"/ - assert_file "my_hyphenated-name/app/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/ + assert_file "my_hyphenated-name/app/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery :with => :exception\n end\n end\nend\n/ assert_file "my_hyphenated-name/app/models/my_hyphenated/name/application_record.rb", /module MyHyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/ assert_file "my_hyphenated-name/app/jobs/my_hyphenated/name/application_job.rb", /module MyHyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/ assert_file "my_hyphenated-name/app/mailers/my_hyphenated/name/application_mailer.rb", /module MyHyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\nend/ @@ -379,7 +379,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/engine.rb", /module Deep\n module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Deep::Hyphenated::Name\n end\n end\n end\nend/ assert_file "deep-hyphenated-name/lib/deep/hyphenated/name.rb", /require "deep\/hyphenated\/name\/engine"/ assert_file "deep-hyphenated-name/test/dummy/config/routes.rb", /mount Deep::Hyphenated::Name::Engine => "\/deep-hyphenated-name"/ - assert_file "deep-hyphenated-name/app/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\n end\nend/ + assert_file "deep-hyphenated-name/app/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery :with => :exception\n end\n end\n end\nend\n/ assert_file "deep-hyphenated-name/app/models/deep/hyphenated/name/application_record.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\n end\nend/ assert_file "deep-hyphenated-name/app/jobs/deep/hyphenated/name/application_job.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/ assert_file "deep-hyphenated-name/app/mailers/deep/hyphenated/name/application_mailer.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\n end\nend/ -- cgit v1.2.3 From c918a3a0691d54f0702c26aac3f47e9449fd2a24 Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Mon, 1 Feb 2016 21:16:07 -0500 Subject: Fix regression in `Hash#dig` for HashWithIndifferentAccess. --- activesupport/CHANGELOG.md | 3 +++ .../lib/active_support/hash_with_indifferent_access.rb | 10 +++++++--- activesupport/test/core_ext/hash_ext_test.rb | 6 ++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index a35214bbe1..bd333da081 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,6 @@ +* Fix regression in `Hash#dig` for HashWithIndifferentAccess. + *Jon Moss* + ## Rails 5.0.0.beta2 (February 01, 2016) ## * Change number_to_currency behavior for checking negativity. diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index b878f31e75..03770a197c 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -69,9 +69,13 @@ module ActiveSupport end def default(*args) - key = args.first - args[0] = key.to_s if key.is_a?(Symbol) - super(*args) + arg_key = args.first + + if include?(key = convert_key(arg_key)) + self[key] + else + super + end end def self.new_from_hash_copying_default(hash) diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 1b66f784e4..be8583e704 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -702,6 +702,12 @@ class HashExtTest < ActiveSupport::TestCase assert_equal h.class, h.dup.class end + def test_nested_dig_indifferent_access + skip if RUBY_VERSION < "2.3.0" + data = {"this" => {"views" => 1234}}.with_indifferent_access + assert_equal 1234, data.dig(:this, :views) + end + def test_assert_valid_keys assert_nothing_raised do { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ]) -- cgit v1.2.3 From b700d4811dbc1ba5efcfd618f8ee0bf0b9e22d4c Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Tue, 2 Feb 2016 15:36:12 +0900 Subject: move `test_generator_if_skip_action_cable_is_given_for_an_api_app` to the appropriate file Test of Rails API should be in `api_app_generator_test.rb`. --- railties/test/generators/api_app_generator_test.rb | 10 ++++++++++ railties/test/generators/app_generator_test.rb | 11 ----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index d9eb7770f3..1ea5661006 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -52,6 +52,16 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase assert_file "app/controllers/application_controller.rb", /ActionController::API/ 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/channels" + assert_file "Gemfile" do |content| + assert_no_match(/redis/, content) + end + end + private def default_files diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 937f68fff8..9595d2a0ef 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -413,17 +413,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generator_if_skip_action_cable_is_given_for_an_api_app - run_generator [destination_root, "--skip-action-cable", "--api"] - assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/ - assert_no_file "config/cable.yml" - assert_no_file "app/assets/javascripts/cable.coffee" - assert_no_file "app/channels" - assert_file "Gemfile" do |content| - assert_no_match(/redis/, content) - end - end - def test_action_cable_redis_gems run_generator assert_gem 'redis' -- cgit v1.2.3 From daf0f23b77a582340c0a52a3024069fd21ade633 Mon Sep 17 00:00:00 2001 From: yui-knk Date: Tue, 2 Feb 2016 20:36:44 +0900 Subject: Remove unnecessary overriding of `#initialize` `#initialize` of `HasManyReflection`, `HasOneReflection` and `BelongsToReflection` only pass all arguments to `super` by passed order. These overriding can be removed. --- activerecord/lib/active_record/reflection.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 823ca1f54f..ab93d97eb3 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -594,10 +594,6 @@ module ActiveRecord end class HasManyReflection < AssociationReflection # :nodoc: - def initialize(name, scope, options, active_record) - super(name, scope, options, active_record) - end - def macro; :has_many; end def collection?; true; end @@ -612,10 +608,6 @@ module ActiveRecord end class HasOneReflection < AssociationReflection # :nodoc: - def initialize(name, scope, options, active_record) - super(name, scope, options, active_record) - end - def macro; :has_one; end def has_one?; true; end @@ -636,10 +628,6 @@ module ActiveRecord end class BelongsToReflection < AssociationReflection # :nodoc: - def initialize(name, scope, options, active_record) - super(name, scope, options, active_record) - end - def macro; :belongs_to; end def belongs_to?; true; end -- cgit v1.2.3 From ac1427d6ca8afbf2cd4245f994084b30ed256581 Mon Sep 17 00:00:00 2001 From: Ryan Nielson Date: Tue, 2 Feb 2016 09:20:10 -0400 Subject: Change command_task.rb to commands_task.rb in docs The initialization documentation references `rails/commands/command_task.rb`. This appears to be a typo as the file is actually `rails/commands/commands_task.rb`. --- guides/source/initialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 6232ef4c57..156f9c92b4 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -157,7 +157,7 @@ snippet. If we had used `s` rather than `server`, Rails would have used the `aliases` defined here to find the matching command. -### `rails/commands/command_tasks.rb` +### `rails/commands/commands_tasks.rb` When one types a valid Rails command, `run_command!` a method of the same name is called. If Rails doesn't recognize the command, it tries to run a Rake task -- cgit v1.2.3 From 5bb26008ce7c979a87f0d8aef0dd10514838787f Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 2 Feb 2016 09:15:47 -0700 Subject: Avoid infinite recursion when bad values are passed to tz aware fields We had previously updated this to attempt to map over whatever was passed in, so that additional types like range and array could benefit from this behavior without the time zone converter having to deal with every known type. However, the default behavior of a type is to just yield the given value to `map`, which means that if we don't actually know how to handle a value, we'll just recurse infinitely. Since both uses of `map` in this case occur in cases where we know receiving the same object will recurse, we can just break on reference equality. Fixes #23241. --- .../attribute_methods/time_zone_conversion.rb | 14 ++++++++++++-- activerecord/test/cases/attribute_methods_test.rb | 7 +++++++ 2 files changed, 19 insertions(+), 2 deletions(-) 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 061628725d..6d345689fa 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -20,7 +20,7 @@ module ActiveRecord nil end else - map(super) { |t| cast(t) } + map_avoiding_infinite_recursion(super) { |v| cast(v) } end end @@ -34,13 +34,23 @@ module ActiveRecord elsif value.is_a?(::Float) value else - map(value) { |v| convert_time_to_time_zone(v) } + map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) } end end def set_time_zone_without_conversion(value) ::Time.zone.local_to_utc(value).in_time_zone end + + def map_avoiding_infinite_recursion(value) + map(value) do |v| + if value.equal?(v) + nil + else + yield(value) + end + end + end end extend ActiveSupport::Concern diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 94dfbc3346..ef84624a8d 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -714,6 +714,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_time_zone_aware_attributes_dont_recurse_infinitely_on_invalid_values + in_time_zone "Pacific Time (US & Canada)" do + record = @target.new(bonus_time: []) + assert_equal nil, record.bonus_time + end + end + def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable Topic.skip_time_zone_conversion_for_attributes = [:field_a] Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b] -- cgit v1.2.3 From 473f63734a55d2a91083a619ce1395dfe32aec8d Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 2 Feb 2016 10:30:44 -0700 Subject: Ensure tz aware attributes continue to work with arrays There was a typo in the variable name leading to infinite recursion --- .../lib/active_record/attribute_methods/time_zone_conversion.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6d345689fa..ebaaa54b2b 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -47,7 +47,7 @@ module ActiveRecord if value.equal?(v) nil else - yield(value) + yield(v) end end end -- cgit v1.2.3 From 830543738507f49444956b3b6ae897f4638b2523 Mon Sep 17 00:00:00 2001 From: Nick Quaranto Date: Tue, 2 Feb 2016 11:16:41 -0500 Subject: [ci skip] Several ActionCable documentation updates: * Properly indent code sample in ActionCable::Channel::Streams * Add a doc comment for #stop_all_streams * Reformat + add blocks around code references in ActionCable::Base docs * Clarify and a little better grammar on ActionCable::RemoteConnections * Correct indentation and clean up ActionCable::Server::Broadcasting code sample --- actioncable/lib/action_cable/channel/base.rb | 33 ++++++++++++++-------- actioncable/lib/action_cable/channel/streams.rb | 26 +++++++++-------- actioncable/lib/action_cable/remote_connections.rb | 12 ++++---- .../lib/action_cable/server/broadcasting.rb | 22 +++++++-------- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb index 88cdc1cab1..874ebe2e71 100644 --- a/actioncable/lib/action_cable/channel/base.rb +++ b/actioncable/lib/action_cable/channel/base.rb @@ -32,8 +32,11 @@ module ActionCable # # == Action processing # - # Unlike Action Controllers, channels do not follow a REST constraint form for its actions. It's a remote-procedure call model. You can - # declare any public method on the channel (optionally taking a data argument), and this method is automatically exposed as callable to the client. + # Unlike subclasses of ActionController::Base, channels do not follow a REST + # constraint form for their actions. Instead, ActionCable operates through a + # remote-procedure call model. You can declare any public method on the + # channel (optionally taking a data argument), and this method is + # automatically exposed as callable to the client. # # Example: # @@ -60,18 +63,22 @@ module ActionCable # end # end # - # In this example, subscribed/unsubscribed are not callable methods, as they were already declared in ActionCable::Channel::Base, but #appear/away - # are. #generate_connection_token is also not callable as its a private method. You'll see that appear accepts a data parameter, which it then - # uses as part of its model call. #away does not, it's simply a trigger action. + # In this example, subscribed/unsubscribed are not callable methods, as they + # were already declared in ActionCable::Channel::Base, but #appear + # and #away are. #generate_connection_token is also not + # callable as it's a private method. You'll see that appear accepts a data + # parameter, which it then uses as part of its model call. #away + # does not, since it's simply a trigger action. # - # Also note that in this example, current_user is available because it was marked as an identifying attribute on the connection. - # All such identifiers will automatically create a delegation method of the same name on the channel instance. + # Also note that in this example, current_user is available because + # it was marked as an identifying attribute on the connection. All such + # identifiers will automatically create a delegation method of the same name + # on the channel instance. # # == Rejecting subscription requests # - # A channel can reject a subscription request in the #subscribed callback by invoking #reject! - # - # Example: + # A channel can reject a subscription request in the #subscribed callback by + # invoking the #reject method: # # class ChatChannel < ApplicationCable::Channel # def subscribed @@ -80,8 +87,10 @@ module ActionCable # end # end # - # In this example, the subscription will be rejected if the current_user does not have access to the chat room. - # On the client-side, Channel#rejected callback will get invoked when the server rejects the subscription request. + # In this example, the subscription will be rejected if the + # current_user does not have access to the chat room. On the + # client-side, the Channel#rejected callback will get invoked when + # the server rejects the subscription request. class Base include Callbacks include PeriodicTimers diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb index a26373e387..3158f30814 100644 --- a/actioncable/lib/action_cable/channel/streams.rb +++ b/actioncable/lib/action_cable/channel/streams.rb @@ -41,22 +41,23 @@ module ActionCable # Example below shows how you can use this to provide performance introspection in the process: # # class ChatChannel < ApplicationCable::Channel - # def subscribed - # @room = Chat::Room[params[:room_number]] + # def subscribed + # @room = Chat::Room[params[:room_number]] # - # stream_for @room, -> (encoded_message) do - # message = ActiveSupport::JSON.decode(encoded_message) + # stream_for @room, -> (encoded_message) do + # message = ActiveSupport::JSON.decode(encoded_message) # - # if message['originated_at'].present? - # elapsed_time = (Time.now.to_f - message['originated_at']).round(2) + # if message['originated_at'].present? + # elapsed_time = (Time.now.to_f - message['originated_at']).round(2) # - # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing - # logger.info "Message took #{elapsed_time}s to arrive" - # end + # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing + # logger.info "Message took #{elapsed_time}s to arrive" + # end # - # transmit message - # end - # end + # transmit message + # end + # end + # end # # You can stop streaming from all broadcasts by calling #stop_all_streams. module Streams @@ -90,6 +91,7 @@ module ActionCable stream_from(broadcasting_for([ channel_name, model ]), callback) end + # Unsubscribes all streams associated with this channel from the pubsub queue. def stop_all_streams streams.each do |broadcasting, callback| pubsub.unsubscribe broadcasting, callback diff --git a/actioncable/lib/action_cable/remote_connections.rb b/actioncable/lib/action_cable/remote_connections.rb index aa2fc95d2f..7ec121308a 100644 --- a/actioncable/lib/action_cable/remote_connections.rb +++ b/actioncable/lib/action_cable/remote_connections.rb @@ -1,6 +1,7 @@ module ActionCable - # If you need to disconnect a given connection, you go through the RemoteConnections. You find the connections you're looking for by - # searching the identifier declared on the connection. Example: + # If you need to disconnect a given connection, you can go through the + # RemoteConnections. You can find the connections you're looking for by + # searching for the identifier declared on the connection. For example: # # module ApplicationCable # class Connection < ActionCable::Connection::Base @@ -11,8 +12,9 @@ module ActionCable # # ActionCable.server.remote_connections.where(current_user: User.find(1)).disconnect # - # That will disconnect all the connections established for User.find(1) across all servers running on all machines (because it uses - # the internal channel that all these servers are subscribed to). + # This will disconnect all the connections established for + # User.find(1) across all servers running on all machines, because + # it uses the internal channel that all these servers are subscribed to. class RemoteConnections attr_reader :server @@ -25,7 +27,7 @@ module ActionCable end private - # Represents a single remote connection found via ActionCable.server.remote_connections.where(*). + # Represents a single remote connection found via ActionCable.server.remote_connections.where(*). # Exists for the solely for the purpose of calling #disconnect on that connection. class RemoteConnection class InvalidIdentifiersError < StandardError; end diff --git a/actioncable/lib/action_cable/server/broadcasting.rb b/actioncable/lib/action_cable/server/broadcasting.rb index 4a26ed9269..7e8aef45f4 100644 --- a/actioncable/lib/action_cable/server/broadcasting.rb +++ b/actioncable/lib/action_cable/server/broadcasting.rb @@ -4,19 +4,19 @@ module ActionCable # broadcastings are streamed directly to the clients subscribed to the named broadcasting. Let's explain with a full-stack example: # # class WebNotificationsChannel < ApplicationCable::Channel - # def subscribed - # stream_from "web_notifications_#{current_user.id}" - # end - # end + # def subscribed + # stream_from "web_notifications_#{current_user.id}" + # end + # end # - # # Somewhere in your app this is called, perhaps from a NewCommentJob - # ActionCable.server.broadcast \ - # "web_notifications_1", { title: 'New things!', body: 'All shit fit for print' } + # # Somewhere in your app this is called, perhaps from a NewCommentJob + # ActionCable.server.broadcast \ + # "web_notifications_1", { title: "New things!", body: "All that's fit for print" } # - # # Client-side coffescript, which assumes you've already requested the right to send web notifications - # App.cable.subscriptions.create "WebNotificationsChannel", - # received: (data) -> - # new Notification data['title'], body: data['body'] + # # Client-side CoffeeScript, which assumes you've already requested the right to send web notifications + # App.cable.subscriptions.create "WebNotificationsChannel", + # received: (data) -> + # new Notification data['title'], body: data['body'] module Broadcasting # Broadcast a hash directly to a named broadcasting. It'll automatically be JSON encoded. def broadcast(broadcasting, message) -- cgit v1.2.3 From 789fabf01f13fa30dd5791baf77cd51e6199eeab Mon Sep 17 00:00:00 2001 From: Zachary Smith Date: Tue, 2 Feb 2016 13:45:12 -0700 Subject: Fix typo. --- guides/source/getting_started.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 3392dad897..2cbc591629 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -767,7 +767,7 @@ Why do you have to bother? The ability to grab and automatically assign all controller parameters to your model in one shot makes the programmer's job easier, but this convenience also allows malicious use. What if a request to the server was crafted to look like a new article form submit but also included -extra fields with values that violated your applications integrity? They would +extra fields with values that violated your application's integrity? They would be 'mass assigned' into your model and then into the database along with the good stuff - potentially breaking your application or worse. @@ -1540,7 +1540,7 @@ This is very similar to the `Article` model that you saw earlier. The difference is the line `belongs_to :article`, which sets up an Active Record _association_. You'll learn a little about associations in the next section of this guide. -The (`:references`) keyword used in the bash command is a special data type for models. +The (`:references`) keyword used in the bash command is a special data type for models. It creates a new column on your database table with the provided model name appended with an `_id` that can hold integer values. You can get a better understanding after analyzing the `db/schema.rb` file below. -- cgit v1.2.3 From 85a3e0fa82aaa42c2d5a179a94ed84f2b2abbd16 Mon Sep 17 00:00:00 2001 From: Remo Mueller Date: Tue, 2 Feb 2016 16:51:07 -0500 Subject: The minimum supported version of PostgreSQL is now >= 9.1 --- activerecord/CHANGELOG.md | 6 ++++++ .../lib/active_record/connection_adapters/postgresql_adapter.rb | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 3b67c6f495..4ca33491aa 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,9 @@ +* Bumped the minimum supported version of PostgreSQL to >= 9.1. + Both PG 9.0 and 8.4 are past their end of life date: + http://www.postgresql.org/support/versioning/ + + *Remo Mueller* + ## Rails 5.0.0.beta2 (February 01, 2016) ## * `ActiveRecord::Relation#reverse_order` throws `ActiveRecord::IrreversibleOrderError` diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index d69c2e186b..a6d9a47b90 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -214,7 +214,7 @@ module ActiveRecord @statements = StatementPool.new @connection, self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }) - if postgresql_version < 80200 + if postgresql_version < 90100 raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!" end @@ -297,9 +297,8 @@ module ActiveRecord true end - # Returns true if pg > 9.1 def supports_extensions? - postgresql_version >= 90100 + true end # Range datatypes weren't introduced until PostgreSQL 9.2 -- cgit v1.2.3 From fae17243984965b152d8212be6405ce840887018 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Wed, 3 Feb 2016 08:16:18 +0900 Subject: use rails command in routes task For other task has become to use the rails command at doc and test, I think that routes task also it is better to use the rails command. --- guides/source/routing.md | 14 +++++++------- railties/lib/rails/tasks/routes.rake | 6 +++--- railties/test/application/rake_test.rb | 14 +++++++------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/guides/source/routing.md b/guides/source/routing.md index d9e64d56ac..777d1d24b6 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -1139,18 +1139,18 @@ edit_user GET /users/:id/edit(.:format) users#edit You can search through your routes with the --grep option (-g for short). This outputs any routes that partially match the URL helper method name, the HTTP verb, or the URL path. ``` -$ bin/rake routes --grep new_comment -$ bin/rake routes -g POST -$ bin/rake routes -g admin +$ bin/rails routes --grep new_comment +$ bin/rails routes -g POST +$ bin/rails routes -g admin ``` If you only want to see the routes that map to a specific controller, there's the --controller option (-c for short). ``` -$ bin/rake routes --controller users -$ bin/rake routes --controller admin/users -$ bin/rake routes -c Comments -$ bin/rake routes -c Articles::CommentsController +$ bin/rails routes --controller users +$ bin/rails routes --controller admin/users +$ bin/rails routes -c Comments +$ bin/rails routes -c Articles::CommentsController ``` TIP: You'll find that the output from `rails routes` is much more readable if you widen your terminal window until the output lines don't wrap. diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index 353b8b4e72..939fa59c75 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -9,8 +9,8 @@ task routes: :environment do inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes) if ARGV.any?{ |argv| argv.start_with? 'CONTROLLER' } puts <<-eow.strip_heredoc - Passing `CONTROLLER` to `bin/rake routes` is deprecated and will be removed in Rails 5.1. - Please use `bin/rake routes -c controller_name` instead. + Passing `CONTROLLER` to `bin/rails routes` is deprecated and will be removed in Rails 5.1. + Please use `bin/rails routes -c controller_name` instead. eow end @@ -18,7 +18,7 @@ task routes: :environment do routes_filter = { controller: ENV['CONTROLLER'] } if ENV['CONTROLLER'] OptionParser.new do |opts| - opts.banner = "Usage: rake routes [options]" + opts.banner = "Usage: rails routes [options]" opts.on("-c", "--controller [CONTROLLER]") do |controller| routes_filter = { controller: controller } end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 7171aa6e1a..745a3e3ec5 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -141,9 +141,9 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path){ `bin/rake routes CONTROLLER=cart` } - assert_equal ["Passing `CONTROLLER` to `bin/rake routes` is deprecated and will be removed in Rails 5.1.", - "Please use `bin/rake routes -c controller_name` instead.", + output = Dir.chdir(app_path){ `bin/rails routes CONTROLLER=cart` } + assert_equal ["Passing `CONTROLLER` to `bin/rails routes` is deprecated and will be removed in Rails 5.1.", + "Please use `bin/rails routes -c controller_name` instead.", "Prefix Verb URI Pattern Controller#Action", " cart GET /cart(.:format) cart#show\n"].join("\n"), output @@ -183,7 +183,7 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path){ `bin/rake routes -g show` } + output = Dir.chdir(app_path){ `bin/rails routes -g show` } assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output end @@ -195,13 +195,13 @@ module ApplicationTests end RUBY - output = Dir.chdir(app_path){ `bin/rake routes -c cart` } + output = Dir.chdir(app_path){ `bin/rails routes -c cart` } assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output - output = Dir.chdir(app_path){ `bin/rake routes -c Cart` } + output = Dir.chdir(app_path){ `bin/rails routes -c Cart` } assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output - output = Dir.chdir(app_path){ `bin/rake routes -c CartController` } + output = Dir.chdir(app_path){ `bin/rails routes -c CartController` } assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output end -- cgit v1.2.3 From c8ac079413acca3f62d4e15bb1b5a1c5bf7d2039 Mon Sep 17 00:00:00 2001 From: Ryo Hashimoto Date: Wed, 3 Feb 2016 10:13:38 +0900 Subject: use rails secret in rails guides --- guides/source/security.md | 2 +- guides/source/upgrading_ruby_on_rails.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/source/security.md b/guides/source/security.md index 1d0e87d831..96b9f4bcce 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -102,7 +102,7 @@ Thus the session becomes a more secure place to store data. The encryption is done using a server-side secret key `secrets.secret_key_base` stored in `config/secrets.yml`. -That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rake secret` instead_. +That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rails secret` instead_. `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`, e.g.: diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 202e5b5cb9..e631445492 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -402,7 +402,7 @@ secrets, you need to: 3. Remove the `secret_token.rb` initializer. -4. Use `rake secret` to generate new keys for the `development` and `test` sections. +4. Use `rails secret` to generate new keys for the `development` and `test` sections. 5. Restart your server. -- cgit v1.2.3 From 522099a13ffea611dfb37d4d22da62eb8cb81c12 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 2 Feb 2016 18:55:02 -0700 Subject: Sleep well, sweet prince Prototype, you have served us well. But you are no longer how we make an XMLHttpRequest. RIP --- actionpack/lib/action_dispatch/testing/integration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 711ca10419..6f51accee7 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -71,7 +71,7 @@ module ActionDispatch end # Performs an XMLHttpRequest request with the given parameters, mirroring - # a request from the Prototype library. + # an AJAX request made from JavaScript. # # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart -- cgit v1.2.3 From 9c9fb19b9eda46a76d7ce4fd6cb9cc94bd965e62 Mon Sep 17 00:00:00 2001 From: Himesh Date: Thu, 23 Jul 2015 15:49:22 +0530 Subject: Changed id-writer to save join table records based on association primary key #20995 Changed id-writer to save join table records based on association primary key --- .../lib/active_record/associations/collection_association.rb | 5 ++++- .../test/cases/associations/has_many_through_associations_test.rb | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 87576abd92..333dd4f180 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -72,7 +72,10 @@ module ActiveRecord pk_type = reflection.primary_key_type ids = Array(ids).reject(&:blank?) ids.map! { |i| pk_type.cast(i) } - replace(klass.find(ids).index_by(&:id).values_at(*ids)) + records = klass.where(reflection.association_primary_key => ids).index_by do |r| + r.send(reflection.association_primary_key) + end.values_at(*ids) + replace(records) end def reset diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index ce2557339e..20ced1feb5 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -882,7 +882,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set company = companies(:rails_core) ids = [Developer.first.id, -9999] - assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids} + assert_raises(ActiveRecord::AssociationTypeMismatch) {company.developer_ids= ids} end def test_build_a_model_from_hm_through_association_with_where_clause -- cgit v1.2.3 From 7ec5f9f8ddb7f41c9406ec2d40e66daaf801639c Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Wed, 3 Feb 2016 10:39:10 +0530 Subject: Mention supported PG version in the error message. --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index a6d9a47b90..beaeef3c78 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -215,7 +215,7 @@ module ActiveRecord self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }) if postgresql_version < 90100 - raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!" + raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1." end add_pg_decoders -- cgit v1.2.3 From 52b3226a7a8662e8c975ad7e820085451e21b8cf Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Wed, 3 Feb 2016 10:41:29 +0530 Subject: Mention PostgreSQL version support in release notes [ci skip] --- guides/source/5_0_release_notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index f45c8005da..4e8252f85b 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -381,6 +381,9 @@ Please refer to the [Changelog][active-record] for detailed changes. * Removed support for the `protected_attributes` gem. ([commit](https://github.com/rails/rails/commit/f4fbc0301021f13ae05c8e941c8efc4ae351fdf9)) +* Removed support for PostgreSQL versions below 9.1. + ([Pull Request](https://github.com/rails/rails/pull/23434)) + ### Deprecations * Deprecated passing a class as a value in a query. Users should pass strings -- cgit v1.2.3 From 7ca7c0ef289595d79ed1a59af9873f4fcc29918e Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Wed, 3 Feb 2016 13:33:31 +0530 Subject: Put some space for non-assets requests in development mode - Fixes #23428. --- actionpack/lib/action_controller/log_subscriber.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 4c9f14e409..d1d6acac26 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -26,6 +26,8 @@ module ActionController end message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" message << " (#{additions.join(" | ".freeze)})" unless additions.blank? + message << "\n\n" if Rails.env.development? + message end end -- cgit v1.2.3 From 887eaa8689b5c6a40d42d99116298a7f6ba8853e Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 3 Feb 2016 22:06:00 +0900 Subject: Typos in AR tests --- activerecord/test/cases/adapters/postgresql/schema_test.rb | 2 +- activerecord/test/cases/associations/callbacks_test.rb | 8 ++++---- .../associations/has_and_belongs_to_many_associations_test.rb | 4 ++-- activerecord/test/cases/base_test.rb | 2 +- activerecord/test/cases/persistence_test.rb | 2 +- activerecord/test/models/author.rb | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 4aeca4d709..f50fe88b9b 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -166,7 +166,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase end end - def test_raise_wraped_exception_on_bad_prepare + def test_raise_wrapped_exception_on_bad_prepare assert_raises(ActiveRecord::StatementInvalid) do @connection.exec_query "select * from developers where id = ?", 'sql', [bind_param(1)] end diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index a531e0e02c..a4298a25a6 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -177,14 +177,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase end def test_dont_add_if_before_callback_raises_exception - assert !@david.unchangable_posts.include?(@authorless) + assert !@david.unchangeable_posts.include?(@authorless) begin - @david.unchangable_posts << @authorless + @david.unchangeable_posts << @authorless rescue Exception end assert @david.post_log.empty? - assert !@david.unchangable_posts.include?(@authorless) + assert !@david.unchangeable_posts.include?(@authorless) @david.reload - assert !@david.unchangable_posts.include?(@authorless) + assert !@david.unchangeable_posts.include?(@authorless) end end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index ccb062f991..5c4586da19 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -827,12 +827,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_no_queries { david.projects.columns } end - def test_attributes_are_being_set_when_initialized_from_habm_association_with_where_clause + def test_attributes_are_being_set_when_initialized_from_habtm_association_with_where_clause new_developer = projects(:action_controller).developers.where(:name => "Marcelo").build assert_equal new_developer.name, "Marcelo" end - def test_attributes_are_being_set_when_initialized_from_habm_association_with_multiple_where_clauses + def test_attributes_are_being_set_when_initialized_from_habtm_association_with_multiple_where_clauses new_developer = projects(:action_controller).developers.where(:name => "Marcelo").where(:salary => 90_000).build assert_equal new_developer.name, "Marcelo" assert_equal new_developer.salary, 90_000 diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index ecdf508e3e..eef2d29d02 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -804,7 +804,7 @@ class BasicsTest < ActiveRecord::TestCase assert_equal dev.salary.amount, dup.salary.amount assert !dup.persisted? - # test if the attributes have been dupd + # test if the attributes have been duped original_amount = dup.salary.amount dev.salary.amount = 1 assert_equal original_amount, dup.salary.amount diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index af15e63d9c..56092aaa0c 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -183,7 +183,7 @@ class PersistenceTest < ActiveRecord::TestCase end end - def test_dupd_becomes_persists_changes_from_the_original + def test_duped_becomes_persists_changes_from_the_original original = topics(:first) copy = original.dup.becomes(Reply) copy.save! diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 0d90cbb110..f25e31b13d 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -75,7 +75,7 @@ class Author < ActiveRecord::Base has_many :posts_with_multiple_callbacks, :class_name => "Post", :before_add => [:log_before_adding, Proc.new {|o, r| o.post_log << "before_adding_proc#{r.id || ''}"}], :after_add => [:log_after_adding, Proc.new {|o, r| o.post_log << "after_adding_proc#{r.id || ''}"}] - has_many :unchangable_posts, :class_name => "Post", :before_add => :raise_exception, :after_add => :log_after_adding + has_many :unchangeable_posts, :class_name => "Post", :before_add => :raise_exception, :after_add => :log_after_adding has_many :categorizations has_many :categories, :through => :categorizations -- cgit v1.2.3 From 33681d000147d8249d7e59bcc2189140c0831e7e Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Wed, 3 Feb 2016 22:22:20 +0900 Subject: update supported version of PostgreSQL in docs [ci skip] Follow up to #23434 --- guides/source/active_record_postgresql.md | 2 +- guides/source/command_line.md | 2 +- .../generators/rails/app/templates/config/databases/jdbcpostgresql.yml | 2 +- .../generators/rails/app/templates/config/databases/postgresql.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md index 68c6a77882..5eb19f5214 100644 --- a/guides/source/active_record_postgresql.md +++ b/guides/source/active_record_postgresql.md @@ -14,7 +14,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- -In order to use the PostgreSQL adapter you need to have at least version 8.2 +In order to use the PostgreSQL adapter you need to have at least version 9.1 installed. Older versions are not supported. To get started with PostgreSQL have a look at the diff --git a/guides/source/command_line.md b/guides/source/command_line.md index e25992fdef..e87ed02ca5 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -618,7 +618,7 @@ We had to create the **gitapp** directory and initialize an empty git repository ```bash $ cat config/database.yml -# PostgreSQL. Versions 8.2 and up are supported. +# PostgreSQL. Versions 9.1 and up are supported. # # Install the pg driver: # gem install pg diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml index 9e99264d33..80ceb9df92 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml @@ -1,4 +1,4 @@ -# PostgreSQL. Versions 8.2 and up are supported. +# PostgreSQL. Versions 9.1 and up are supported. # # Configure Using Gemfile # gem 'activerecord-jdbcpostgresql-adapter' diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml index feb25bbc6b..d51b2ec199 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml @@ -1,4 +1,4 @@ -# PostgreSQL. Versions 8.2 and up are supported. +# PostgreSQL. Versions 9.1 and up are supported. # # Install the pg driver: # gem install pg -- cgit v1.2.3 From 3366a3ad780b140805890b2d3b2d613acec441e4 Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Wed, 3 Feb 2016 18:09:58 +0100 Subject: Document the fact that Action Cable does not require a multi-threaded app server [ci skip] --- actioncable/README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/actioncable/README.md b/actioncable/README.md index ac57532b62..6e74551483 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -448,8 +448,17 @@ as long as you haven’t committed any thread-safety sins. But this also means that Action Cable needs to run in its own server process. So you'll have one set of server processes for your normal web work, and another -set of server processes for the Action Cable. The former can be single-threaded, -like Unicorn, but the latter must be multi-threaded, like Puma. +set of server processes for the Action Cable. + +The Action Cable server does _not_ need to be a multi-threaded application server. +This is because Action Cable uses the [Rack socket hijacking API](http://old.blog.phusion.nl/2013/01/23/the-new-rack-socket-hijacking-api/) +to take over control of connections from the application server. Action Cable +then manages connections internally, in a multithreaded manner, regardless of +whether the application server is multi-threaded or not. So Action Cable works +with all the popular application servers -- Unicorn, Puma and Passenger. + +Action Cable does not work with WEBrick, because WEBrick does not support the +Rack socket hijacking API. ## License -- cgit v1.2.3 From f3433f7c757ef8352c3ea3796a9b350b4454a2b6 Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Wed, 3 Feb 2016 10:49:14 -0800 Subject: Remove json gem dependency All modern Rubies ship JSON as part of stdlib. Using the gem actually hurts multi-platform support due to build difficulties on Windows. --- Gemfile | 1 - Gemfile.lock | 2 -- activesupport/activesupport.gemspec | 1 - 3 files changed, 4 deletions(-) diff --git a/Gemfile b/Gemfile index 78f9853bed..ba102fe711 100644 --- a/Gemfile +++ b/Gemfile @@ -103,7 +103,6 @@ platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do end platforms :jruby do - gem 'json' if ENV['AR_JDBC'] gem 'activerecord-jdbcsqlite3-adapter', github: 'jruby/activerecord-jdbc-adapter', branch: 'master' group :db do diff --git a/Gemfile.lock b/Gemfile.lock index bd2e2c30b9..249028d697 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -64,7 +64,6 @@ PATH activesupport (5.0.0.beta2) concurrent-ruby (~> 1.0) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) method_source minitest (~> 5.1) tzinfo (~> 1.1) @@ -287,7 +286,6 @@ DEPENDENCIES faye-websocket hiredis jquery-rails - json kindlerb (= 0.1.1) listen (~> 3.0.5) minitest (< 5.3.4) diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 32e28c0212..3b71858350 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -21,7 +21,6 @@ Gem::Specification.new do |s| s.rdoc_options.concat ['--encoding', 'UTF-8'] s.add_dependency 'i18n', '~> 0.7' - s.add_dependency 'json', '~> 1.7', '>= 1.7.7' s.add_dependency 'tzinfo', '~> 1.1' s.add_dependency 'minitest', '~> 5.1' s.add_dependency 'concurrent-ruby', '~> 1.0' -- cgit v1.2.3 From deae52a62ec6085b7b8d5f9db113946343266582 Mon Sep 17 00:00:00 2001 From: Alexey Zabelin Date: Wed, 3 Feb 2016 14:50:23 -0500 Subject: Add dummy apple icon files Previously Safari would try to load these files when you visit localhost:3000. That created two exceptions in the log. It also caused the exception notifier to send them out. In response to #23427 --- railties/CHANGELOG.md | 4 ++++ .../rails/app/templates/public/apple-touch-icon-precomposed.png | 0 .../rails/generators/rails/app/templates/public/apple-touch-icon.png | 0 3 files changed, 4 insertions(+) create mode 100644 railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon-precomposed.png create mode 100644 railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon.png diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 96149acc08..a1736470ae 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,9 @@ ## Rails 5.0.0.beta2 (February 01, 2016) ## +* Add dummy files for apple-touch-icon.png and apple-touch-icon.png. GH#23427 + + *Alexey Zabelin* + * Add `after_bundle` callbacks in Rails plugin templates. Useful for allowing templates to perform actions that are dependent upon `bundle install`. diff --git a/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon-precomposed.png b/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon.png b/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon.png new file mode 100644 index 0000000000..e69de29bb2 -- cgit v1.2.3 From e4f0608164b1a25bc3a35a1bd024efe425410758 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Wed, 3 Feb 2016 21:58:55 +0100 Subject: Fix line filters running tests from multiple runnables. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `derive_regexp` was written with the assumption that we were run from a blank slate — that if the filter didn't match we might as well return it because it was nil. This isn't the case because minitest calls `run` on every runnable. Which is any subclass of Minitest::Runnable, such as ActiveSupport::TestCase, ActionDispatch::IntegrationTest as well as any inheriting from those. Thus after the first `run` we'd have put in a composite filter in `options[:filter]` making the next `run` create a linked list when it failed to match the regexp and put the composite filter as the head. Every runnable would accumulate more and more of the same filters, which effectively acted like an expanding whitelist and we ran tests from other runnables. Clog the accumulation by returning nil if there's no filter to derive a regexp from. Note: we pass a seed in the tests because Minitest shuffles the runnables to ensure the whitelist is expanded enough that the failure is triggered. --- railties/lib/rails/test_unit/line_filtering.rb | 2 +- railties/test/application/test_runner_test.rb | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/test_unit/line_filtering.rb b/railties/lib/rails/test_unit/line_filtering.rb index dab4d3631d..b7635c71f4 100644 --- a/railties/lib/rails/test_unit/line_filtering.rb +++ b/railties/lib/rails/test_unit/line_filtering.rb @@ -26,7 +26,7 @@ module Rails private def derive_regexp(filter) # Regexp filtering copied from Minitest. - filter =~ %r%/(.*)/% ? Regexp.new($1) : filter + Regexp.new $1 if filter =~ %r%/(.*)/% end def derive_line_filters(patterns) diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index a7eb0feb11..33bc998722 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -382,6 +382,30 @@ module ApplicationTests end end + def test_line_filters_trigger_only_one_runnable + app_file 'test/models/post_test.rb', <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + test 'truth' do + assert true + end + end + + class SecondPostTest < ActiveSupport::TestCase + test 'truth' do + assert false, 'ran second runnable' + end + end + RUBY + + # Pass seed guaranteeing failure. + run_test_command('test/models/post_test.rb:4 --seed 30410').tap do |output| + assert_no_match 'ran second runnable', output + assert_match '1 runs, 1 assertions', output + end + end + def test_shows_filtered_backtrace_by_default create_backtrace_test -- cgit v1.2.3 From d10b48dd0aa00504212cb6f8598e5c1b7d0d968e Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Wed, 3 Feb 2016 22:10:02 +0100 Subject: Fix model test path typo uncovered in previous commit. Because of the expanding whitelist for test filters, this test ended up running the tests on lines 4 and 9 in the post test even though the path wasn't right. Happened incidentally because the same line numbers were used in both account and post test. Add the .rb line so the file is required correctly and the filters are applied. --- railties/test/application/test_runner_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index 33bc998722..821ac9b033 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -363,7 +363,7 @@ module ApplicationTests end RUBY - run_test_command('test/models/account_test.rb:4:9 test/models/post_test:4:9').tap do |output| + run_test_command('test/models/account_test.rb:4:9 test/models/post_test.rb:4:9').tap do |output| assert_match 'AccountTest:FirstFilter', output assert_match 'AccountTest:SecondFilter', output assert_match 'PostTest:FirstFilter', output -- cgit v1.2.3 From cdc112e3ea8bd7b5ba787e64f3f8ee3da3e5a64f Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Thu, 4 Feb 2016 08:02:45 +1030 Subject: Defer Arel attribute lookup to the model class This still isn't as separated as I'd like, but it at least moves most of the burden of alias mapping in one place. --- .../associations/join_dependency/join_association.rb | 2 +- .../lib/active_record/associations/preloader/association.rb | 2 +- activerecord/lib/active_record/core.rb | 5 +++++ activerecord/lib/active_record/inheritance.rb | 2 +- activerecord/lib/active_record/relation.rb | 8 ++++---- activerecord/lib/active_record/relation/batches.rb | 6 +++--- activerecord/lib/active_record/relation/calculations.rb | 12 ++---------- activerecord/lib/active_record/relation/finder_methods.rb | 4 ++-- .../relation/predicate_builder/relation_handler.rb | 2 +- activerecord/lib/active_record/relation/query_methods.rb | 12 +++++------- activerecord/lib/active_record/table_metadata.rb | 6 +++++- activerecord/test/cases/relation/mutation_test.rb | 4 ++++ 12 files changed, 34 insertions(+), 31 deletions(-) diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index be65cf318c..708b3af5bd 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -75,7 +75,7 @@ module ActiveRecord column = klass.columns_hash[reflection.type.to_s] binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name)) - constraint = constraint.and table[reflection.type].eq(Arel::Nodes::BindParam.new) + constraint = constraint.and klass.arel_attribute(reflection.type, table).eq(Arel::Nodes::BindParam.new) end joins << table.create_join(table, table.create_on(constraint), join_type) diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index e11a5cfb8a..3032bc786e 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -47,7 +47,7 @@ module ActiveRecord # This is overridden by HABTM as the condition should be on the foreign_key column in # the join table def association_key - table[association_key_name] + klass.arel_attribute(association_key_name, table) end # The name of the key on the model which declares the association diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 475a298467..aa8f22ce42 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -256,6 +256,11 @@ module ActiveRecord end end + def arel_attribute(name, table) # :nodoc: + name = attribute_alias(name) if attribute_alias?(name) + table[name] + end + def predicate_builder # :nodoc: @predicate_builder ||= PredicateBuilder.new(table_metadata) end diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 3b6fb70d0d..899683ee4f 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -192,7 +192,7 @@ module ActiveRecord end def type_condition(table = arel_table) - sti_column = table[inheritance_column] + sti_column = arel_attribute(inheritance_column, table) sti_names = ([self] + descendants).map(&:sti_name) sti_column.in(sti_names) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 032b8d4c5d..baddf4828a 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -47,7 +47,7 @@ module ActiveRecord if !primary_key_value && connection.prefetch_primary_key?(klass.table_name) primary_key_value = connection.next_sequence_value(klass.sequence_name) - values[klass.arel_table[klass.primary_key]] = primary_key_value + values[klass.arel_attribute(klass.primary_key, table)] = primary_key_value end end @@ -373,9 +373,9 @@ module ActiveRecord stmt.table(table) if joins_values.any? - @klass.connection.join_to_update(stmt, arel, table[primary_key]) + @klass.connection.join_to_update(stmt, arel, @klass.arel_attribute(primary_key, table)) else - stmt.key = table[primary_key] + stmt.key = @klass.arel_attribute(primary_key, table) stmt.take(arel.limit) stmt.order(*arel.orders) stmt.wheres = arel.constraints @@ -527,7 +527,7 @@ module ActiveRecord stmt.from(table) if joins_values.any? - @klass.connection.join_to_delete(stmt, arel, table[primary_key]) + @klass.connection.join_to_delete(stmt, arel, @klass.arel_attribute(primary_key, table)) else stmt.wheres = arel.constraints end diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 54587ae18e..af951d4da7 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -204,15 +204,15 @@ module ActiveRecord yield yielded_relation break if ids.length < of - batch_relation = relation.where(table[primary_key].gt(primary_key_offset)) + batch_relation = relation.where(klass.arel_attribute(primary_key, table).gt(primary_key_offset)) end end private def apply_limits(relation, start, finish) - relation = relation.where(table[primary_key].gteq(start)) if start - relation = relation.where(table[primary_key].lteq(finish)) if finish + relation = relation.where(klass.arel_attribute(primary_key, table).gteq(start)) if start + relation = relation.where(klass.arel_attribute(primary_key, table).lteq(finish)) if finish relation end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index f45844a9ea..c02b648cb1 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -155,15 +155,7 @@ module ActiveRecord # See also #ids. # def pluck(*column_names) - column_names.map! do |column_name| - if column_name.is_a?(Symbol) && attribute_alias?(column_name) - attribute_alias(column_name) - else - column_name.to_s - end - end - - if loaded? && (column_names - @klass.column_names).empty? + if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty? return @records.pluck(*column_names) end @@ -172,7 +164,7 @@ module ActiveRecord else relation = spawn relation.select_values = column_names.map { |cn| - columns_hash.key?(cn) ? arel_table[cn] : cn + @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? klass.arel_attribute(cn, table) : cn } result = klass.connection.select_all(relation.arel, nil, bound_attributes) result.cast_values(klass.attribute_types) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 3f5d6de78a..5388c4cd65 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -147,7 +147,7 @@ module ActiveRecord def last(limit = nil) if limit if order_values.empty? && primary_key - order(arel_table[primary_key].desc).limit(limit).reverse + order(klass.arel_attribute(primary_key, table).desc).limit(limit).reverse else to_a.last(limit) end @@ -514,7 +514,7 @@ module ActiveRecord # TODO: once the offset argument is removed from find_nth, # find_nth_with_limit_and_offset can be merged into this method relation = if order_values.empty? && primary_key - order(arel_table[primary_key].asc) + order(klass.arel_attribute(primary_key, table).asc) else self end diff --git a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb index 063150958a..a43478f815 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb @@ -3,7 +3,7 @@ module ActiveRecord class RelationHandler # :nodoc: def call(attribute, value) if value.select_values.empty? - value = value.select(value.klass.arel_table[value.klass.primary_key]) + value = value.select(value.klass.arel_attribute(value.klass.primary_key, value.klass.arel_table)) end attribute.in(value.arel) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 8ef9f9f627..ce03c0902b 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1093,8 +1093,8 @@ module ActiveRecord def arel_columns(columns) columns.map do |field| - if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_clause.value - arel_table[field] + if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value + klass.arel_attribute(field, table) elsif Symbol === field connection.quote_table_name(field.to_s) else @@ -1105,7 +1105,7 @@ module ActiveRecord def reverse_sql_order(order_query) if order_query.empty? - return [table[primary_key].desc] if primary_key + return [klass.arel_attribute(primary_key, table).desc] if primary_key raise IrreversibleOrderError, "Relation has no current order and table has no primary key to be used as default order" end @@ -1170,12 +1170,10 @@ module ActiveRecord order_args.map! do |arg| case arg when Symbol - arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg) - table[arg].asc + klass.arel_attribute(arg, table).asc when Hash arg.map { |field, dir| - field = klass.attribute_alias(field) if klass.attribute_alias?(field) - table[field].send(dir.downcase) + klass.arel_attribute(field, table).send(dir.downcase) } else arg diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index f9bb1cf5e0..0faad48ce3 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -22,7 +22,11 @@ module ActiveRecord end def arel_attribute(column_name) - arel_table[column_name] + if klass + klass.arel_attribute(column_name, arel_table) + else + arel_table[column_name] + end end def type(column_name) diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index d0f60a84b5..ffb2da7a26 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -26,6 +26,10 @@ module ActiveRecord def sanitize_sql_for_order(sql) sql end + + def arel_attribute(name, table) + table[name] + end end def relation -- cgit v1.2.3 From c7f8019bff16554095ff5c2c4e539962922b7a55 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Thu, 4 Feb 2016 06:44:37 +0900 Subject: Active Record supports MySQL >= 5.0 Currently some features uses `information_schema` (e.g. foreign key support). `information_schema` introduced since MySQL 5.0. --- .../connection_adapters/abstract_mysql_adapter.rb | 17 +++++++---------- activerecord/test/cases/defaults_test.rb | 3 +-- .../rails/app/templates/config/databases/jdbcmysql.yml | 2 +- .../rails/app/templates/config/databases/mysql.yml | 2 +- 4 files changed, 10 insertions(+), 14 deletions(-) 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 70d7956baa..8751b6da4b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -52,7 +52,6 @@ module ActiveRecord INDEX_TYPES = [:fulltext, :spatial] INDEX_USINGS = [:btree, :hash] - # FIXME: Make the first parameter more similar for the two adapters def initialize(connection, logger, connection_options, config) super(connection, logger, config) @quoted_column_names, @quoted_table_names = {}, {} @@ -65,6 +64,10 @@ module ActiveRecord else @prepared_statements = false end + + if version < '5.0.0' + raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0." + end end CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32'] @@ -98,12 +101,8 @@ module ActiveRecord true end - # MySQL 4 technically support transaction isolation, but it is affected by a bug - # where the transaction level gets persisted for the whole session: - # - # http://bugs.mysql.com/bug.php?id=39170 def supports_transaction_isolation? - version >= '5.0.0' + true end def supports_explain? @@ -119,17 +118,15 @@ module ActiveRecord end def supports_views? - version >= '5.0.0' + true end def supports_datetime_with_precision? version >= '5.6.4' end - # 5.0.0 definitely supports it, possibly supported by earlier versions but - # not sure def supports_advisory_locks? - version >= '5.0.0' + true end def get_advisory_lock(lock_name, timeout = 0) # :nodoc: diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index 69b0487dd8..067513e24c 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -201,8 +201,7 @@ if current_adapter?(:Mysql2Adapter) assert_equal '0', klass.columns_hash['zero'].default assert !klass.columns_hash['zero'].null - # 0 in MySQL 4, nil in 5. - assert [0, nil].include?(klass.columns_hash['omit'].default) + assert_equal nil, klass.columns_hash['omit'].default assert !klass.columns_hash['omit'].null assert_raise(ActiveRecord::StatementInvalid) { klass.create! } 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 5ca549a8c8..f2c4922e7d 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 @@ -1,4 +1,4 @@ -# MySQL. Versions 4.1 and 5.0 are recommended. +# MySQL. Versions 5.0 and up are supported. # # Install the MySQL driver: # gem install activerecord-jdbcmysql-adapter 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 119c2fe2c3..193423e84a 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 @@ -1,4 +1,4 @@ -# MySQL. Versions 5.0+ are recommended. +# MySQL. Versions 5.0 and up are supported. # # Install the MySQL driver # gem install mysql2 -- cgit v1.2.3 From cf18c34e3a3e010e1b987b06a55c4f60543b2ba4 Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Wed, 3 Feb 2016 14:28:55 -0800 Subject: Remove unused dependency railties uses method_source, activesupport does not. I assume code was refactored and the dependency wasn't removed. --- Gemfile.lock | 1 - activesupport/activesupport.gemspec | 1 - 2 files changed, 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 249028d697..169c65b39d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -64,7 +64,6 @@ PATH activesupport (5.0.0.beta2) concurrent-ruby (~> 1.0) i18n (~> 0.7) - method_source minitest (~> 5.1) tzinfo (~> 1.1) rails (5.0.0.beta2) diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 3b71858350..68a80701ed 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -24,5 +24,4 @@ Gem::Specification.new do |s| s.add_dependency 'tzinfo', '~> 1.1' s.add_dependency 'minitest', '~> 5.1' s.add_dependency 'concurrent-ruby', '~> 1.0' - s.add_dependency 'method_source' end -- cgit v1.2.3 From 8b8ee6539cb4d4b5389db084014aa8da4fa997a7 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Thu, 4 Feb 2016 07:30:05 +0900 Subject: InnoDB supports FULLTEXT and Spatial Indexes [ci skip] https://dev.mysql.com/doc/refman/5.7/en/innodb-fulltext-index.html https://dev.mysql.com/doc/refman/5.7/en/creating-spatial-indexes.html --- .../lib/active_record/connection_adapters/abstract/schema_statements.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 cc245587c1..65087f35a8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -700,7 +700,7 @@ module ActiveRecord # # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL # - # Note: only supported by MySQL. Supported: :fulltext and :spatial on MyISAM tables. + # Note: only supported by MySQL. def add_index(table_name, column_name, options = {}) index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}" -- cgit v1.2.3 From 5952861948a0918b1955202c1ea19589634537dc Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Thu, 4 Feb 2016 09:14:05 +1030 Subject: Extract a Relation#arel_attribute --- activerecord/lib/active_record/core.rb | 2 +- activerecord/lib/active_record/relation.rb | 12 ++++++++---- activerecord/lib/active_record/relation/batches.rb | 6 +++--- activerecord/lib/active_record/relation/calculations.rb | 2 +- activerecord/lib/active_record/relation/finder_methods.rb | 4 ++-- .../relation/predicate_builder/relation_handler.rb | 2 +- activerecord/lib/active_record/relation/query_methods.rb | 8 ++++---- 7 files changed, 20 insertions(+), 16 deletions(-) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index aa8f22ce42..24fd0aaecf 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -256,7 +256,7 @@ module ActiveRecord end end - def arel_attribute(name, table) # :nodoc: + def arel_attribute(name, table = arel_table) # :nodoc: name = attribute_alias(name) if attribute_alias?(name) table[name] end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index baddf4828a..7e842668c6 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -47,7 +47,7 @@ module ActiveRecord if !primary_key_value && connection.prefetch_primary_key?(klass.table_name) primary_key_value = connection.next_sequence_value(klass.sequence_name) - values[klass.arel_attribute(klass.primary_key, table)] = primary_key_value + values[arel_attribute(klass.primary_key)] = primary_key_value end end @@ -105,6 +105,10 @@ module ActiveRecord [substitutes, binds] end + def arel_attribute(name) # :nodoc: + klass.arel_attribute(name, table) + end + # Initializes new record from relation while maintaining the current # scope. # @@ -373,9 +377,9 @@ module ActiveRecord stmt.table(table) if joins_values.any? - @klass.connection.join_to_update(stmt, arel, @klass.arel_attribute(primary_key, table)) + @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key)) else - stmt.key = @klass.arel_attribute(primary_key, table) + stmt.key = arel_attribute(primary_key) stmt.take(arel.limit) stmt.order(*arel.orders) stmt.wheres = arel.constraints @@ -527,7 +531,7 @@ module ActiveRecord stmt.from(table) if joins_values.any? - @klass.connection.join_to_delete(stmt, arel, @klass.arel_attribute(primary_key, table)) + @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key)) else stmt.wheres = arel.constraints end diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index af951d4da7..de005e2810 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -204,15 +204,15 @@ module ActiveRecord yield yielded_relation break if ids.length < of - batch_relation = relation.where(klass.arel_attribute(primary_key, table).gt(primary_key_offset)) + batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset)) end end private def apply_limits(relation, start, finish) - relation = relation.where(klass.arel_attribute(primary_key, table).gteq(start)) if start - relation = relation.where(klass.arel_attribute(primary_key, table).lteq(finish)) if finish + relation = relation.where(arel_attribute(primary_key).gteq(start)) if start + relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish relation end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index c02b648cb1..54c9af4898 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -164,7 +164,7 @@ module ActiveRecord else relation = spawn relation.select_values = column_names.map { |cn| - @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? klass.arel_attribute(cn, table) : cn + @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn } result = klass.connection.select_all(relation.arel, nil, bound_attributes) result.cast_values(klass.attribute_types) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 5388c4cd65..d48bcea28a 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -147,7 +147,7 @@ module ActiveRecord def last(limit = nil) if limit if order_values.empty? && primary_key - order(klass.arel_attribute(primary_key, table).desc).limit(limit).reverse + order(arel_attribute(primary_key).desc).limit(limit).reverse else to_a.last(limit) end @@ -514,7 +514,7 @@ module ActiveRecord # TODO: once the offset argument is removed from find_nth, # find_nth_with_limit_and_offset can be merged into this method relation = if order_values.empty? && primary_key - order(klass.arel_attribute(primary_key, table).asc) + order(arel_attribute(primary_key).asc) else self end diff --git a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb index a43478f815..8a910a82fe 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb @@ -3,7 +3,7 @@ module ActiveRecord class RelationHandler # :nodoc: def call(attribute, value) if value.select_values.empty? - value = value.select(value.klass.arel_attribute(value.klass.primary_key, value.klass.arel_table)) + value = value.select(value.arel_attribute(value.klass.primary_key)) end attribute.in(value.arel) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index ce03c0902b..91bfa4d131 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1094,7 +1094,7 @@ module ActiveRecord def arel_columns(columns) columns.map do |field| if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value - klass.arel_attribute(field, table) + arel_attribute(field) elsif Symbol === field connection.quote_table_name(field.to_s) else @@ -1105,7 +1105,7 @@ module ActiveRecord def reverse_sql_order(order_query) if order_query.empty? - return [klass.arel_attribute(primary_key, table).desc] if primary_key + return [arel_attribute(primary_key).desc] if primary_key raise IrreversibleOrderError, "Relation has no current order and table has no primary key to be used as default order" end @@ -1170,10 +1170,10 @@ module ActiveRecord order_args.map! do |arg| case arg when Symbol - klass.arel_attribute(arg, table).asc + arel_attribute(arg).asc when Hash arg.map { |field, dir| - klass.arel_attribute(field, table).send(dir.downcase) + arel_attribute(field).send(dir.downcase) } else arg -- cgit v1.2.3 From 210c81440aa7951a8780009e659151bd4eb55e46 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Thu, 4 Feb 2016 09:15:25 +0900 Subject: Remove commented out code in `ci/travis.rb` [ci skip] --- ci/travis.rb | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/ci/travis.rb b/ci/travis.rb index e9a3626b9a..063c6acb07 100755 --- a/ci/travis.rb +++ b/ci/travis.rb @@ -157,20 +157,6 @@ ENV['GEM'].split(',').each do |gem| end end -# puts -# puts "Build environment:" -# puts " #{`cat /etc/issue`}" -# puts " #{`uname -a`}" -# puts " #{`ruby -v`}" -# puts " #{`mysql --version`}" -# puts " #{`pg_config --version`}" -# puts " SQLite3: #{`sqlite3 -version`}" -# `gem env`.each_line {|line| print " #{line}"} -# puts " Bundled gems:" -# `bundle show`.each_line {|line| print " #{line}"} -# puts " Local gems:" -# `gem list`.each_line {|line| print " #{line}"} - failures = results.select { |key, value| !value } if failures.empty? -- cgit v1.2.3 From c9feea6c9ab4494b0cb0b8cf4316847854f65af6 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Thu, 4 Feb 2016 09:23:11 +0900 Subject: SQLite 2 support has been dropped [ci skip] --- .../lib/active_record/connection_adapters/abstract/schema_statements.rb | 2 -- activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb | 1 - guides/source/command_line.md | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) 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 cc245587c1..3eb0fdefa7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -496,8 +496,6 @@ module ActiveRecord # Default is (10,0). # * PostgreSQL: :precision [1..infinity], # :scale [0..infinity]. No default. - # * SQLite2: Any :precision and :scale may be used. - # Internal storage as strings. No default. # * SQLite3: No restrictions on :precision and :scale, # but the maximum supported :precision is 16. No default. # * Oracle: :precision [1..38], :scale [-84..127]. diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index a5cbbf0c69..c65d33ccb3 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -8,7 +8,6 @@ require 'sqlite3' module ActiveRecord module ConnectionHandling # :nodoc: - # sqlite3 adapter reuses sqlite_connection. def sqlite3_connection(config) # Require database. unless config[:database] diff --git a/guides/source/command_line.md b/guides/source/command_line.md index e87ed02ca5..e865a02cbd 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -325,7 +325,7 @@ With the `helper` method it is possible to access Rails and your application's h ### `rails dbconsole` -`rails dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3. +`rails dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL and SQLite3. INFO: You can also use the alias "db" to invoke the dbconsole: `rails db`. -- cgit v1.2.3 From 250c0d08c4753c6f29956612ea33999f19ee26d6 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 4 Feb 2016 17:24:00 +0900 Subject: Typos in AR tests --- activerecord/test/cases/associations/has_many_associations_test.rb | 2 +- activerecord/test/cases/attribute_methods_test.rb | 2 +- activerecord/test/cases/relation/record_fetch_warning_test.rb | 4 ++-- activerecord/test/cases/store_test.rb | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index ad157582a4..ecaa521283 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -2271,7 +2271,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [], authors(:david).posts_with_signature.map(&:title) end - test 'associations autosaves when object is already persited' do + test 'associations autosaves when object is already persisted' do bulb = Bulb.create! tyre = Tyre.create! diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ef84624a8d..1db52af59b 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -798,7 +798,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_nil computer.system end - def test_global_methods_are_overwritte_when_subclassing + def test_global_methods_are_overwritten_when_subclassing klass = Class.new(ActiveRecord::Base) { self.abstract_class = true } subklass = Class.new(klass) do diff --git a/activerecord/test/cases/relation/record_fetch_warning_test.rb b/activerecord/test/cases/relation/record_fetch_warning_test.rb index 62f0a7cc49..53daf436e5 100644 --- a/activerecord/test/cases/relation/record_fetch_warning_test.rb +++ b/activerecord/test/cases/relation/record_fetch_warning_test.rb @@ -7,7 +7,7 @@ module ActiveRecord def test_warn_on_records_fetched_greater_than original_logger = ActiveRecord::Base.logger - orginal_warn_on_records_fetched_greater_than = ActiveRecord::Base.warn_on_records_fetched_greater_than + original_warn_on_records_fetched_greater_than = ActiveRecord::Base.warn_on_records_fetched_greater_than log = StringIO.new ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) @@ -22,7 +22,7 @@ module ActiveRecord assert_match(/Query fetched/, log.string) ensure ActiveRecord::Base.logger = original_logger - ActiveRecord::Base.warn_on_records_fetched_greater_than = orginal_warn_on_records_fetched_greater_than + ActiveRecord::Base.warn_on_records_fetched_greater_than = original_warn_on_records_fetched_greater_than end end end diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index e9cdf94c99..ab63f5825c 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -104,7 +104,7 @@ class StoreTest < ActiveRecord::TestCase assert_equal true, user.settings.instance_of?(ActiveSupport::HashWithIndifferentAccess) end - test "convert store attributes from any format other than Hash or HashWithIndifferent access losing the data" do + test "convert store attributes from any format other than Hash or HashWithIndifferentAccess losing the data" do @john.json_data = "somedata" @john.height = 'low' assert_equal true, @john.json_data.instance_of?(ActiveSupport::HashWithIndifferentAccess) -- cgit v1.2.3 From 2cd405f34077c2766eda9c3bea75989136eb5f13 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 4 Feb 2016 18:07:35 +0900 Subject: Revert "Lines of code can be 100,000+ in a Rails app" This reverts commit 293bd95c3e77275193130bc14c986348aae8b0e2. This broke the header :< --- railties/lib/rails/code_statistics.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index 0997414482..a4e22af45b 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -69,12 +69,12 @@ class CodeStatistics #:nodoc: def print_header print_splitter - puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |" + puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |" print_splitter end def print_splitter - puts "+----------------------+--------+--------+---------+---------+-----+-------+" + puts "+----------------------+-------+-------+---------+---------+-----+-------+" end def print_line(name, statistics) @@ -82,8 +82,8 @@ class CodeStatistics #:nodoc: loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0 puts "| #{name.ljust(20)} " \ - "| #{statistics.lines.to_s.rjust(6)} " \ - "| #{statistics.code_lines.to_s.rjust(6)} " \ + "| #{statistics.lines.to_s.rjust(5)} " \ + "| #{statistics.code_lines.to_s.rjust(5)} " \ "| #{statistics.classes.to_s.rjust(7)} " \ "| #{statistics.methods.to_s.rjust(7)} " \ "| #{m_over_c.to_s.rjust(3)} " \ -- cgit v1.2.3 From dfa48f200cbc5c1ca18457a8cde14642e12af594 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 4 Feb 2016 18:12:28 +0900 Subject: rake stats dynamically scales now So it can properly show stats for an app with 1,000,000+ LOC --- railties/lib/rails/code_statistics.rb | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index a4e22af45b..fc8717c752 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -9,6 +9,8 @@ class CodeStatistics #:nodoc: 'Job tests', 'Integration tests'] + HEADERS = {lines: ' Lines', code_lines: ' LOC', classes: 'Classes', methods: 'Methods'} + def initialize(*pairs) @pairs = pairs @statistics = calculate_statistics @@ -67,27 +69,37 @@ class CodeStatistics #:nodoc: test_loc end + def width_for(label) + [@statistics.values.sum {|s| s.send(label) }.to_s.size, HEADERS[label].length].max + end + def print_header print_splitter - puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |" + print '| Name ' + HEADERS.each do |k, v| + print " | #{v.rjust(width_for(k))}" + end + puts ' | M/C | LOC/M |' print_splitter end def print_splitter - puts "+----------------------+-------+-------+---------+---------+-----+-------+" + print '+----------------------' + HEADERS.each_key do |k| + print "+#{'-' * (width_for(k) + 2)}" + end + puts '+-----+-------+' end def print_line(name, statistics) m_over_c = (statistics.methods / statistics.classes) rescue m_over_c = 0 loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0 - puts "| #{name.ljust(20)} " \ - "| #{statistics.lines.to_s.rjust(5)} " \ - "| #{statistics.code_lines.to_s.rjust(5)} " \ - "| #{statistics.classes.to_s.rjust(7)} " \ - "| #{statistics.methods.to_s.rjust(7)} " \ - "| #{m_over_c.to_s.rjust(3)} " \ - "| #{loc_over_m.to_s.rjust(5)} |" + print "| #{name.ljust(20)} " + HEADERS.each_key do |k| + print "| #{statistics.send(k).to_s.rjust(width_for(k))} " + end + puts "| #{m_over_c.to_s.rjust(3)} | #{loc_over_m.to_s.rjust(5)} |" end def print_code_test_stats -- cgit v1.2.3 From 7429bc58a0dffa94636b21cc0cba1d19a5ae7a84 Mon Sep 17 00:00:00 2001 From: Jeremy Baker Date: Thu, 4 Feb 2016 01:32:45 -0800 Subject: Remove the assumption of schema in DATABASE_URL If you set the DATABASE_URL environment variable to `mydatabase` by accident, you end up getting a series of errors that are hard to trace. For example: ``` warning: already initialized constant ActiveRecord::Base::OrmAdapter ``` Turns out the cascade of errors is due to the error raised by `.tr` being called on `nil`. This commit makes sure that `scheme` is set before calling `.tr` on it. My previous iteration used `@uri.scheme.try(:tr, '-', '_')` but using the `&&` logical operator is a fair bit faster: http://stackoverflow.com/questions/26655032/try-vs-performance With this change, the error message becomes much more understandable: ``` FATAL: database "mydatabase" does not exist (ActiveRecord::NoDatabaseError) ``` --- .../lib/active_record/connection_adapters/connection_specification.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index f633892dee..4bc6447368 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -33,7 +33,7 @@ module ActiveRecord def initialize(url) raise "Database URL cannot be empty" if url.blank? @uri = uri_parser.parse(url) - @adapter = @uri.scheme.tr('-', '_') + @adapter = @uri.scheme && @uri.scheme.tr('-', '_') @adapter = "postgresql" if @adapter == "postgres" if @uri.opaque -- cgit v1.2.3 From 5a4e878876a5813dca09fbf8b62180e3c1ba7aa6 Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Thu, 4 Feb 2016 10:40:04 +0100 Subject: Don't publicize Kernel core extensions This is a reaction to a [bug] we hit in web-console. The cause of it was a `Kernel` extension called `#console` that was public and was fighting over Railties with console block to be run on `rails console`. We solved it by making the method private. We did that through `module_function` so `::Kernel.console` can be invoked even in `BasicObject`. I'm proposing to make most of the core Active Support `Kernel` extensions `module_function` as well. Those are currently public and we are polluting every `Object` public interface with them. ```ruby >> Object.new.respond_to? :silence_warnings => true >> Object.new.respond_to? :with_warnings => true >> Object.new.respond_to? :enable_warnings => true >> Object.new.respond_to? :suppress => true `` Some extensions like `Kernel#class_eval` should be public, but most of them don't really need to be. [bug]: https://github.com/rails/web-console/issues/184 --- activesupport/lib/active_support/core_ext/kernel/concern.rb | 2 ++ activesupport/lib/active_support/core_ext/kernel/reporting.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/activesupport/lib/active_support/core_ext/kernel/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb index bf72caa058..18bcc01fa4 100644 --- a/activesupport/lib/active_support/core_ext/kernel/concern.rb +++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb @@ -1,6 +1,8 @@ require 'active_support/core_ext/module/concerning' module Kernel + module_function + # A shortcut to define a toplevel concern, not within a module. # # See Module::Concerning for more. diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 8afc258df8..d0197af95f 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -1,4 +1,6 @@ module Kernel + module_function + # Sets $VERBOSE to nil for the duration of the block and back to its original # value afterwards. # -- cgit v1.2.3 From 5e5fd246d5852b1c49dfdb8e635fb2e2c6ae8e55 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 4 Feb 2016 12:10:35 +0100 Subject: Allow for non-standard redis connectors --- actioncable/CHANGELOG.md | 7 +++++++ .../lib/action_cable/subscription_adapter/evented_redis.rb | 12 ++++++++++-- actioncable/lib/action_cable/subscription_adapter/redis.rb | 6 +++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index e671a07563..bfc229d795 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,3 +1,10 @@ +* Added ActionCable::SubscriptionAdapter::EventedRedis.em_redis_connector/redis_connector and + ActionCable::SubscriptionAdapter::Redis.redis_connector factory methods for redis connections, + so you can overwrite with your own initializers. This is used when you want to use different-than-standard Redis adapters, + like for Makara distributed Redis. + + *DHH* + ## Rails 5.0.0.beta2 (February 01, 2016) ## * Support PostgreSQL pubsub adapter. diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb index d697548cbd..af04a58c70 100644 --- a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb @@ -13,6 +13,14 @@ module ActionCable class EventedRedis < Base # :nodoc: @@mutex = Mutex.new + # Overwrite this factory method for EventMachine redis connections if you want to use a different Redis library than EM::Hiredis. + # This is needed, for example, when using Makara proxies for distributed Redis. + cattr_accessor(:em_redis_connector) { ->(config) { EM::Hiredis.connect(config[:url]) } } + + # Overwrite this factory method for redis connections if you want to use a different Redis library than Redis. + # This is needed, for example, when using Makara proxies for distributed Redis. + cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } } + def initialize(*) super @redis_connection_for_broadcasts = @redis_connection_for_subscriptions = nil @@ -41,7 +49,7 @@ module ActionCable def redis_connection_for_subscriptions ensure_reactor_running @redis_connection_for_subscriptions || @server.mutex.synchronize do - @redis_connection_for_subscriptions ||= EM::Hiredis.connect(@server.config.cable[:url]).tap do |redis| + @redis_connection_for_subscriptions ||= self.class.em_redis_connector.call(@server.config.cable).tap do |redis| redis.on(:reconnect_failed) do @logger.info "[ActionCable] Redis reconnect failed." end @@ -51,7 +59,7 @@ module ActionCable def redis_connection_for_broadcasts @redis_connection_for_broadcasts || @server.mutex.synchronize do - @redis_connection_for_broadcasts ||= ::Redis.new(@server.config.cable) + @redis_connection_for_broadcasts ||= self.class.redis_connector.call(@server.config.cable) end end diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index 7076383efe..ba4934a264 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -6,6 +6,10 @@ require 'redis' module ActionCable module SubscriptionAdapter class Redis < Base # :nodoc: + # Overwrite this factory method for redis connections if you want to use a different Redis library than Redis. + # This is needed, for example, when using Makara proxies for distributed Redis. + cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } } + def initialize(*) super @listener = nil @@ -39,7 +43,7 @@ module ActionCable def redis_connection_for_broadcasts @redis_connection_for_broadcasts || @server.mutex.synchronize do - @redis_connection_for_broadcasts ||= ::Redis.new(@server.config.cable) + @redis_connection_for_broadcasts ||= self.class.redis_connector.call(@server.config.cable) end end -- cgit v1.2.3 From 91864439c7aebb6ca710831aac6781903a433904 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 4 Feb 2016 12:16:57 +0100 Subject: Redis is now only needed if you want to use Action Cable in production So no need to have it on by default in the Gemfile. --- railties/lib/rails/generators/app_base.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 8bc7dd63f9..8522f5196b 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -352,9 +352,9 @@ module Rails def cable_gemfile_entry return [] if options[:skip_action_cable] - comment = 'Action Cable dependencies for the Redis adapter' + comment = 'Use Redis adapter to run Action Cable in production' gems = [] - gems << GemfileEntry.new("redis", '~> 3.0', comment) + gems << GemfileEntry.new("redis", '~> 3.0', comment, {}, true) gems end -- cgit v1.2.3 From f2306d05d8b4e77741145be5ce63a1270655fed2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 4 Feb 2016 12:17:12 +0100 Subject: Turbolinks has moved out All grown up and shit! --- railties/lib/rails/generators/app_base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 8522f5196b..961efd05e3 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -326,7 +326,7 @@ module Rails unless options[:skip_turbolinks] gems << GemfileEntry.version("turbolinks", nil, - "Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks") + "Turbolinks makes following links in your web application faster. Read more: https://github.com/turbolinks/turbolinks") end gems -- cgit v1.2.3 From e0e872f6751ae653b07bd529267b4dcb5b7fca81 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Thu, 4 Feb 2016 19:40:23 +0530 Subject: Pass api only option to rails rails:update task to update only based on API. Fixes #23470 --- railties/lib/rails/tasks/framework.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 904b9d9ad6..7601836809 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -45,7 +45,7 @@ namespace :rails do @app_generator ||= begin require 'rails/generators' require 'rails/generators/rails/app/app_generator' - gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, + gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true, api: !!Rails.application.config.api_only }, destination_root: Rails.root File.exist?(Rails.root.join("config", "application.rb")) ? gen.send(:app_const) : gen.send(:valid_const?) -- cgit v1.2.3 From b1ae3a32b5ba3b53124ea12747d320800f2da8f5 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Thu, 4 Feb 2016 09:46:05 -0500 Subject: Update assertion on redis in generated Gemfile Redis now included in Gemfile but commented out. This change was made in 91864439c7aebb6ca710831aac6781903a433904 and is causing the test failure. See https://travis-ci.org/rails/rails/jobs/106994913#L1025 --- railties/test/generators/app_generator_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 9595d2a0ef..b954738ff2 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -415,7 +415,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_action_cable_redis_gems run_generator - assert_gem 'redis' + assert_file "Gemfile", /^# gem 'redis'/ end def test_inclusion_of_javascript_runtime -- cgit v1.2.3 From c074343cffceaa8e5fc97db1f48e58dd8aff4723 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Thu, 4 Feb 2016 20:04:13 +0530 Subject: - app generate option --skip-sprockets leaves jquery-rails gem, which relies on sprockets environment - Remove jquery-rails if --skip-sprockets is true Fixes #23431 --- railties/lib/rails/generators/app_base.rb | 2 +- railties/test/generators/app_generator_test.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 961efd05e3..9b1e16a7a3 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -317,7 +317,7 @@ module Rails end def javascript_gemfile_entry - if options[:skip_javascript] + if options[:skip_javascript] || options[:skip_sprockets] [] else gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry] diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index b954738ff2..f483a0bcbd 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -385,9 +385,10 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content) end assert_file "Gemfile" do |content| + assert_no_match(/jquery-rails/, content) assert_no_match(/sass-rails/, content) assert_no_match(/uglifier/, content) - assert_match(/coffee-rails/, content) + assert_no_match(/coffee-rails/, content) end assert_file "config/environments/development.rb" do |content| assert_no_match(/config\.assets\.debug = true/, content) -- cgit v1.2.3 From dd67df6136951d7a449bf883bd8c4af10c75afb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 4 Feb 2016 14:24:08 -0200 Subject: Test with Turbolinks 5 --- Gemfile | 2 +- Gemfile.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index ba102fe711..8e2c69214a 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ gem 'mocha', '~> 0.14', require: false gem 'rack-cache', '~> 1.2' gem 'jquery-rails' gem 'coffee-rails', '~> 4.1.0' -gem 'turbolinks' +gem 'turbolinks', '~> 5.0.0.beta1' # require: false so bcrypt is loaded only when has_secure_password is used. # This is to avoid Active Model (and by extension the entire framework) diff --git a/Gemfile.lock b/Gemfile.lock index 169c65b39d..6b619e701f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -245,8 +245,9 @@ GEM thor (0.19.1) thread (0.1.7) thread_safe (0.3.5) - turbolinks (2.5.3) - coffee-rails + turbolinks (5.0.0.beta1) + turbolinks-source + turbolinks-source (5.0.0.beta1.1) tzinfo (1.2.2) thread_safe (~> 0.1) tzinfo-data (1.2015.7) @@ -314,7 +315,7 @@ DEPENDENCIES sqlite3 (~> 1.3.6) stackprof sucker_punch - turbolinks + turbolinks (~> 5.0.0.beta1) tzinfo-data uglifier (>= 1.3.0) w3c_validators -- cgit v1.2.3 From d601f4b08bf5ba744442d06aada78d22364d390d Mon Sep 17 00:00:00 2001 From: Sam Ruby Date: Thu, 4 Feb 2016 12:59:28 -0500 Subject: hotlink to the source and results for AWDwR tests --- RELEASING_RAILS.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/RELEASING_RAILS.md b/RELEASING_RAILS.md index faf8fa7f0d..83586c22c0 100644 --- a/RELEASING_RAILS.md +++ b/RELEASING_RAILS.md @@ -19,13 +19,13 @@ http://travis-ci.org/rails/rails ### Is Sam Ruby happy? If not, make him happy. -Sam Ruby keeps a test suite that makes sure the code samples in his book (Agile -Web Development with Rails) all work. These are valuable integration tests -for Rails. You can check the status of his tests here: +Sam Ruby keeps a [test suite](https://github.com/rubys/awdwr) that makes +sure the code samples in his book +([Agile Web Development with Rails](https://pragprog.com/titles/rails4/agile-web-development-with-rails-4th-edition)) +all work. These are valuable system tests +for Rails. You can check the status of these tests here: -``` -http://intertwingly.net/projects/dashboard.html -``` +[http://intertwingly.net/projects/dashboard.html](http://intertwingly.net/projects/dashboard.html) Do not release with Red AWDwR tests. @@ -212,4 +212,4 @@ There are two simple steps for fixing the CI: 1. Identify the problem 2. Fix it -Repeat these steps until the CI is green. \ No newline at end of file +Repeat these steps until the CI is green. -- cgit v1.2.3 From e38ced376fc4f67036d44bd905fb03dcc12782f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Emin=20=C4=B0NA=C3=87?= Date: Thu, 4 Feb 2016 20:03:24 +0200 Subject: Add documentation about method to describe how it works [ci skip] --- activemodel/lib/active_model/errors.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index ef6141a51d..ea69e7549e 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -160,6 +160,15 @@ module ActiveModel # # person.errors[:name] # => ["cannot be nil"] # person.errors['name'] # => ["cannot be nil"] + # + # Note that, if you try to get errors of an attribute which has + # no errors associated with it, this method will instantiate + # an empty error list for it and +keys+ will return an array + # of error keys which includes this attribute. + # + # person.errors.keys # => [] + # person.errors[:name] # => [] + # person.errors.keys # => [:name] def [](attribute) messages[attribute.to_sym] end -- cgit v1.2.3 From d871696b4a8b021b67a3ce6c3cb5d5dcbbb578fe Mon Sep 17 00:00:00 2001 From: Jeremy Baker Date: Thu, 4 Feb 2016 10:59:52 -0800 Subject: Add a resolver test for the missing scheme --- .../test/cases/connection_adapters/connection_specification_test.rb | 5 +++++ activerecord/test/cases/connection_specification/resolver_test.rb | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/activerecord/test/cases/connection_adapters/connection_specification_test.rb b/activerecord/test/cases/connection_adapters/connection_specification_test.rb index ea2196cda2..99fe7020ea 100644 --- a/activerecord/test/cases/connection_adapters/connection_specification_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_specification_test.rb @@ -7,6 +7,11 @@ module ActiveRecord spec = ConnectionSpecification.new({ :a => :b }, "bar") assert_not_equal(spec.config.object_id, spec.dup.config.object_id) end + + def test_handle_missing_scheme + spec = ConnectionSpecification.new({ :url => 'testing' }, "bar") + assert_not_equal(spec.config.object_id, spec.dup.config.object_id) + end end end end diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index 3c2f5d4219..358b6ad537 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -57,6 +57,12 @@ module ActiveRecord "encoding" => "utf8" }, spec) end + def test_url_missing_scheme + spec = resolve 'foo' + assert_equal({ + "database" => "foo" }, spec) + end + def test_url_host_db spec = resolve 'abstract://foo/bar?encoding=utf8' assert_equal({ -- cgit v1.2.3 From 96fdbd3be3e6c5c57e394019e8b812da6d705769 Mon Sep 17 00:00:00 2001 From: Jeremy Baker Date: Thu, 4 Feb 2016 11:13:05 -0800 Subject: Remove accidental additional test --- .../test/cases/connection_adapters/connection_specification_test.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/activerecord/test/cases/connection_adapters/connection_specification_test.rb b/activerecord/test/cases/connection_adapters/connection_specification_test.rb index 99fe7020ea..ea2196cda2 100644 --- a/activerecord/test/cases/connection_adapters/connection_specification_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_specification_test.rb @@ -7,11 +7,6 @@ module ActiveRecord spec = ConnectionSpecification.new({ :a => :b }, "bar") assert_not_equal(spec.config.object_id, spec.dup.config.object_id) end - - def test_handle_missing_scheme - spec = ConnectionSpecification.new({ :url => 'testing' }, "bar") - assert_not_equal(spec.config.object_id, spec.dup.config.object_id) - end end end end -- cgit v1.2.3 From 924f33ad96ee6475f5f61633a3114e08dae83c67 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Fri, 5 Feb 2016 13:09:23 +0900 Subject: add missing include to engine test example [ci skip] Using url helper method of engine in example code, include `Engine.routes.url_helpers` is required to use helper method of engine. --- guides/source/engines.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guides/source/engines.md b/guides/source/engines.md index 697938434c..415def8367 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -1034,6 +1034,8 @@ typical `GET` to a controller in a controller's functional test like this: ```ruby module Blorgh class FooControllerTest < ActionDispatch::IntegrationTest + include Engine.routes.url_helpers + def test_index get foos_url ... @@ -1050,6 +1052,8 @@ in your setup code: ```ruby module Blorgh class FooControllerTest < ActionDispatch::IntegrationTest + include Engine.routes.url_helpers + setup do @routes = Engine.routes end -- cgit v1.2.3 From c9768b8a88039fffd160a859d91ed9d5aa618b87 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Fri, 5 Feb 2016 18:27:22 +0900 Subject: move CHANGELOG entry to the appropriate position [ci skip] Dummy apple icon files has been added after the 5.0.0.beta2 release. ref: #23455 --- railties/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index a1736470ae..8f4dc736a8 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,9 +1,9 @@ -## Rails 5.0.0.beta2 (February 01, 2016) ## - * Add dummy files for apple-touch-icon.png and apple-touch-icon.png. GH#23427 *Alexey Zabelin* +## Rails 5.0.0.beta2 (February 01, 2016) ## + * Add `after_bundle` callbacks in Rails plugin templates. Useful for allowing templates to perform actions that are dependent upon `bundle install`. -- cgit v1.2.3 From 507a952686adad4aaea9dcb9c4cad45491df40ee Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Fri, 5 Feb 2016 10:42:22 +0100 Subject: docs, link Rails specific assertions to the API. [ci skip] --- guides/source/testing.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/guides/source/testing.md b/guides/source/testing.md index f4894d4c11..1487a1e1a0 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -316,12 +316,12 @@ Rails adds some custom assertions of its own to the `minitest` framework: | Assertion | Purpose | | --------------------------------------------------------------------------------- | ------- | -| `assert_difference(expressions, difference = 1, message = nil) {...}` | Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.| -| `assert_no_difference(expressions, message = nil, &block)` | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| -| `assert_recognizes(expected_options, path, extras={}, message=nil)` | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| -| `assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)` | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| -| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.| -| `assert_redirected_to(options = {}, message=nil)` | Asserts that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.| +| [`assert_difference(expressions, difference = 1, message = nil) {...}`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_difference) | Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.| +| [`assert_no_difference(expressions, message = nil, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_difference) | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| +| [`assert_recognizes(expected_options, path, extras={}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_recognizes) | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| +| [`assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_generates) | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| +| [`assert_response(type, message = nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_response) | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.| +| [`assert_redirected_to(options = {}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_redirected_to) | Asserts that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.| You'll see the usage of some of these assertions in the next chapter. -- cgit v1.2.3 From f5032667f4f021548e2980b3b260949fd8cd1ff4 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Fri, 5 Feb 2016 10:43:38 +0100 Subject: docs, remove trailing whitespace from testing guide. [ci skip] --- guides/source/testing.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/guides/source/testing.md b/guides/source/testing.md index 1487a1e1a0..953947d32a 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -265,7 +265,7 @@ By now you've caught a glimpse of some of the assertions that are available. Ass Here's an extract of the assertions you can use with [`Minitest`](https://github.com/seattlerb/minitest), the default testing library used by Rails. The `[msg]` parameter is an optional string message you can -specify to make your test failure messages clearer. +specify to make your test failure messages clearer. | Assertion | Purpose | | ---------------------------------------------------------------- | ------- | @@ -589,7 +589,7 @@ class BlogFlowTest < ActionDispatch::IntegrationTest end ``` -We will take a look at `assert_select` to query the resulting HTML of a request in the "Testing Views" section below. It is used for testing the response of our request by asserting the presence of key HTML elements and their content. +We will take a look at `assert_select` to query the resulting HTML of a request in the "Testing Views" section below. It is used for testing the response of our request by asserting the presence of key HTML elements and their content. When we visit our root path, we should see `welcome/index.html.erb` rendered for the view. So this assertion should pass. @@ -912,13 +912,13 @@ We can also add a test for updating an existing Article. ```ruby test "should update article" do article = articles(:one) - + patch '/article', params: { id: article.id, article: { title: "updated" } } - + assert_redirected_to article_path(article) - # Reload association to fetch updated data and assert that title is updated. + # Reload association to fetch updated data and assert that title is updated. article.reload - assert_equal "updated", article.title + assert_equal "updated", article.title end ``` @@ -957,11 +957,11 @@ class ArticlesControllerTest < ActionDispatch::IntegrationTest test "should update article" do patch '/article', params: { id: @article.id, article: { title: "updated" } } - + assert_redirected_to article_path(@article) - # Reload association to fetch updated data and assert that title is updated. + # Reload association to fetch updated data and assert that title is updated. @article.reload - assert_equal "updated", @article.title + assert_equal "updated", @article.title end end ``` -- cgit v1.2.3 From be45d2f4774aff3b9de5602f974ada2668286623 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Fri, 5 Feb 2016 19:56:23 +0900 Subject: Check off some todos for the Testing guide with @senny [ci skip] --- guides/source/testing.md | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/guides/source/testing.md b/guides/source/testing.md index 953947d32a..1c64b2c0ac 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -329,11 +329,11 @@ You'll see the usage of some of these assertions in the next chapter. All the basic assertions such as `assert_equal` defined in `Minitest::Assertions` are also available in the classes we use in our own test cases. In fact, Rails provides the following classes for you to inherit from: -* `ActiveSupport::TestCase` -* `ActionMailer::TestCase` -* `ActionView::TestCase` -* `ActionDispatch::IntegrationTest` -* `ActiveJob::TestCase` +* [`ActiveSupport::TestCase`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html) +* [`ActionMailer::TestCase`](http://api.rubyonrails.org/classes/ActionMailer/TestCase.html) +* [`ActionView::TestCase`](http://api.rubyonrails.org/classes/ActionView/TestCase.html) +* [`ActionDispatch::IntegrationTest`](http://api.rubyonrails.org/classes/ActionDispatch/IntegrationTest.html) +* [`ActiveJob::TestCase`](http://api.rubyonrails.org/classes/ActiveJob/TestCase.html) Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests. @@ -415,6 +415,8 @@ You can find comprehensive documentation in the [Fixtures API documentation](htt _Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and written in YAML. There is one file per model. +NOTE: Fixtures are not designed to create every object that your tests need, and are best managed when only used for default data that can be applied to the common case. + You'll find fixtures under your `test/fixtures` directory. When you run `rails generate model` to create a new model, Rails automatically creates fixture stubs in this directory. #### YAML @@ -518,7 +520,7 @@ create test/models/article_test.rb create test/fixtures/articles.yml ``` -Model tests don't have their own superclass like `ActionMailer::TestCase` instead they inherit from `ActiveSupport::TestCase`. +Model tests don't have their own superclass like `ActionMailer::TestCase` instead they inherit from [`ActiveSupport::TestCase`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html). Integration Testing @@ -1007,6 +1009,8 @@ Testing Routes Like everything else in your Rails application, you can test your routes. +NOTE: If your application has complex routes, Rails provides a number of useful helpers to test them. + For more information on routing assertions available in Rails, see the API documentation for [`ActionDispatch::Assertions::RoutingAssertions`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html). Testing Views @@ -1053,7 +1057,7 @@ assert_select "ol" do end ``` -This assertion is quite powerful. For more advanced usage, refer to its [documentation](http://www.rubydoc.info/github/rails/rails-dom-testing). +This assertion is quite powerful. For more advanced usage, refer to its [documentation](https://github.com/rails/rails-dom-testing/blob/master/lib/rails/dom/testing/assertions/selector_assertions.rb). #### Additional View-Based Assertions @@ -1076,28 +1080,31 @@ end Testing Helpers --------------- +A helper is just a simple module where you can define methods which are +available into your views. + In order to test helpers, all you need to do is check that the output of the helper method matches what you'd expect. Tests related to the helpers are located under the `test/helpers` directory. -A helper test looks like so: +Given we have the following helper: ```ruby -require 'test_helper' - -class UserHelperTest < ActionView::TestCase +module UserHelper + def link_to_user(user) + link_to "#{user.first_name} #{user.last_name}", user + end end ``` -A helper is just a simple module where you can define methods which are -available into your views. To test the output of the helper's methods, you just -have to use a mixin like this: +We can test the output of this method like this: ```ruby class UserHelperTest < ActionView::TestCase test "should return the user's full name" do user = users(:david) - assert_equal "David Heinemeier Hansson", user_full_name(user) + + assert_dom_equal %{David Heinemeier Hansson}, link_to_user(user) end end ``` @@ -1261,8 +1268,10 @@ class ProductTest < ActiveJob::TestCase end ``` -Testing Time-Dependent Code ---------------------------- +Additional Testing Resources +---------------------------- + +### Testing Time-Dependent Code Rails provides built-in helper methods that enable you to assert that your time-sensitive code works as expected. -- cgit v1.2.3 From 2669f3753d15b688be4340524606dd62f2455f37 Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Fri, 5 Feb 2016 17:02:22 +0530 Subject: Update turbolinks-rails for passing railties test Ref - https://github.com/turbolinks/turbolinks-rails/pull/3 --- Gemfile | 2 +- Gemfile.lock | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 8e2c69214a..f8779a3e49 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ gem 'mocha', '~> 0.14', require: false gem 'rack-cache', '~> 1.2' gem 'jquery-rails' gem 'coffee-rails', '~> 4.1.0' -gem 'turbolinks', '~> 5.0.0.beta1' +gem 'turbolinks', github: 'turbolinks/turbolinks-rails' # require: false so bcrypt is loaded only when has_secure_password is used. # This is to avoid Active Model (and by extension the entire framework) diff --git a/Gemfile.lock b/Gemfile.lock index 6b619e701f..0d2ce7b97f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,6 +26,13 @@ GIT specs: sass (3.4.21) +GIT + remote: git://github.com/turbolinks/turbolinks-rails.git + revision: 1604dcd7bad911f1471d65e4f47cd19d844354f1 + specs: + turbolinks (5.0.0.beta1) + turbolinks-source + PATH remote: . specs: @@ -245,8 +252,6 @@ GEM thor (0.19.1) thread (0.1.7) thread_safe (0.3.5) - turbolinks (5.0.0.beta1) - turbolinks-source turbolinks-source (5.0.0.beta1.1) tzinfo (1.2.2) thread_safe (~> 0.1) @@ -315,7 +320,7 @@ DEPENDENCIES sqlite3 (~> 1.3.6) stackprof sucker_punch - turbolinks (~> 5.0.0.beta1) + turbolinks! tzinfo-data uglifier (>= 1.3.0) w3c_validators -- cgit v1.2.3 From cdb6f2eb9e9e60d0c6aa6ddcb854f74595fa5f06 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Fri, 5 Feb 2016 21:52:59 +0900 Subject: =?UTF-8?q?don=E2=80=99t=20explicitly=20mention=20EventMachine=20[?= =?UTF-8?q?ci=20skip]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow up to 6accef4e11b0c793e1c085536b5ed27f32b6a0c3 --- actioncable/lib/rails/generators/channel/templates/channel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actioncable/lib/rails/generators/channel/templates/channel.rb b/actioncable/lib/rails/generators/channel/templates/channel.rb index 6cf04ee61f..7bff3341c1 100644 --- a/actioncable/lib/rails/generators/channel/templates/channel.rb +++ b/actioncable/lib/rails/generators/channel/templates/channel.rb @@ -1,4 +1,4 @@ -# Be sure to restart your server when you modify this file. Action Cable runs in an EventMachine loop that does not support auto reloading. +# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading. <% module_namespacing do -%> class <%= class_name %>Channel < ApplicationCable::Channel def subscribed -- cgit v1.2.3 From 7fe32d28a8e73bfa826773c5a8777a316126cd38 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 5 Feb 2016 11:56:16 +0100 Subject: Cant run on an out-of-the-box OSX installation without running out of TOO MANY FILES OPEN --- actioncable/test/client_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index 199d2b90a3..4ade9832e0 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -187,7 +187,7 @@ class ClientTest < ActionCable::TestCase def test_many_clients with_puma_server do |port| - clients = 200.times.map { faye_client(port) } + clients = 100.times.map { faye_client(port) } clients.map {|c| Concurrent::Future.execute { c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') -- cgit v1.2.3 From 625baa69d14881ac49ba2e5c7d9cac4b222d7022 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 5 Feb 2016 15:35:37 +0100 Subject: Change the default adapter from inline to async --- activejob/CHANGELOG.md | 7 +++++++ activejob/lib/active_job/queue_adapter.rb | 6 +++--- guides/source/active_job_basics.md | 10 ++++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 981b6a20ec..229ef03879 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,10 @@ +* 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. This is especially important with things like jobs kicked off + in Active Record lifecycle callbacks. + + *DHH* + + ## Rails 5.0.0.beta2 (February 01, 2016) ## * No changes. diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb index 457015b741..7dfdd0ae5c 100644 --- a/activejob/lib/active_job/queue_adapter.rb +++ b/activejob/lib/active_job/queue_adapter.rb @@ -10,19 +10,19 @@ module ActiveJob included do class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false - self.queue_adapter = :inline + self.queue_adapter = :async end # Includes the setter method for changing the active queue adapter. module ClassMethods # Returns the backend queue provider. The default queue adapter - # is the +:inline+ queue. See QueueAdapters for more information. + # is the +:async+ queue. See QueueAdapters for more information. def queue_adapter _queue_adapter end # Specify the backend queue provider. The default queue adapter - # is the +:inline+ queue. See QueueAdapters for more + # is the +:async+ queue. See QueueAdapters for more # information. def queue_adapter=(name_or_adapter_or_class) self._queue_adapter = interpret_adapter(name_or_adapter_or_class) diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md index 76c13f0ea9..d8ea1ee079 100644 --- a/guides/source/active_job_basics.md +++ b/guides/source/active_job_basics.md @@ -109,10 +109,12 @@ That's it! Job Execution ------------- -For enqueuing and executing jobs you need to set up a queuing backend, that is to -say you need to decide for a 3rd-party queuing library that Rails should use. -Rails itself does not provide a sophisticated queuing system and just executes the -job immediately if no adapter is set. +For enqueuing and executing jobs in production you need to set up a queuing backend, +that is to say you need to decide for a 3rd-party queuing library that Rails should use. +Rails itself only provides an in-process queuing system, which only keeps the jobs in RAM. +If the process crashes or the machine is reset, then all outstanding jobs are lost with the +default async back-end. This may be fine for smaller apps or non-critical jobs, but most +production apps will need to pick a persistent backend. ### Backends -- cgit v1.2.3 From 439fadf758f6f0bc50e3d8c6f96b0ddbd613d299 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 5 Feb 2016 16:05:48 +0100 Subject: Missed a few spots in inline -> async switch --- activejob/README.md | 7 +------ activejob/lib/active_job/queue_adapter.rb | 2 +- activejob/lib/active_job/railtie.rb | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/activejob/README.md b/activejob/README.md index 7268186c00..8becac7753 100644 --- a/activejob/README.md +++ b/activejob/README.md @@ -20,12 +20,7 @@ switch between them without having to rewrite your jobs. ## Usage -Set the queue adapter for Active Job: - -``` ruby -ActiveJob::Base.queue_adapter = :inline # default queue adapter -``` -Note: To learn how to use your preferred queueing backend see its adapter +To learn how to use your preferred queueing backend see its adapter documentation at [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb index 7dfdd0ae5c..72e4ebf935 100644 --- a/activejob/lib/active_job/queue_adapter.rb +++ b/activejob/lib/active_job/queue_adapter.rb @@ -4,7 +4,7 @@ require 'active_support/core_ext/string/inflections' module ActiveJob # The ActiveJob::QueueAdapter module is used to load the - # correct adapter. The default queue adapter is the +:inline+ queue. + # correct adapter. The default queue adapter is the +:async+ queue. module QueueAdapter #:nodoc: extend ActiveSupport::Concern diff --git a/activejob/lib/active_job/railtie.rb b/activejob/lib/active_job/railtie.rb index 6538ac1b30..b249ec09dd 100644 --- a/activejob/lib/active_job/railtie.rb +++ b/activejob/lib/active_job/railtie.rb @@ -12,7 +12,7 @@ module ActiveJob initializer "active_job.set_configs" do |app| options = app.config.active_job - options.queue_adapter ||= :inline + options.queue_adapter ||= :async ActiveSupport.on_load(:active_job) do options.each { |k,v| send("#{k}=", v) } -- cgit v1.2.3 From 8417d967e016f0219cc4ec30bf0d3908ce6cd29b Mon Sep 17 00:00:00 2001 From: Andrew Kaspick Date: Fri, 5 Feb 2016 13:59:17 -0500 Subject: When generating a mailer, you must specify Mailer in the class name in order to generate the proper files. Some of the docs/comments are missing this important detail. --- actionmailer/lib/action_mailer/base.rb | 2 +- actionmailer/lib/rails/generators/mailer/USAGE | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index bb3cb1be45..8285a8618e 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -13,7 +13,7 @@ module ActionMailer # # To use Action Mailer, you need to create a mailer model. # - # $ rails generate mailer Notifier + # $ rails generate mailer NotifierMailer # # The generated model inherits from ApplicationMailer which in turn # inherits from ActionMailer::Base. A mailer model defines methods diff --git a/actionmailer/lib/rails/generators/mailer/USAGE b/actionmailer/lib/rails/generators/mailer/USAGE index 2b0a078109..9f2a6b4cd8 100644 --- a/actionmailer/lib/rails/generators/mailer/USAGE +++ b/actionmailer/lib/rails/generators/mailer/USAGE @@ -8,7 +8,7 @@ Description: Example: ======== - rails generate mailer Notifications signup forgot_password invoice + rails generate mailer NotificationsMailer signup forgot_password invoice creates a Notifications mailer class, views, and test: Mailer: app/mailers/notifications_mailer.rb -- cgit v1.2.3 From a640da454fdb9cd8806a1b6bd98c2da93f1b53b9 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 5 Feb 2016 11:42:06 -0800 Subject: disable controller / view thread spawning in tests Tests can (and do) access the database from the main thread. In this case they were starting a transaction, then making a request. The request would create a new thread, which would allocate a new database connection. Since the main thread started a transaction that contains data that the new thread wants to see, the new thread would not see it due to data visibility from transactions. Spawning the new thread in production is fine because middleware should not be doing database manipulation similar to the test harness. Before 603fe20c it was possible to set the database connection id based on a thread local, but 603fe20c changes the connection lookup code to never look at the "connection id" but only at the thread object itself. Without that indirection, we can't force threads to use the same connection pool as another thread. Fixes #23483 --- actionpack/lib/action_controller/metal/live.rb | 15 +++++++++++++-- actionpack/lib/action_controller/test_case.rb | 10 ++++++++++ actionpack/test/controller/live_stream_test.rb | 9 ++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index e3c540bf5f..acc4507b2d 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -237,9 +237,8 @@ module ActionController # This processes the action in a child thread. It lets us return the # response code and headers back up the rack stack, and still process # the body in parallel with sending data to the client - Thread.new { + new_controller_thread { t2 = Thread.current - t2.abort_on_exception = true # Since we're processing the view in a different thread, copy the # thread locals from the main thread to the child thread. :'( @@ -270,6 +269,18 @@ module ActionController raise error if error end + # Spawn a new thread to serve up the controller in. This is to get + # around the fact that Rack isn't based around IOs and we need to use + # a thread to stream data from the response bodies. Nobody should call + # this method except in Rails internals. Seriously! + def new_controller_thread # :nodoc: + Thread.new { + t2 = Thread.current + t2.abort_on_exception = true + yield + } + end + def log_error(exception) logger = ActionController::Base.logger return unless logger diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index b43bb9dc17..ac45b2e363 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -12,6 +12,16 @@ module ActionController include Testing::Functional end + module Live + # Disable controller / rendering threads in tests. User tests can access + # the database on the main thread, so they could open a txn, then the + # controller thread will open a new connection and try to access data + # that's only visible to the main thread's txn. This is the problem in #23483 + def new_controller_thread # :nodoc: + yield + end + end + # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1. # Please use ActionDispatch::IntegrationTest going forward. class TestRequest < ActionDispatch::TestRequest #:nodoc: diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 2ef9734269..0c3884cd38 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -152,7 +152,6 @@ module ActionController def thread_locals tc.assert_equal 'aaron', Thread.current[:setting] - tc.assert_not_equal Thread.current.object_id, Thread.current[:originating_thread] response.headers['Content-Type'] = 'text/event-stream' %w{ hello world }.each do |word| @@ -261,6 +260,14 @@ module ActionController end end + def setup + super + + def @controller.new_controller_thread + Thread.new { yield } + end + end + def test_set_cookie get :set_cookie assert_equal({'hello' => 'world'}, @response.cookies) -- cgit v1.2.3 From 4dd0cddc436646b15dfa46eb7287ab0683eea060 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Fri, 5 Feb 2016 14:58:23 -0500 Subject: No need to specify Ruby patch version on Travis CI Travis CI now select the latest patch version of Ruby automatically when given MAJOR.MINOR version string. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ae38617b99..5a4ba5c33d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,8 +24,8 @@ env: - "GEM=aj:integration" - "GEM=guides" rvm: - - 2.2.4 - - 2.3.0 + - 2.2 + - 2.3 - ruby-head matrix: allow_failures: -- cgit v1.2.3 From d666a5a5cfd4692cb8ff3914d7ff0e3885c7eb5c Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 5 Feb 2016 13:29:28 -0700 Subject: Revert "Dump indexes in `create_table` instead of `add_index`" This reverts commit 99801c6a7b69eb4b006a55de17ada78f3a0fa4c1. Ultimately it doesn't matter whether `add_index` or `t.index` are used in the schema dumper in any meaningful way. There are gems out there which hook into the old behavior for things like indexing materialized views. Since the reverted commit doesn't seem to add much benefit, there's no reason for us to break these gems. --- activerecord/CHANGELOG.md | 7 ------- activerecord/lib/active_record/schema_dumper.rb | 10 ++++++---- activerecord/test/cases/schema_dumper_test.rb | 20 ++++++++++---------- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 4ca33491aa..0832652d32 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1004,13 +1004,6 @@ *Alex Coomans* -* Dump indexes in `create_table` instead of `add_index`. - - If the adapter supports indexes in `create_table`, generated SQL is - slightly more efficient. - - *Ryuta Kamizono* - * Correctly dump `:options` on `create_table` for MySQL. *Ryuta Kamizono* diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 65005bd44b..f115c7542b 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -178,11 +178,11 @@ HEADER tbl.puts end - indexes(table, tbl) - tbl.puts " end" tbl.puts + indexes(table, tbl) + tbl.rewind stream.print tbl.read rescue => e @@ -198,7 +198,8 @@ HEADER if (indexes = @connection.indexes(table)).any? add_index_statements = indexes.map do |index| statement_parts = [ - "t.index #{index.columns.inspect}", + "add_index #{remove_prefix_and_suffix(index.table).inspect}", + index.columns.inspect, "name: #{index.name.inspect}", ] statement_parts << 'unique: true' if index.unique @@ -212,10 +213,11 @@ HEADER statement_parts << "using: #{index.using.inspect}" if index.using statement_parts << "type: #{index.type.inspect}" if index.type - " #{statement_parts.join(', ')}" + " #{statement_parts.join(', ')}" end stream.puts add_index_statements.sort.join("\n") + stream.puts end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 25f4a69ad1..8def74e75b 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -171,24 +171,24 @@ class SchemaDumperTest < ActiveRecord::TestCase end def test_schema_dumps_index_columns_in_right_order - index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_index/).first.strip + index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) - assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition + assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition else - assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition + assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition end end def test_schema_dumps_partial_indices - index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_partial_index/).first.strip + index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip if current_adapter?(:PostgreSQLAdapter) - assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition + assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition elsif current_adapter?(:Mysql2Adapter) - assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition + assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index? - assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition + assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition else - assert_equal 't.index ["firm_id", "type"], name: "company_partial_index"', index_definition + assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition end end @@ -235,8 +235,8 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dumps_index_type output = standard_dump - assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output - assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output + assert_match %r{add_index "key_tests", \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output + assert_match %r{add_index "key_tests", \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output end end -- cgit v1.2.3 From 84b72a812f9fb488241f06ec573dd751d52f7a67 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Thu, 4 Feb 2016 23:06:33 +0100 Subject: Fix mixing line filters with Minitest's -n filter. Previous commit accidentally broke mixing line filters with string -n filter. Fix by checking if it is a string and returning it. We also need to ensure the -n filter carry forward into any other composite filters. Fix by letting the named filter be extractable, so we'll keep this for the next runnable's run. --- railties/lib/rails/test_unit/line_filtering.rb | 16 ++++++++++++---- railties/test/application/test_runner_test.rb | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/railties/lib/rails/test_unit/line_filtering.rb b/railties/lib/rails/test_unit/line_filtering.rb index b7635c71f4..dd9732bb12 100644 --- a/railties/lib/rails/test_unit/line_filtering.rb +++ b/railties/lib/rails/test_unit/line_filtering.rb @@ -13,9 +13,12 @@ module Rails end class CompositeFilter # :nodoc: + attr_reader :named_filter + def initialize(runnable, filter, patterns) @runnable = runnable - @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact + @named_filter = derive_named_filter(filter) + @filters = [ @named_filter, *derive_line_filters(patterns) ].compact end # Minitest uses === to find matching filters. @@ -24,9 +27,14 @@ module Rails end private - def derive_regexp(filter) - # Regexp filtering copied from Minitest. - Regexp.new $1 if filter =~ %r%/(.*)/% + def derive_named_filter(filter) + if filter.respond_to?(:named_filter) + filter.named_filter + elsif filter =~ %r%/(.*)/% # Regexp filtering copied from Minitest. + Regexp.new $1 + elsif filter.is_a?(String) + filter + end end def derive_line_filters(patterns) diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index 821ac9b033..7ecadb60ca 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -406,6 +406,30 @@ module ApplicationTests end end + def test_line_filter_with_minitest_string_filter + app_file 'test/models/post_test.rb', <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + test 'by line' do + puts 'by line' + assert true + end + + test 'by name' do + puts 'by name' + assert true + end + end + RUBY + + run_test_command('test/models/post_test.rb:4 -n test_by_name').tap do |output| + assert_match 'by line', output + assert_match 'by name', output + assert_match '2 runs, 2 assertions', output + end + end + def test_shows_filtered_backtrace_by_default create_backtrace_test -- cgit v1.2.3 From 38b5af6595338cb2212980062d9aaf51241878cc Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 5 Feb 2016 13:43:56 -0800 Subject: add missing require --- actionpack/lib/action_dispatch/http/response.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 14f86c7c07..fa4c54701a 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/module/attribute_accessors' require 'action_dispatch/http/filter_redirect' +require 'action_dispatch/http/cache' require 'monitor' module ActionDispatch # :nodoc: -- cgit v1.2.3 From 3ee7bc87b1d96a4edf2e93f155b5b38568dabd0d Mon Sep 17 00:00:00 2001 From: yui-knk Date: Sat, 6 Feb 2016 14:01:07 +0900 Subject: [ci skip] Good bye SQLite2 Follow up of https://github.com/rails/rails/commit/c9feea6c9ab4494b0cb0b8cf4316847854f65af6 --- activerecord/test/cases/migration_test.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index f51e366b1d..99e288c25b 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -192,8 +192,6 @@ class MigrationTest < ActiveRecord::TestCase # of 0, they take on the compile-time limit for precision and scale, # so the following should succeed unless you have used really wacky # compilation options - # - SQLite2 has the default behavior of preserving all data sent in, - # so this happens there too assert_kind_of BigDecimal, b.value_of_e assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e elsif current_adapter?(:SQLite3Adapter) -- cgit v1.2.3 From ea785e535eaf8eeb2cb5f3a1c025f4ebaf145f31 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Sat, 6 Feb 2016 14:54:26 +0900 Subject: Revert "When generating a mailer, you must specify Mailer in the class name in" This reverts commit 8417d967e016f0219cc4ec30bf0d3908ce6cd29b. In 5697bdbb6da5d08e541a3b12251cec90269b059b and af3eb5961e55a46b011be797e71f615f20f56686, add mailer suffix to generated files and classes. Therefore, no longer need to specify `Mailer` to class name. [ci skip] --- actionmailer/lib/action_mailer/base.rb | 2 +- actionmailer/lib/rails/generators/mailer/USAGE | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 8285a8618e..bb3cb1be45 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -13,7 +13,7 @@ module ActionMailer # # To use Action Mailer, you need to create a mailer model. # - # $ rails generate mailer NotifierMailer + # $ rails generate mailer Notifier # # The generated model inherits from ApplicationMailer which in turn # inherits from ActionMailer::Base. A mailer model defines methods diff --git a/actionmailer/lib/rails/generators/mailer/USAGE b/actionmailer/lib/rails/generators/mailer/USAGE index 9f2a6b4cd8..2b0a078109 100644 --- a/actionmailer/lib/rails/generators/mailer/USAGE +++ b/actionmailer/lib/rails/generators/mailer/USAGE @@ -8,7 +8,7 @@ Description: Example: ======== - rails generate mailer NotificationsMailer signup forgot_password invoice + rails generate mailer Notifications signup forgot_password invoice creates a Notifications mailer class, views, and test: Mailer: app/mailers/notifications_mailer.rb -- cgit v1.2.3 From ecd8f8f0edec97675b5fafe938c690fa6e46fa90 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 6 Feb 2016 13:51:14 +0900 Subject: Revert "No need to specify Ruby patch version on Travis CI" --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5a4ba5c33d..ae38617b99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,8 +24,8 @@ env: - "GEM=aj:integration" - "GEM=guides" rvm: - - 2.2 - - 2.3 + - 2.2.4 + - 2.3.0 - ruby-head matrix: allow_failures: -- cgit v1.2.3 From 91678fe9f78b498cc9919355213bc06b11841ce4 Mon Sep 17 00:00:00 2001 From: Genadi Samokovarov Date: Sat, 6 Feb 2016 09:14:42 +0200 Subject: Replace old Rails greeting references A couple of the READMEs were still referring the old welcome page. This is a small change that goes over it. [ci skip] --- README.md | 2 +- railties/RDOC_MAIN.rdoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b4f32940fc..0c58685573 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ and may also be used independently outside Rails. Run with `--help` or `-h` for options. 4. Using a browser, go to `http://localhost:3000` and you'll see: -"Welcome aboard: You're riding Ruby on Rails!" +"Yay! You’re on Rails!" 5. Follow the guidelines to start developing your application. You may find the following resources handy: diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc index ce024563c4..26a25ee9dc 100644 --- a/railties/RDOC_MAIN.rdoc +++ b/railties/RDOC_MAIN.rdoc @@ -51,7 +51,7 @@ can read more about Action Pack in its {README}[link:files/actionpack/README_rdo 4. Go to http://localhost:3000 and you'll see: - "Welcome aboard: You're riding Ruby on Rails!" + "Yay! You’re on Rails!" 5. Follow the guidelines to start developing your application. You may find the following resources handy: -- cgit v1.2.3 From 4ca3f99d4ceafa8e3fde3f4baa5e4c611b764714 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 6 Feb 2016 17:43:56 +0900 Subject: Remove duplicated `require 'arel'` It appears first in `lib/active_record.rb`. --- activerecord/lib/active_record/base.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index fdffc3e6b9..7ed2fe48be 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -13,7 +13,6 @@ require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/introspection' require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/class/subclasses' -require 'arel' require 'active_record/attribute_decorators' require 'active_record/errors' require 'active_record/log_subscriber' -- cgit v1.2.3 From 00c64e8ae2c312139e5a242331d3919fe139b87c Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Sat, 6 Feb 2016 12:15:28 +0900 Subject: set association name to generated fixtures if attribute is reference It has been changed to require `belongs_to` by default in Rails 5. Therefore in order to pass the controller test, have association of set to fixtures. Fixes #23384 --- .../rails/generators/test_unit/model/templates/fixtures.yml | 2 +- railties/test/application/rake_test.rb | 9 ++++----- railties/test/generators/model_generator_test.rb | 10 +++++----- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml index 2656767eb4..0681780c97 100644 --- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml +++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml @@ -6,7 +6,7 @@ <%- if attribute.password_digest? -%> password_digest: <%%= BCrypt::Password.create('secret') %> <%- elsif attribute.reference? -%> - <%= yaml_key_value(attribute.column_name.sub(/_id$/, ''), attribute.default) %> + <%= yaml_key_value(attribute.column_name.sub(/_id$/, ''), attribute.default || name) %> <%- else -%> <%= yaml_key_value(attribute.column_name, attribute.default) %> <%- end -%> diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 745a3e3ec5..3d3e47de8d 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -291,12 +291,11 @@ module ApplicationTests assert_no_match(/Errors running/, output) end - def test_scaffold_with_references_columns_tests_pass_when_belongs_to_is_optional - app_file "config/initializers/active_record_belongs_to_required_by_default.rb", - "Rails.application.config.active_record.belongs_to_required_by_default = false" - + def test_scaffold_with_references_columns_tests_pass_by_default output = Dir.chdir(app_path) do - `bin/rails generate scaffold LineItems product:references cart:belongs_to; + `bin/rails generate model Product; + bin/rails generate model Cart; + bin/rails generate scaffold LineItems product:references cart:belongs_to; RAILS_ENV=test bin/rails db:migrate test` end diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 814f4c050e..c8c8f0aa3b 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -298,18 +298,18 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_fixtures_use_the_references_ids run_generator ["LineItem", "product:references", "cart:belongs_to"] - assert_file "test/fixtures/line_items.yml", /product: \n cart: / + assert_file "test/fixtures/line_items.yml", /product: one\n cart: one/ assert_generated_fixture("test/fixtures/line_items.yml", - {"one"=>{"product"=>nil, "cart"=>nil}, "two"=>{"product"=>nil, "cart"=>nil}}) + {"one"=>{"product"=>"one", "cart"=>"one"}, "two"=>{"product"=>"two", "cart"=>"two"}}) end def test_fixtures_use_the_references_ids_and_type run_generator ["LineItem", "product:references{polymorphic}", "cart:belongs_to"] - assert_file "test/fixtures/line_items.yml", /product: \n product_type: Product\n cart: / + assert_file "test/fixtures/line_items.yml", /product: one\n product_type: Product\n cart: one/ assert_generated_fixture("test/fixtures/line_items.yml", - {"one"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil}, - "two"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil}}) + {"one"=>{"product"=>"one", "product_type"=>"Product", "cart"=>"one"}, + "two"=>{"product"=>"two", "product_type"=>"Product", "cart"=>"two"}}) end def test_fixtures_respect_reserved_yml_keywords -- cgit v1.2.3 From d9bdc2f9ef44c8d24b8d331b55e5878cd211542f Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 6 Feb 2016 17:28:17 +0900 Subject: MariaDB does not support JSON type Fixes #22980. --- activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index c3c5b660fd..23b33a3555 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -42,7 +42,7 @@ module ActiveRecord end def supports_json? - version >= '5.7.8' + !mariadb? && version >= '5.7.8' end # HELPER METHODS =========================================== -- cgit v1.2.3 From 04021c4b78abc89045a7d3daf517a5d2dc63e5a7 Mon Sep 17 00:00:00 2001 From: Syamil MJ Date: Sun, 7 Feb 2016 04:07:48 +0800 Subject: Remove unused private method --- railties/lib/rails/rack/logger.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb index aa7d3ca6c6..b63d3a58d2 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -72,10 +72,6 @@ module Rails instrumenter.finish 'request.action_dispatch', request: request end - def development? - Rails.env.development? - end - def logger Rails.logger end -- cgit v1.2.3 From 4e4bcae0841b81438ce3b6c627eebc7536566bf5 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Sat, 6 Feb 2016 22:28:59 +0100 Subject: Avoid coupling Action Pack to Railties. Referencing Rails.env without checking if it's defined couples us to Railties. Fix by avoiding the line breaks if we don't have an env check to rely on. --- actionpack/lib/action_controller/log_subscriber.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index d1d6acac26..450a04779f 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -26,7 +26,7 @@ module ActionController end message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" message << " (#{additions.join(" | ".freeze)})" unless additions.blank? - message << "\n\n" if Rails.env.development? + message << "\n\n" if defined?(Rails.env) && Rails.env.development? message end -- cgit v1.2.3 From 3e4a69e52d8c8f0335e0ddbb46fe21009b962334 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Sun, 7 Feb 2016 08:24:57 +1030 Subject: Hand off the interlock to the new thread in AC::Live Most importantly, the original request thread must yield its share lock while waiting for the live thread to commit -- otherwise a request's base and live threads can deadlock against each other. --- actionpack/lib/action_controller/metal/live.rb | 51 ++++++++++++---------- .../lib/active_support/concurrency/share_lock.rb | 48 +++++++++++++------- .../lib/active_support/dependencies/interlock.rb | 6 +++ activesupport/test/share_lock_test.rb | 26 +++++++++++ 4 files changed, 93 insertions(+), 38 deletions(-) diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index acc4507b2d..fc20e7a421 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -238,34 +238,39 @@ module ActionController # response code and headers back up the rack stack, and still process # the body in parallel with sending data to the client new_controller_thread { - t2 = Thread.current - - # Since we're processing the view in a different thread, copy the - # thread locals from the main thread to the child thread. :'( - locals.each { |k,v| t2[k] = v } - - begin - super(name) - rescue => e - if @_response.committed? - begin - @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html - @_response.stream.call_on_error - rescue => exception - log_error(exception) - ensure - log_error(e) - @_response.stream.close + ActiveSupport::Dependencies.interlock.running do + t2 = Thread.current + + # Since we're processing the view in a different thread, copy the + # thread locals from the main thread to the child thread. :'( + locals.each { |k,v| t2[k] = v } + + begin + super(name) + rescue => e + if @_response.committed? + begin + @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html + @_response.stream.call_on_error + rescue => exception + log_error(exception) + ensure + log_error(e) + @_response.stream.close + end + else + error = e end - else - error = e + ensure + @_response.commit! end - ensure - @_response.commit! end } - @_response.await_commit + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @_response.await_commit + end + raise error if error end diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb index 8e4ca272ba..2ac278a2f1 100644 --- a/activesupport/lib/active_support/concurrency/share_lock.rb +++ b/activesupport/lib/active_support/concurrency/share_lock.rb @@ -51,7 +51,7 @@ module ActiveSupport if busy_for_exclusive?(purpose) return false if no_wait - yield_shares(purpose, compatible) do + yield_shares(purpose: purpose, compatible: compatible) do @cv.wait_while { busy_for_exclusive?(purpose) } end end @@ -73,10 +73,10 @@ module ActiveSupport if @exclusive_depth == 0 @exclusive_thread = nil - yield_shares(nil, compatible) do - @cv.broadcast + yield_shares(compatible: compatible) do @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) } end + @cv.broadcast end end end @@ -127,6 +127,36 @@ module ActiveSupport end end + def yield_shares(purpose: nil, compatible: []) + loose_shares = previous_wait = nil + synchronize do + if loose_shares = @sharing.delete(Thread.current) + if previous_wait = @waiting[Thread.current] + purpose = nil unless purpose == previous_wait[0] + compatible &= previous_wait[1] + end + @waiting[Thread.current] = [purpose, compatible] + end + + @cv.broadcast + end + + begin + yield + ensure + synchronize do + @cv.wait_while { @exclusive_thread && @exclusive_thread != Thread.current } + + if previous_wait + @waiting[Thread.current] = previous_wait + else + @waiting.delete Thread.current + end + @sharing[Thread.current] = loose_shares if loose_shares + end + end + end + private # Must be called within synchronize @@ -143,18 +173,6 @@ module ActiveSupport def eligible_waiters?(compatible) @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } } end - - def yield_shares(purpose, compatible) - loose_shares = @sharing.delete(Thread.current) - @waiting[Thread.current] = [purpose, compatible] if loose_shares - - begin - yield - ensure - @waiting.delete Thread.current - @sharing[Thread.current] = loose_shares if loose_shares - end - end end end end diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb index b6a1b25eee..47bcecbb35 100644 --- a/activesupport/lib/active_support/dependencies/interlock.rb +++ b/activesupport/lib/active_support/dependencies/interlock.rb @@ -42,6 +42,12 @@ module ActiveSupport #:nodoc: yield end end + + def permit_concurrent_loads + @lock.yield_shares(compatible: [:load]) do + yield + end + end end end end diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb index 12953d99a6..68fa5bb69e 100644 --- a/activesupport/test/share_lock_test.rb +++ b/activesupport/test/share_lock_test.rb @@ -287,6 +287,32 @@ class ShareLockTest < ActiveSupport::TestCase assert_threads_not_stuck threads end + def test_manual_yield + ready = Concurrent::CyclicBarrier.new(2) + done = Concurrent::CyclicBarrier.new(2) + + threads = [ + Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive(purpose: :x) {} + done.wait + end + end, + + Thread.new do + @lock.sharing do + ready.wait + @lock.yield_shares(compatible: [:x]) do + done.wait + end + end + end, + ] + + assert_threads_not_stuck threads + end + def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads scratch_pad = [] scratch_pad_mutex = Mutex.new -- cgit v1.2.3 From 9f2df7856d0515f097d517395f944e6faf414d6d Mon Sep 17 00:00:00 2001 From: thedarkone Date: Sun, 7 Feb 2016 00:57:37 +0100 Subject: AS::Conc::ShareLock#yield_shares tests. --- activesupport/test/share_lock_test.rb | 107 ++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb index 68fa5bb69e..5fceebf023 100644 --- a/activesupport/test/share_lock_test.rb +++ b/activesupport/test/share_lock_test.rb @@ -313,6 +313,113 @@ class ShareLockTest < ActiveSupport::TestCase assert_threads_not_stuck threads end + def test_manual_incompatible_yield + ready = Concurrent::CyclicBarrier.new(2) + done = Concurrent::CyclicBarrier.new(2) + + threads = [ + Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive(purpose: :x) {} + done.wait + end + end, + + Thread.new do + @lock.sharing do + ready.wait + @lock.yield_shares(compatible: [:y]) do + done.wait + end + end + end, + ] + + assert_threads_stuck threads + ensure + threads.each(&:kill) if threads + end + + def test_manual_recursive_yield + ready = Concurrent::CyclicBarrier.new(2) + done = Concurrent::CyclicBarrier.new(2) + do_compatible_nesting = Concurrent::CountDownLatch.new + + threads = [ + Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive(purpose: :x) {} + done.wait + end + end, + + Thread.new do + @lock.sharing do + ready.wait + @lock.yield_shares(compatible: [:y]) do + do_compatible_nesting.wait + @lock.sharing do + @lock.yield_shares(compatible: [:x, :y]) do + done.wait + end + end + end + end + end + ] + + assert_threads_stuck threads + do_compatible_nesting.count_down + assert_threads_not_stuck threads + end + + def test_manual_recursive_yield_restores_previous_compatible + ready = Concurrent::CyclicBarrier.new(2) + do_nesting = Concurrent::CountDownLatch.new + after_nesting = Concurrent::CountDownLatch.new + + incompatible_thread = Thread.new do + ready.wait + @lock.exclusive(purpose: :z) {} + end + + recursive_yield_shares_thread = Thread.new do + @lock.sharing do + ready.wait + @lock.yield_shares(compatible: [:y]) do + do_nesting.wait + @lock.sharing do + @lock.yield_shares(compatible: [:x, :y]) {} + end + after_nesting.wait + end + end + end + + assert_threads_stuck incompatible_thread + do_nesting.count_down + assert_threads_stuck incompatible_thread + + compatible_thread = Thread.new do + @lock.exclusive(purpose: :y) {} + end + assert_threads_not_stuck compatible_thread + + post_nesting_incompatible_thread = Thread.new do + @lock.exclusive(purpose: :x) {} + end + assert_threads_stuck post_nesting_incompatible_thread + + after_nesting.count_down + assert_threads_not_stuck recursive_yield_shares_thread + # post_nesting_incompatible_thread can now proceed + assert_threads_not_stuck post_nesting_incompatible_thread + # assert_threads_not_stuck can now proceed + assert_threads_not_stuck incompatible_thread + end + def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads scratch_pad = [] scratch_pad_mutex = Mutex.new -- cgit v1.2.3 From 6809758bc65825966bcf9dd1f716e4332724b609 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Sun, 7 Feb 2016 09:12:19 +0900 Subject: fix typo in `assert_enqueued_jobs` example [ci skip] --- activejob/lib/active_job/test_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index 44ddfa5f69..ed0c05c1e5 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -58,7 +58,7 @@ module ActiveJob # The number of times a specific job is enqueued can be asserted. # # def test_logging_job - # assert_enqueued_jobs 2, only: LoggingJob do + # assert_enqueued_jobs 1, only: LoggingJob do # LoggingJob.perform_later # HelloJob.perform_later('jeremy') # end -- cgit v1.2.3 From aa38f7d6159574e3cfb3f2e313fa2c5e16e2ee1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Emin=20=C4=B0NA=C3=87?= Date: Sat, 6 Feb 2016 01:20:21 +0200 Subject: Added numeric helper into migrations. With this addition, you can add a column into the table like: ``` create_table(:numeric_types) do |t| t.numeric :foo, precision: 10, scale: 2, default: 2.0 end ``` The result of the migration above is same with: ``` create_table(:numeric_types) do |t| t.decimal :foo, precision: 10, scale: 2, default: 2.0 end ``` --- activerecord/CHANGELOG.md | 10 ++++++++++ .../connection_adapters/abstract/schema_definitions.rb | 2 ++ 2 files changed, 12 insertions(+) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 4ca33491aa..a25e850240 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,13 @@ +* Added `numeric` helper into migrations. + + Example: + + create_table(:numeric_types) do |t| + t.numeric :numeric_type, precision: 10, scale: 2 + end + + *Mehmet Emin İNAÇ* + * Bumped the minimum supported version of PostgreSQL to >= 9.1. Both PG 9.0 and 8.4 are past their end of life date: http://www.postgresql.org/support/versioning/ diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 690e0ba957..cb10ca9929 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -182,6 +182,7 @@ module ActiveRecord end CODE end + alias_method :numeric, :decimal end # Represents the schema of an SQL table in an abstract way. This class @@ -436,6 +437,7 @@ module ActiveRecord # t.bigint # t.float # t.decimal + # t.numeric # t.datetime # t.timestamp # t.time -- cgit v1.2.3 From 73d1975810cf83351f3c72e3aaa2ea43a64d08b9 Mon Sep 17 00:00:00 2001 From: Scott Bronson Date: Sat, 6 Feb 2016 18:24:10 -0800 Subject: fix 'method redefined' warnings --- actionpack/lib/action_controller/test_case.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index ac45b2e363..0c4b661214 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -17,6 +17,7 @@ module ActionController # the database on the main thread, so they could open a txn, then the # controller thread will open a new connection and try to access data # that's only visible to the main thread's txn. This is the problem in #23483 + remove_method :new_controller_thread def new_controller_thread # :nodoc: yield end -- cgit v1.2.3 From 87f060a7ee00fa87b1b90ecb831e9fcb6b2376a7 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sun, 7 Feb 2016 07:12:16 +0000 Subject: fix indentation --- activerecord/lib/active_record/reflection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 666039a9f3..8b22edcb5b 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -137,7 +137,7 @@ module ActiveRecord # ThroughReflection # PolymorphicReflection # RuntimeReflection - class AbstractReflection # :nodoc: + class AbstractReflection # :nodoc: def table_name klass.table_name end -- cgit v1.2.3 From 9a9587ff301cd3101a02f3aff62463a7f9f355e2 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sun, 7 Feb 2016 07:25:03 +0000 Subject: Separate for new and existing applications clearly [ci skip] --- guides/source/api_app.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 64b6bb64f2..563214896a 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -135,6 +135,8 @@ If you're building a Rails application that will be an API server first and foremost, you can start with a more limited subset of Rails and add in features as needed. +### Creating a new application + You can generate a new api Rails app: ```bash @@ -153,6 +155,8 @@ This will do three main things for you: - Configure the generators to skip generating views, helpers and assets when you generate a new resource. +### Changing an existing application + If you want to take an existing application and make it an API one, read the following steps. -- cgit v1.2.3 From 38f6d4e8182ea4be1a0c5304b7d5cde25c93572a Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Sun, 7 Feb 2016 15:27:48 +0530 Subject: Publish guide on "Using Rails for API-only Applications" Fixes #23322 [ci skip] --- guides/source/documents.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index fdd6d4d33d..2cf613f47f 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -137,7 +137,6 @@ description: This guide explains how to profile your Rails applications to improve performance. - name: Using Rails for API-only Applications - work_in_progress: true url: api_app.html description: This guide explains how to effectively use Rails to develop a JSON API application. -- cgit v1.2.3 From d2c671e3cffc60ef00df63a28cc9ae0eb89f1b31 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 8 Feb 2016 00:36:21 +1030 Subject: Eagerly reacquire when start_sharing is nested inside yield_shares A full write-preferring wait can lead to deadlock. --- .../lib/active_support/concurrency/share_lock.rb | 29 ++++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb index 2ac278a2f1..ed3345d4dc 100644 --- a/activesupport/lib/active_support/concurrency/share_lock.rb +++ b/activesupport/lib/active_support/concurrency/share_lock.rb @@ -6,12 +6,6 @@ module ActiveSupport # A share/exclusive lock, otherwise known as a read/write lock. # # https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock - #-- - # Note that a pending Exclusive lock attempt does not block incoming - # Share requests (i.e., we are "read-preferring"). That seems - # consistent with the behavior of "loose" upgrades, but may be the - # wrong choice otherwise: it nominally reduces the possibility of - # deadlock by risking starvation instead. class ShareLock include MonitorMixin @@ -73,8 +67,10 @@ module ActiveSupport if @exclusive_depth == 0 @exclusive_thread = nil - yield_shares(compatible: compatible) do - @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) } + if eligible_waiters?(compatible) + yield_shares(compatible: compatible) do + @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) } + end end @cv.broadcast end @@ -83,7 +79,15 @@ module ActiveSupport def start_sharing(purpose: :share) synchronize do - if @sharing[Thread.current] == 0 && @exclusive_thread != Thread.current && busy_for_sharing?(purpose) + if @sharing[Thread.current] > 0 || @exclusive_thread == Thread.current + # We already hold a lock; nothing to wait for + elsif @waiting[Thread.current] + # We're nested inside a +yield_shares+ call: we'll resume as + # soon as there isn't an exclusive lock in our way + @cv.wait_while { @exclusive_thread } + else + # This is an initial / outermost share call: any outstanding + # requests for an exclusive lock get to go first @cv.wait_while { busy_for_sharing?(purpose) } end @sharing[Thread.current] += 1 @@ -127,6 +131,9 @@ module ActiveSupport end end + # Temporarily give up all held Share locks while executing the + # supplied block, allowing any +compatible+ exclusive lock request + # to proceed. def yield_shares(purpose: nil, compatible: []) loose_shares = previous_wait = nil synchronize do @@ -136,9 +143,9 @@ module ActiveSupport compatible &= previous_wait[1] end @waiting[Thread.current] = [purpose, compatible] - end - @cv.broadcast + @cv.broadcast + end end begin -- cgit v1.2.3 From 7e35cb2987e146de29d50f97a43caef61e7588af Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 7 Feb 2016 15:34:13 +0100 Subject: Add SVG as a default mime type --- actionpack/CHANGELOG.md | 4 ++++ actionpack/lib/action_dispatch/http/mime_types.rb | 1 + 2 files changed, 5 insertions(+) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 809b735deb..93e598e493 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,7 @@ +* Add image/svg+xml as a default mime type. + + *DHH* + ## Rails 5.0.0.beta2 (February 01, 2016) ## * Add `-g` and `-c` (short for _grep_ and _controller_ respectively) options diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index 87715205d9..8356d1a238 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -14,6 +14,7 @@ Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg) Mime::Type.register "image/gif", :gif, [], %w(gif) Mime::Type.register "image/bmp", :bmp, [], %w(bmp) Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff) +Mime::Type.register "image/svg+xml", :svg Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe) -- cgit v1.2.3 From 5d9e59131312f83f925e2ce59865bcb537976422 Mon Sep 17 00:00:00 2001 From: thedarkone Date: Sun, 7 Feb 2016 19:02:37 +0100 Subject: Fix a nonsensical ShareLock test. --- activesupport/test/share_lock_test.rb | 43 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb index 5fceebf023..143e65aa0c 100644 --- a/activesupport/test/share_lock_test.rb +++ b/activesupport/test/share_lock_test.rb @@ -341,38 +341,37 @@ class ShareLockTest < ActiveSupport::TestCase threads.each(&:kill) if threads end - def test_manual_recursive_yield + def test_manual_recursive_yield_cannot_expand_outer_compatible ready = Concurrent::CyclicBarrier.new(2) - done = Concurrent::CyclicBarrier.new(2) do_compatible_nesting = Concurrent::CountDownLatch.new + in_compatible_nesting = Concurrent::CountDownLatch.new - threads = [ - Thread.new do - @lock.sharing do - ready.wait - @lock.exclusive(purpose: :x) {} - done.wait - end - end, + incompatible_thread = Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive(purpose: :x) {} + end + end - Thread.new do - @lock.sharing do - ready.wait - @lock.yield_shares(compatible: [:y]) do - do_compatible_nesting.wait - @lock.sharing do - @lock.yield_shares(compatible: [:x, :y]) do - done.wait - end + yield_shares_thread = Thread.new do + @lock.sharing do + ready.wait + @lock.yield_shares(compatible: [:y]) do + do_compatible_nesting.wait + @lock.sharing do + @lock.yield_shares(compatible: [:x, :y]) do + in_compatible_nesting.wait end end end end - ] + end - assert_threads_stuck threads + assert_threads_stuck incompatible_thread do_compatible_nesting.count_down - assert_threads_not_stuck threads + assert_threads_stuck incompatible_thread + in_compatible_nesting.count_down + assert_threads_not_stuck [yield_shares_thread, incompatible_thread] end def test_manual_recursive_yield_restores_previous_compatible -- cgit v1.2.3 From fe7d77cc01ae652a15b2c1896f677a56133bc0f1 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 8 Feb 2016 05:13:05 +1030 Subject: Test the happy path for recursive yields too --- activesupport/test/share_lock_test.rb | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb index 143e65aa0c..acefa185a8 100644 --- a/activesupport/test/share_lock_test.rb +++ b/activesupport/test/share_lock_test.rb @@ -341,6 +341,41 @@ class ShareLockTest < ActiveSupport::TestCase threads.each(&:kill) if threads end + def test_manual_recursive_yield + ready = Concurrent::CyclicBarrier.new(2) + done = Concurrent::CyclicBarrier.new(2) + do_nesting = Concurrent::CountDownLatch.new + + threads = [ + Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive(purpose: :x) {} + done.wait + end + end, + + Thread.new do + @lock.sharing do + @lock.yield_shares(compatible: [:x]) do + @lock.sharing do + ready.wait + do_nesting.wait + @lock.yield_shares(compatible: [:x, :y]) do + done.wait + end + end + end + end + end + ] + + assert_threads_stuck threads + do_nesting.count_down + + assert_threads_not_stuck threads + end + def test_manual_recursive_yield_cannot_expand_outer_compatible ready = Concurrent::CyclicBarrier.new(2) do_compatible_nesting = Concurrent::CountDownLatch.new -- cgit v1.2.3 From 11579b8306dd6303b306ee10c8bc9f7a4a6adea3 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 8 Feb 2016 05:22:56 +1030 Subject: Manual yield doesn't block new shares --- activesupport/lib/active_support/concurrency/share_lock.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb index ed3345d4dc..54244317e4 100644 --- a/activesupport/lib/active_support/concurrency/share_lock.rb +++ b/activesupport/lib/active_support/concurrency/share_lock.rb @@ -45,7 +45,7 @@ module ActiveSupport if busy_for_exclusive?(purpose) return false if no_wait - yield_shares(purpose: purpose, compatible: compatible) do + yield_shares(purpose: purpose, compatible: compatible, block_share: true) do @cv.wait_while { busy_for_exclusive?(purpose) } end end @@ -68,7 +68,7 @@ module ActiveSupport @exclusive_thread = nil if eligible_waiters?(compatible) - yield_shares(compatible: compatible) do + yield_shares(compatible: compatible, block_share: true) do @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) } end end @@ -77,7 +77,7 @@ module ActiveSupport end end - def start_sharing(purpose: :share) + def start_sharing synchronize do if @sharing[Thread.current] > 0 || @exclusive_thread == Thread.current # We already hold a lock; nothing to wait for @@ -88,7 +88,7 @@ module ActiveSupport else # This is an initial / outermost share call: any outstanding # requests for an exclusive lock get to go first - @cv.wait_while { busy_for_sharing?(purpose) } + @cv.wait_while { busy_for_sharing?(false) } end @sharing[Thread.current] += 1 end @@ -134,7 +134,7 @@ module ActiveSupport # Temporarily give up all held Share locks while executing the # supplied block, allowing any +compatible+ exclusive lock request # to proceed. - def yield_shares(purpose: nil, compatible: []) + def yield_shares(purpose: nil, compatible: [], block_share: false) loose_shares = previous_wait = nil synchronize do if loose_shares = @sharing.delete(Thread.current) @@ -142,6 +142,7 @@ module ActiveSupport purpose = nil unless purpose == previous_wait[0] compatible &= previous_wait[1] end + compatible |= [false] unless block_share @waiting[Thread.current] = [purpose, compatible] @cv.broadcast -- cgit v1.2.3 From bf25ab9523e628c83ca4bd26a4b4d1d1ca84f723 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 8 Feb 2016 04:19:34 +0900 Subject: `schema_type` returns symbol rather than string A return value of `schema_type` is used by: 1. primary key type: using as `symbol.inspect` 2. normal column type: using as `symbol.to_s` It is better to return symbol. --- .../lib/active_record/connection_adapters/abstract/schema_dumper.rb | 6 +++--- .../lib/active_record/connection_adapters/mysql/schema_dumper.rb | 4 ++-- .../active_record/connection_adapters/postgresql/schema_dumper.rb | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) 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 a95109fdae..b1b6044e72 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -14,7 +14,7 @@ module ActiveRecord def column_spec_for_primary_key(column) return if column.type == :integer - spec = { id: column.type.inspect } + spec = { id: schema_type(column).inspect } spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type].include?(key) }) end @@ -24,7 +24,7 @@ module ActiveRecord def prepare_column_options(column) spec = {} spec[:name] = column.name.inspect - spec[:type] = schema_type(column) + spec[:type] = schema_type(column).to_s spec[:null] = 'false' unless column.null if limit = schema_limit(column) @@ -57,7 +57,7 @@ module ActiveRecord private def schema_type(column) - column.type.to_s + column.type end def schema_limit(column) 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 9dee3172f4..ccf5b6cadc 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -12,7 +12,7 @@ module ActiveRecord spec[:unsigned] = 'true' if column.unsigned? return if spec.empty? else - spec[:id] = column.type.inspect + spec[:id] = schema_type(column).inspect spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) }) end spec @@ -32,7 +32,7 @@ module ActiveRecord def schema_type(column) if column.sql_type == 'tinyblob' - 'blob' + :blob else super 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 cc7721ddd8..b82bdb8b0c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -11,7 +11,7 @@ module ActiveRecord spec[:id] = ':uuid' spec[:default] = schema_default(column) || 'nil' else - spec[:id] = column.type.inspect + spec[:id] = schema_type(column).inspect spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) }) end spec @@ -35,9 +35,9 @@ module ActiveRecord return super unless column.serial? if column.bigint? - 'bigserial' + :bigserial else - 'serial' + :serial end end -- cgit v1.2.3 From 5015215f9d77699868fa8984d592686747d296bd Mon Sep 17 00:00:00 2001 From: Larry Kyrala Date: Sat, 6 Feb 2016 16:41:16 -0500 Subject: doc changes for rails/rails#23431 modified: guides/source/asset_pipeline.md * description of asset combination from apps and gems, e.g. jquery-rails * after @vipulnsward's related change rails/rails#23479 correction: --skip-sprockets will prevent all of these gems, not just sass-rails and uglifier modified: guides/source/working_with_javascript_in_rails.md * noted that rails.js requires the asset pipeline [ci skip] --- guides/source/asset_pipeline.md | 5 ++++- guides/source/working_with_javascript_in_rails.md | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 439f2bef3a..f28e3ad81c 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -21,6 +21,9 @@ What is the Asset Pipeline? The asset pipeline provides a framework to concatenate and minify or compress JavaScript and CSS assets. It also adds the ability to write these assets in other languages and pre-processors such as CoffeeScript, Sass and ERB. +It allows assets in your application to be automatically combined with assets +from other gems. For example, jquery-rails includes a copy of jquery.js +and enables AJAX features in Rails. The asset pipeline is technically no longer a core feature of Rails 4, it has been extracted out of the framework into the @@ -45,7 +48,7 @@ gem 'coffee-rails' ``` Using the `--skip-sprockets` option will prevent Rails 4 from adding -`sass-rails` and `uglifier` to your Gemfile, so if you later want to enable +them to your Gemfile, so if you later want to enable the asset pipeline you will have to add those gems to your Gemfile. Also, creating an application with the `--skip-sprockets` option will generate a slightly different `config/application.rb` file, with a require statement diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index 48fc6bc9c0..8aaa824bc3 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -148,10 +148,10 @@ and Rails has got your back in those cases. Because of Unobtrusive JavaScript, the Rails "Ajax helpers" are actually in two parts: the JavaScript half and the Ruby half. +Unless you have disabled the Asset Pipeline, [rails.js](https://github.com/rails/jquery-ujs/blob/master/src/rails.js) provides the JavaScript half, and the regular Ruby view helpers add appropriate -tags to your DOM. The CoffeeScript in rails.js then listens for these -attributes, and attaches appropriate handlers. +tags to your DOM. ### form_for -- cgit v1.2.3 From ba2aea98079fb50a2c210001deb16ab84fac3b41 Mon Sep 17 00:00:00 2001 From: Scott Bronson Date: Sun, 7 Feb 2016 14:21:40 -0800 Subject: revert dev:cache to rake task, fixes #23410 --- railties/lib/rails/commands.rb | 1 - railties/lib/rails/commands/dev_cache.rb | 21 ------------------ railties/lib/rails/tasks.rb | 1 + railties/lib/rails/tasks/dev.rake | 14 ++++++++++++ railties/test/application/rake/dev_test.rb | 34 ++++++++++++++++++++++++++++++ railties/test/commands/dev_cache_test.rb | 32 ---------------------------- 6 files changed, 49 insertions(+), 54 deletions(-) delete mode 100644 railties/lib/rails/commands/dev_cache.rb create mode 100644 railties/lib/rails/tasks/dev.rake create mode 100644 railties/test/application/rake/dev_test.rb delete mode 100644 railties/test/commands/dev_cache_test.rb diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 7627fcf5a0..fa47c52b96 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -14,6 +14,5 @@ command = ARGV.shift command = aliases[command] || command require 'rails/command' -require 'rails/commands/dev_cache' Rails::Command.run(command, ARGV) diff --git a/railties/lib/rails/commands/dev_cache.rb b/railties/lib/rails/commands/dev_cache.rb deleted file mode 100644 index ec96e8f630..0000000000 --- a/railties/lib/rails/commands/dev_cache.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'rails/command' - -module Rails - module Commands - # This is a wrapper around the Rails dev:cache command - class DevCache < Command - set_banner :dev_cache, 'Toggle development mode caching on/off' - def dev_cache - if File.exist? 'tmp/caching-dev.txt' - File.delete 'tmp/caching-dev.txt' - puts 'Development mode is no longer being cached.' - else - FileUtils.touch 'tmp/caching-dev.txt' - puts 'Development mode is now being cached.' - end - - FileUtils.touch 'tmp/restart.txt' - end - end - end -end diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index d60eaf6f4f..d3e33584d7 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -3,6 +3,7 @@ require 'rake' # Load Rails Rakefile extensions %w( annotations + dev framework initializers log diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake new file mode 100644 index 0000000000..4593100465 --- /dev/null +++ b/railties/lib/rails/tasks/dev.rake @@ -0,0 +1,14 @@ +namespace :dev do + desc 'Toggle development mode caching on/off' + task :cache do + if File.exist? 'tmp/caching-dev.txt' + File.delete 'tmp/caching-dev.txt' + puts 'Development mode is no longer being cached.' + else + FileUtils.touch 'tmp/caching-dev.txt' + puts 'Development mode is now being cached.' + end + + FileUtils.touch 'tmp/restart.txt' + end +end diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb new file mode 100644 index 0000000000..43d7a5e156 --- /dev/null +++ b/railties/test/application/rake/dev_test.rb @@ -0,0 +1,34 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + module RakeTests + class RakeDevTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test 'dev:cache creates file and outputs message' do + Dir.chdir(app_path) do + output = `rake dev:cache` + assert File.exist?('tmp/caching-dev.txt') + assert_match(/Development mode is now being cached/, output) + end + end + + test 'dev:cache deletes file and outputs message' do + Dir.chdir(app_path) do + `rails dev:cache` # Create caching file. + output = `rails dev:cache` # Delete caching file. + assert_not File.exist?('tmp/caching-dev.txt') + assert_match(/Development mode is no longer being cached/, output) + end + end + end + end +end diff --git a/railties/test/commands/dev_cache_test.rb b/railties/test/commands/dev_cache_test.rb deleted file mode 100644 index 1b7a72e7fc..0000000000 --- a/railties/test/commands/dev_cache_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require_relative '../isolation/abstract_unit' - -module CommandsTests - class DevCacheTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - - def setup - build_app - end - - def teardown - teardown_app - end - - test 'dev:cache creates file and outputs message' do - Dir.chdir(app_path) do - output = `rails dev:cache` - assert File.exist?('tmp/caching-dev.txt') - assert_match(%r{Development mode is now being cached}, output) - end - end - - test 'dev:cache deletes file and outputs message' do - Dir.chdir(app_path) do - `rails dev:cache` # Create caching file. - output = `rails dev:cache` # Delete caching file. - assert_not File.exist?('tmp/caching-dev.txt') - assert_match(%r{Development mode is no longer being cached}, output) - end - end - end -end -- cgit v1.2.3 From 0ec48ab0cbc4ebb739101fffcbee98e7347aefda Mon Sep 17 00:00:00 2001 From: Daniel Fox Date: Sun, 7 Feb 2016 17:02:47 -0600 Subject: config examples for ActionCable now use Rails.application.config.action_cable Some existing examples used ActionCable.server.config but for configuring allowed_request_origins that is overridden in development mode. The correct place to set that is Rails.application.config.action_cable which the ActionCable initializer loads from. I thought the other two examples should be changed as well just in case a default value that would override a configured value is introduced for either log_tags or disable_request_forgery_protection in the future. --- actioncable/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actioncable/README.md b/actioncable/README.md index 6e74551483..3316f88a23 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -324,7 +324,7 @@ Rails.application.paths.add "config/cable", with: "somewhere/else/cable.yml" Action Cable will only accept requests from specified origins, which are passed to the server config as an array. The origins can be instances of strings or regular expressions, against which a check for match will be performed. ```ruby -ActionCable.server.config.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/] +Rails.application.config.action_cable.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/] ``` When running in the development environment, this defaults to "http://localhost:3000". @@ -332,7 +332,7 @@ When running in the development environment, this defaults to "http://localhost: To disable and allow requests from any origin: ```ruby -ActionCable.server.config.disable_request_forgery_protection = true +Rails.application.config.action_cable.disable_request_forgery_protection = true ``` ### Consumer Configuration @@ -374,7 +374,7 @@ App.cable = ActionCable.createConsumer() The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp: ```ruby -ActionCable.server.config.log_tags = [ +Rails.application.config.action_cable.log_tags = [ -> request { request.env['bc.account_id'] || "no-account" }, :action_cable, -> request { request.uuid } -- cgit v1.2.3 From f83d57d3e1a74e1297083ae771f7fffe591014ef Mon Sep 17 00:00:00 2001 From: mabras Date: Mon, 8 Feb 2016 03:17:31 +0200 Subject: update turbolinks url [ci skip] --- guides/source/working_with_javascript_in_rails.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index 8aaa824bc3..26ff5da7a3 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -350,7 +350,7 @@ $("<%= escape_javascript(render @user) %>").appendTo("#users"); Turbolinks ---------- -Rails 4 ships with the [Turbolinks gem](https://github.com/rails/turbolinks). +Rails 4 ships with the [Turbolinks gem](https://github.com/turbolinks/turbolinks). This gem uses Ajax to speed up page rendering in most applications. ### How Turbolinks Works @@ -395,7 +395,7 @@ $(document).on "page:change", -> For more details, including other events you can bind to, check out [the Turbolinks -README](https://github.com/rails/turbolinks/blob/master/README.md). +README](https://github.com/turbolinks/turbolinks/blob/master/README.md). Other Resources --------------- -- cgit v1.2.3 From 3546b3f0d406499827e40c18b88a05e624aee6ed Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Mon, 8 Feb 2016 08:49:49 +0900 Subject: remove `faye-websocket` dependency from README [ci skip] `faye-websocket` gem is no longer used from 322dca293b3716ccaa09e7e82046e539b0d2ffda. --- actioncable/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actioncable/README.md b/actioncable/README.md index 6e74551483..a1261f0820 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -436,7 +436,7 @@ messages back and forth over the WebSocket cable connection. This dependency may be alleviated in the future, but for the moment that's what it is. So be sure to have Redis installed and running. -The Ruby side of things is built on top of [faye-websocket](https://github.com/faye/faye-websocket-ruby) and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby). +The Ruby side of things is built on top of [websocket-driver](https://github.com/faye/websocket-driver-ruby), [nio4r](https://github.com/celluloid/nio4r), and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby). ## Deployment -- cgit v1.2.3 From abef3c2b0d5b1153d28f4725b9ada4e2ac4181fc Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Mon, 8 Feb 2016 08:46:38 +0530 Subject: Remove references to Rails 4 from assets guide [ci skip] --- guides/source/asset_pipeline.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index f28e3ad81c..4efffc6605 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -21,11 +21,11 @@ What is the Asset Pipeline? The asset pipeline provides a framework to concatenate and minify or compress JavaScript and CSS assets. It also adds the ability to write these assets in other languages and pre-processors such as CoffeeScript, Sass and ERB. -It allows assets in your application to be automatically combined with assets +It allows assets in your application to be automatically combined with assets from other gems. For example, jquery-rails includes a copy of jquery.js and enables AJAX features in Rails. -The asset pipeline is technically no longer a core feature of Rails 4, it has +The asset pipeline is technically no longer a core feature from Rails 4 onwards -- it has been extracted out of the framework into the [sprockets-rails](https://github.com/rails/sprockets-rails) gem. @@ -38,7 +38,7 @@ passing the `--skip-sprockets` option. rails new appname --skip-sprockets ``` -Rails 4 automatically adds the `sass-rails`, `coffee-rails` and `uglifier` +Rails automatically adds the `sass-rails`, `coffee-rails` and `uglifier` gems to your Gemfile, which are used by Sprockets for asset compression: ```ruby @@ -47,7 +47,7 @@ gem 'uglifier' gem 'coffee-rails' ``` -Using the `--skip-sprockets` option will prevent Rails 4 from adding +Using the `--skip-sprockets` option will prevent Rails from adding them to your Gemfile, so if you later want to enable the asset pipeline you will have to add those gems to your Gemfile. Also, creating an application with the `--skip-sprockets` option will generate @@ -330,7 +330,7 @@ familiar `javascript_include_tag` and `stylesheet_link_tag`: <%= javascript_include_tag "application" %> ``` -If using the turbolinks gem, which is included by default in Rails 4, then +If using the turbolinks gem, which is included by default in Rails, then include the 'data-turbolinks-track' option which causes turbolinks to check if an asset has been updated and if so loads it into the page: @@ -446,7 +446,7 @@ makes fewer requests. Compression also reduces file size, enabling the browser to download them faster. -For example, a new Rails 4 application includes a default +For example, a new Rails application includes a default `app/assets/javascripts/application.js` file containing the following lines: ```js @@ -487,7 +487,7 @@ which contains these lines: */ ``` -Rails 4 creates both `app/assets/javascripts/application.js` and +Rails creates both `app/assets/javascripts/application.js` and `app/assets/stylesheets/application.css` regardless of whether the --skip-sprockets option is used when creating a new rails application. This is so you can easily add asset pipelining later if you like. @@ -1111,7 +1111,7 @@ supported runtime in order to use `uglifier`. If you are using Mac OS X or Windows you have a JavaScript runtime installed in your operating system. NOTE: The `config.assets.compress` initialization option is no longer used in -Rails 4 to enable either CSS or JavaScript compression. Setting it will have no +Rails to enable either CSS or JavaScript compression. Setting it will have no effect on the application. Instead, setting `config.assets.css_compressor` and `config.assets.js_compressor` will control compression of CSS and JavaScript assets. @@ -1293,7 +1293,7 @@ config.assets.digest = true # config.assets.precompile += %w( search.js ) ``` -Rails 4 no longer sets default config values for Sprockets in `test.rb`, so +Rails 4 and above no longer set default config values for Sprockets in `test.rb`, so `test.rb` now requires Sprockets configuration. The old defaults in the test environment are: `config.assets.compile = true`, `config.assets.compress = false`, `config.assets.debug = false` and `config.assets.digest = false`. -- cgit v1.2.3 From 40fd56052b03919b7ba5f07415e215a2b47df95f Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 6 Feb 2016 14:31:35 +0900 Subject: Add numeric type in the doc [ci skip] Follow up to #23508. --- .../active_record/connection_adapters/abstract/schema_statements.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 983c4340c6..f0f855963a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -459,7 +459,7 @@ module ActiveRecord # The +type+ parameter is normally one of the migrations native types, # which is one of the following: # :primary_key, :string, :text, - # :integer, :bigint, :float, :decimal, + # :integer, :bigint, :float, :decimal, :numeric, # :datetime, :time, :date, # :binary, :boolean. # @@ -477,9 +477,9 @@ module ActiveRecord # Allows or disallows +NULL+ values in the column. This option could # have been named :null_allowed. # * :precision - - # Specifies the precision for a :decimal column. + # Specifies the precision for the :decimal and :numeric columns. # * :scale - - # Specifies the scale for a :decimal column. + # Specifies the scale for the :decimal and :numeric columns. # # Note: The precision is the total number of significant digits # and the scale is the number of digits that can be stored following -- cgit v1.2.3 From ec82c13dd47e386de8928f3cdd24eef33b8f835b Mon Sep 17 00:00:00 2001 From: Karim El-Husseiny Date: Mon, 8 Feb 2016 17:04:31 +0200 Subject: Update rails-html-sanitizer version to v1.0.3 rails-html-sanitizer 1.0.2 is vulnerable: https://groups.google.com/d/msg/rubyonrails-security/uh--W4TDwmI/m_CVZtdbFQAJ --- actionpack/actionpack.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 28d8bc3091..f2d08dc6ca 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| s.add_dependency 'rack', '~> 2.x' s.add_dependency 'rack-test', '~> 0.6.3' - s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.2' + s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.3' s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5' s.add_dependency 'actionview', version -- cgit v1.2.3 From 9b5ae716db601a8a34a4227c9c4ef2ec9cf90f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 8 Feb 2016 14:09:29 -0200 Subject: Revert "Merge pull request #23562 from Azzurrio/patch-1" This reverts commit 8c3cca5e113213958469b1cec8aa9a664535251a, reversing changes made to 9dcf67c4da35b165301865d9721da1d552f7e03f. Reason: https://github.com/rails/rails/pull/23562#issuecomment-181442569 --- actionpack/actionpack.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index f2d08dc6ca..28d8bc3091 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| s.add_dependency 'rack', '~> 2.x' s.add_dependency 'rack-test', '~> 0.6.3' - s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.3' + s.add_dependency 'rails-html-sanitizer', '~> 1.0', '>= 1.0.2' s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.5' s.add_dependency 'actionview', version -- cgit v1.2.3 From 581c2a6547e15e63c5039010ee581e2a593d6a83 Mon Sep 17 00:00:00 2001 From: Prayag Verma Date: Mon, 8 Feb 2016 22:40:48 +0530 Subject: Fix a typo Replace `a` with `an` --- guides/source/getting_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 2cbc591629..8eb3b6190f 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -653,7 +653,7 @@ run this command in your terminal: $ bin/rails generate model Article title:string text:text ``` -With that command we told Rails that we want a `Article` model, together +With that command we told Rails that we want an `Article` model, together with a _title_ attribute of type string, and a _text_ attribute of type text. Those attributes are automatically added to the `articles` table in the database and mapped to the `Article` model. -- cgit v1.2.3 From c2b9a5dbef76ae3f8363964d1c90e3da6d292b1d Mon Sep 17 00:00:00 2001 From: Abhishek Jain Date: Tue, 9 Feb 2016 04:16:28 +0530 Subject: [ci skip] Fix grammar --- guides/source/security.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/security.md b/guides/source/security.md index 96b9f4bcce..b30bed3767 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -787,7 +787,7 @@ The following is an excerpt from the [Js.Yamanner@m](http://www.symantec.com/sec var IDList = ''; var CRumb = ''; function makeRequest(url, Func, Method,Param) { ... ``` -The worms exploits a hole in Yahoo's HTML/JavaScript filter, which usually filters all target and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application. +The worms exploit a hole in Yahoo's HTML/JavaScript filter, which usually filters all targets and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application. Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on [Rosario Valotta's paper](http://www.xssed.com/news/37/Nduja_Connection_A_cross_webmail_worm_XWW/). Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with. -- cgit v1.2.3 From 02c3867882d6d23b10df262a6db5f937ca69fb53 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 8 Feb 2016 15:40:10 -0800 Subject: speed up string xor operation and reduce object allocations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` [aaron@TC rails (master)]$ cat xor.rb a = "\x14b\"\xB4P8\x05\x8D\xC74\xC3\xEC}\xFDf\x8E!h\xCF^\xBF\xA5%\xC6\xF0\xA9\xF9x\x04\xFA\xF1\x82" b = "O.\xF7\x01\xA9D\xA3\xE1D\x7FU\x85\xFC\x8Ak\e\x04\x8A\x97\x91\xD01\x02\xA4G\x1EIf:Y\x0F@" def xor_byte_strings(s1, s2) s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*') end def xor_byte_strings2(s1, s2) s2_bytes = s2.bytes s1.bytes.map.with_index { |c1, i| c1 ^ s2_bytes[i] }.pack('c*') end require 'benchmark/ips' require 'allocation_tracer' Benchmark.ips do |x| x.report 'xor_byte_strings' do xor_byte_strings a, b end x.report 'xor_byte_strings2' do xor_byte_strings2 a, b end end ObjectSpace::AllocationTracer.setup(%i{type}) result = ObjectSpace::AllocationTracer.trace do xor_byte_strings a, b end p :xor_byte_strings => result ObjectSpace::AllocationTracer.clear result = ObjectSpace::AllocationTracer.trace do xor_byte_strings2 a, b end p :xor_byte_strings2 => result [aaron@TC rails (master)]$ ruby -I~/git/allocation_tracer/lib xor.rb Calculating ------------------------------------- xor_byte_strings 10.087k i/100ms xor_byte_strings2 11.339k i/100ms ------------------------------------------------- xor_byte_strings 108.386k (± 5.8%) i/s - 544.698k xor_byte_strings2 122.239k (± 3.0%) i/s - 612.306k {:xor_byte_strings=>{[:T_ARRAY]=>[38, 0, 0, 0, 0, 0], [:T_STRING]=>[2, 0, 0, 0, 0, 0]}} {:xor_byte_strings2=>{[:T_ARRAY]=>[3, 0, 0, 0, 0, 0], [:T_DATA]=>[1, 0, 0, 0, 0, 0], [:T_IMEMO]=>[2, 0, 0, 0, 0, 0], [:T_STRING]=>[2, 0, 0, 0, 0, 0]}} ``` --- actionpack/lib/action_controller/metal/request_forgery_protection.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 91b3403ad5..6586985ff5 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -378,7 +378,8 @@ module ActionController #:nodoc: end def xor_byte_strings(s1, s2) - s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*') + s2_bytes = s2.bytes + s1.bytes.map.with_index { |c1, i| c1 ^ s2_bytes[i] }.pack('c*') end # The form's authenticity parameter. Override to provide your own. -- cgit v1.2.3 From 783858c8e150eb0f98d7e5d893e492ee08998662 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 8 Feb 2016 15:48:59 -0800 Subject: drop array allocations on `html_safe` For better or worse, anonymous `*` args will allocate arrays. Ideally, the interpreter would optimize away this allocation. However, given the number of times we call `html_safe` it seems worth the shedding idealism and going for performance. This line was the top allocation spot for a scaffold (and presumably worse on real applications). --- activesupport/lib/active_support/core_ext/string/output_safety.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 6251f34daf..43b9fd4bf7 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -171,7 +171,7 @@ module ActiveSupport #:nodoc: original_concat(value) end - def initialize(*) + def initialize(str = '') @html_safe = true super end -- cgit v1.2.3 From 3f184cab489618aed0b2969ad7893ee3bdd7a843 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Tue, 9 Feb 2016 09:47:47 +0900 Subject: Fix typo [ci skip] --- actioncable/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actioncable/README.md b/actioncable/README.md index 7f7a830e6c..5029f583cb 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -17,7 +17,7 @@ The client of a WebSocket connection is called the consumer. Each consumer can in turn subscribe to multiple cable channels. Each channel encapsulates a logical unit of work, similar to what a controller does in a regular MVC setup. For example, -you could have a `ChatChannel` and a `AppearancesChannel`, and a consumer could be subscribed to either +you could have a `ChatChannel` and an `AppearancesChannel`, and a consumer could be subscribed to either or to both of these channels. At the very least, a consumer should be subscribed to one channel. When the consumer is subscribed to a channel, they act as a subscriber. The connection between -- cgit v1.2.3 From 9e70daa9417d9eef875dae0e324aeea8884977ed Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Tue, 9 Feb 2016 14:32:16 +0900 Subject: remove description of `render :nothing` from guide [ci skip] `:nothing` option was deprecated in 44781b6e9790d90b4f8b9a41d2b2c114b1a582ee --- guides/source/layouts_and_rendering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index d55e1007ee..6946eb81eb 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -700,7 +700,7 @@ This would detect that there are no books with the specified ID, populate the `@ ### Using `head` To Build Header-Only Responses -The `head` method can be used to send responses with only headers to the browser. It provides a more obvious alternative to calling `render :nothing`. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing a HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header: +The `head` method can be used to send responses with only headers to the browser. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing a HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header: ```ruby head :bad_request -- cgit v1.2.3 From 836f6f17475279748469348f990f998f534fd439 Mon Sep 17 00:00:00 2001 From: Dave Gynn Date: Tue, 9 Feb 2016 14:47:55 -0500 Subject: Update documentation regarding initializers [ci skip] - Remove ActionController `logger` and `initialize_framework_caches` which were merged into `set_configs` in fbc9d0f4 - Rename ActiveRecord `set_reloader_hooks` changed in 283a0876 - Add missing initializers for ActionController and ActiveRecord --- guides/source/configuring.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/guides/source/configuring.md b/guides/source/configuring.md index d9c345fb71..a5fb396f15 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -998,7 +998,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `initialize_cache` If `Rails.cache` isn't set yet, initializes the cache by referencing the value in `config.cache_store` and stores the outcome as `Rails.cache`. If this object responds to the `middleware` method, its middleware is inserted before `Rack::Runtime` in the middleware stack. -* `set_clear_dependencies_hook` Provides a hook for `active_record.set_dispatch_hooks` to use, which will run before this initializer. This initializer - which runs only if `cache_classes` is set to `false` - uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. +* `set_clear_dependencies_hook` This initializer - which runs only if `cache_classes` is set to `false` - uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. * `initialize_dependency_mechanism` If `config.cache_classes` is true, configures `ActiveSupport::Dependencies.mechanism` to `require` dependencies rather than `load` them. @@ -1012,13 +1012,17 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `active_support.initialize_beginning_of_week` Sets the default beginning of week for the application based on `config.beginning_of_week` setting, which defaults to `:monday`. +* `active_support.set_configs` Sets up Active Support by using the settings in `config.active_support` by `send`'ing the method names as setters to `ActiveSupport` and passing the values through. + * `action_dispatch.configure` Configures the `ActionDispatch::Http::URL.tld_length` to be set to the value of `config.action_dispatch.tld_length`. * `action_view.set_configs` Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through. -* `action_controller.logger` Sets `ActionController::Base.logger` - if it's not already set - to `Rails.logger`. +* `action_controller.assets_config` Initializes the `config.actions_controller.assets_dir` to the app's public directory if not explicitly configured + +* `action_controller.set_helpers_path` Sets Action Controller's helpers_path to the application's helpers_path -* `action_controller.initialize_framework_caches` Sets `ActionController::Base.cache_store` - if it's not already set - to `Rails.cache`. +* `action_controller.parameters_config` Configures strong parameters options for `ActionController::Parameters` * `action_controller.set_configs` Sets up Action Controller by using the settings in `config.action_controller` by `send`'ing the method names as setters to `ActionController::Base` and passing the values through. @@ -1028,13 +1032,21 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `active_record.logger` Sets `ActiveRecord::Base.logger` - if it's not already set - to `Rails.logger`. +* `active_record.migration_error` Configures middleware to check for pending migrations + +* `active_record.check_schema_cache_dump` Loads the schema cache dump if configured and available + +* `active_record.warn_on_records_fetched_greater_than` Enables warnings when queries return large numbers of records + * `active_record.set_configs` Sets up Active Record by using the settings in `config.active_record` by `send`'ing the method names as setters to `ActiveRecord::Base` and passing the values through. * `active_record.initialize_database` Loads the database configuration (by default) from `config/database.yml` and establishes a connection for the current environment. * `active_record.log_runtime` Includes `ActiveRecord::Railties::ControllerRuntime` which is responsible for reporting the time taken by Active Record calls for the request back to the logger. -* `active_record.set_dispatch_hooks` Resets all reloadable connections to the database if `config.cache_classes` is set to `false`. +* `active_record.set_reloader_hooks` Resets all reloadable connections to the database if `config.cache_classes` is set to `false`. + +* `active_record.add_watchable_files` Adds `schema.rb` and `structure.sql` files to watchable files * `active_job.logger` Sets `ActiveJob::Base.logger` - if it's not already set - to `Rails.logger`. -- cgit v1.2.3 From d5c6babd9e139deed5d43c35a41b2af5d73376ba Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 9 Feb 2016 12:12:05 -0800 Subject: AC::Request#format always returns a value, so we do not need to try --- actionpack/lib/action_controller/metal/instrumentation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 3dbf34eb2a..f66e3efffe 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -19,7 +19,7 @@ module ActionController :controller => self.class.name, :action => self.action_name, :params => request.filtered_parameters, - :format => request.format.try(:ref), + :format => request.format.ref, :method => request.request_method, :path => (request.fullpath rescue "unknown") } -- cgit v1.2.3 From b2a9047558e20e35681eae8d47ad7a95c3e1d19e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 9 Feb 2016 13:34:44 -0800 Subject: Request#fullpath should not raise an exception, so remove the rescue --- actionpack/lib/action_controller/metal/instrumentation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index f66e3efffe..bf74b39ac4 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -21,7 +21,7 @@ module ActionController :params => request.filtered_parameters, :format => request.format.ref, :method => request.request_method, - :path => (request.fullpath rescue "unknown") + :path => request.fullpath } ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup) -- cgit v1.2.3 From b5eb2423b6e431ba53e3836d58449e7e810096b4 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 9 Feb 2016 13:48:03 -0800 Subject: `log_process_action` will return an array, so use `empty?` We don't need to use active support in this case because we know the type that will be returned. --- actionpack/lib/action_controller/log_subscriber.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 450a04779f..a0917b4fdb 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -25,7 +25,7 @@ module ActionController status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name) end message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" - message << " (#{additions.join(" | ".freeze)})" unless additions.blank? + message << " (#{additions.join(" | ".freeze)})" unless additions.empty? message << "\n\n" if defined?(Rails.env) && Rails.env.development? message -- cgit v1.2.3 From de6ad5665d2679944a9ee9407826ba88395a1003 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Wed, 10 Feb 2016 00:18:25 +0100 Subject: enables the evented monitor in new applications --- railties/CHANGELOG.md | 5 +++++ railties/lib/rails/generators/app_base.rb | 4 ++++ .../rails/generators/rails/app/templates/Gemfile | 3 +++ .../config/environments/development.rb.tt | 2 +- railties/test/generators/app_generator_test.rb | 22 ++++++++++++++++++++++ 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 8f4dc736a8..65fedbd659 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,8 @@ +* New applications are generated with the evented file system monitor enabled + on Linux and Mac OS X. + + *Xavier Noria* + * Add dummy files for apple-touch-icon.png and apple-touch-icon.png. GH#23427 *Alexey Zabelin* diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 9b1e16a7a3..8f8c2ec9e1 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -390,6 +390,10 @@ module Rails !options[:skip_spring] && !options.dev? && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin") end + def os_supports_listen_out_of_the_box? + RbConfig::CONFIG['host_os'] =~ /darwin|linux/ + end + def run_bundle bundle_command('install') if bundle_install? end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 3825dc4e38..c816c9e7e0 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -38,6 +38,9 @@ group :development do gem 'web-console', '~> 3.0' <%- end -%> <%- end -%> +<% if os_supports_listen_out_of_the_box? -%> + gem 'listen', '~> 3.0.5' +<% end -%> <% if spring_install? -%> # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index fd41372d9c..3451ade158 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -58,5 +58,5 @@ Rails.application.configure do # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. - # config.file_watcher = ActiveSupport::EventedFileUpdateChecker + <%= '# ' unless os_supports_listen_out_of_the_box? %>config.file_watcher = ActiveSupport::EventedFileUpdateChecker end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index f483a0bcbd..f4d0b15546 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -479,6 +479,28 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_inclusion_of_listen_related_gems + run_generator + if RbConfig::CONFIG['host_os'] =~ /darwin|linux/ + assert_gem 'listen' + else + assert_file 'Gemfile' do |content| + assert_no_match(/listen/, content) + end + end + end + + def test_evented_file_update_checker_config + run_generator + assert_file 'config/environments/development.rb' do |content| + if RbConfig::CONFIG['host_os'] =~ /darwin|linux/ + assert_match(/^\s*config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) + else + assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) + end + end + end + def test_template_from_dir_pwd FileUtils.cd(Rails.root) assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"])) -- cgit v1.2.3 From 00a5eb6aeb1277472da5fe9e7dd02d003c766b13 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Wed, 10 Feb 2016 00:22:42 +0100 Subject: include spring-watcher-listen in the Gemfile of new applications --- railties/CHANGELOG.md | 5 +++++ railties/lib/rails/generators/rails/app/templates/Gemfile | 3 +++ railties/test/generators/app_generator_test.rb | 1 + 3 files changed, 9 insertions(+) diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 65fedbd659..2506baac16 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,8 @@ +* The Gemfiles of new applications include spring-watcher-listen on Linux and + Mac OS X (unless --skip-spring). + + *Xavier Noria* + * New applications are generated with the evented file system monitor enabled on Linux and Mac OS X. diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index c816c9e7e0..c3fad31f23 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -44,6 +44,9 @@ group :development do <% if spring_install? -%> # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' +<% if os_supports_listen_out_of_the_box? -%> + gem 'spring-watcher-listen', '~> 2.0.0' +<% end -%> <% end -%> end <% end -%> diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index f4d0b15546..be05e779ea 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -483,6 +483,7 @@ class AppGeneratorTest < Rails::Generators::TestCase run_generator if RbConfig::CONFIG['host_os'] =~ /darwin|linux/ assert_gem 'listen' + assert_gem 'spring-watcher-listen' else assert_file 'Gemfile' do |content| assert_no_match(/listen/, content) -- cgit v1.2.3 From bd6971812434e6f016977dc5c39fbc6450433471 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Wed, 10 Feb 2016 01:31:00 +0100 Subject: prevent apps in the railties test suite from running the evented monitor --- railties/test/isolation/abstract_unit.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index dddf8bd257..e7a261fa1f 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -154,6 +154,8 @@ module TestHelpers config.action_controller.allow_forgery_protection = false config.log_level = :info RUBY + + remove_from_env_config('development', 'config.file_watcher.*') end def teardown_app @@ -272,10 +274,17 @@ module TestHelpers end def remove_from_config(str) - file = "#{app_path}/config/application.rb" + remove_from_file("#{app_path}/config/application.rb", str) + end + + def remove_from_env_config(env, str) + remove_from_file("#{app_path}/config/environments/#{env}.rb", str) + end + + def remove_from_file(file, str) contents = File.read(file) - contents.sub!(/#{str}/, "") - File.open(file, "w+") { |f| f.puts contents } + contents.sub!(/#{str}/, '') + File.write(file, contents) end def app_file(path, contents, mode = 'w') -- cgit v1.2.3 From 767f168772aa697192d6c79b6bfaf942ed96b1e2 Mon Sep 17 00:00:00 2001 From: Matt Michnal Date: Thu, 4 Feb 2016 22:34:30 -0700 Subject: Fixed grammatical errors in rails docs [ci skip] Fixed errors in rails migrations docs [ci skip] Fixed errors in rails security docs [ci skip] --- guides/source/active_record_migrations.md | 4 ++-- guides/source/security.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index 83f4b951ee..bd7dbd0f11 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -883,7 +883,7 @@ Changing Existing Migrations ---------------------------- Occasionally you will make a mistake when writing a migration. If you have -already run the migration then you cannot just edit the migration and run the +already run the migration, then you cannot just edit the migration and run the migration again: Rails thinks it has already run the migration and so will do nothing when you run `rails db:migrate`. You must rollback the migration (for example with `bin/rails db:rollback`), edit your migration and then run @@ -933,7 +933,7 @@ There are two ways to dump the schema. This is set in `config/application.rb` by the `config.active_record.schema_format` setting, which may be either `:sql` or `:ruby`. -If `:ruby` is selected then the schema is stored in `db/schema.rb`. If you look +If `:ruby` is selected, then the schema is stored in `db/schema.rb`. If you look at this file you'll find that it looks an awful lot like one very big migration: diff --git a/guides/source/security.md b/guides/source/security.md index b30bed3767..98324141cc 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -23,7 +23,7 @@ Web application frameworks are made to help developers build web applications. S In general there is no such thing as plug-n-play security. Security depends on the people using the framework, and sometimes on the development method. And it depends on all layers of a web application environment: The back-end storage, the web server and the web application itself (and possibly other layers or applications). -The Gartner Group however estimates that 75% of attacks are at the web application layer, and found out "that out of 300 audited sites, 97% are vulnerable to attack". This is because web applications are relatively easy to attack, as they are simple to understand and manipulate, even by the lay person. +The Gartner Group, however, estimates that 75% of attacks are at the web application layer, and found out "that out of 300 audited sites, 97% are vulnerable to attack". This is because web applications are relatively easy to attack, as they are simple to understand and manipulate, even by the lay person. The threats against web applications include user account hijacking, bypass of access control, reading or modifying sensitive data, or presenting fraudulent content. Or an attacker might be able to install a Trojan horse program or unsolicited e-mail sending software, aim at financial enrichment or cause brand name damage by modifying company resources. In order to prevent attacks, minimize their impact and remove points of attack, first of all, you have to fully understand the attack methods in order to find the correct countermeasures. That is what this guide aims at. @@ -62,7 +62,7 @@ Many web applications have an authentication system: a user provides a user name Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures: -* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file: +* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN, it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file: ```ruby config.force_ssl = true -- cgit v1.2.3 From 344ddcbc3e57e49a7c476fa383a182564fdad198 Mon Sep 17 00:00:00 2001 From: John Cole Date: Tue, 9 Feb 2016 23:07:38 -0500 Subject: Fix typo --- guides/source/association_basics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 3386791cdb..09ab64837a 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -713,7 +713,7 @@ By default, Active Record doesn't know about the connection between these associ ```ruby a = Author.first -b = c.books.first +b = a.books.first a.first_name == b.author.first_name # => true a.first_name = 'Manny' a.first_name == b.author.first_name # => false @@ -735,7 +735,7 @@ With these changes, Active Record will only load one copy of the author object, ```ruby a = author.first -b = c.books.first +b = a.books.first a.first_name == b.author.first_name # => true a.first_name = 'Manny' a.first_name == b.author.first_name # => true -- cgit v1.2.3 From f2fcd3a000f70cc520b9267e82184ab87fea7f80 Mon Sep 17 00:00:00 2001 From: Mawueli Kofi Adzoe Date: Tue, 9 Feb 2016 22:35:40 -0700 Subject: Fix tiny grammar. --- actioncable/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actioncable/README.md b/actioncable/README.md index 5029f583cb..3ed8a23a29 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -188,7 +188,7 @@ can be reached as remote procedure calls via a subscription's `perform` method. The appearance example was all about exposing server functionality to client-side invocation over the WebSocket connection. But the great thing about WebSockets is that it's a two-way street. So now let's show an example where the server invokes -action on the client. +an action on the client. This is a web notification channel that allows you to trigger client-side web notifications when you broadcast to the right streams: -- cgit v1.2.3