diff options
39 files changed, 252 insertions, 87 deletions
diff --git a/.travis.yml b/.travis.yml index ef85107515..f3314f49af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,11 @@ rvm: matrix: include: # Latest compiled version in http://rubies.travis-ci.org + - rvm: 2.3.0 + env: + - "GEM=ar:mysql2" + addons: + mariadb: 10.0 - rvm: jruby-9.0.5.0 jdk: oraclejdk8 env: diff --git a/actioncable/README.md b/actioncable/README.md index 595830feb0..de67b507a7 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -412,12 +412,12 @@ The above will start a cable server on port 28080. ### In app -If you are using a server that supports the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking), Action Cable can run alongside your Rails application. For example, to listen for WebSocket requests on `/cable`, mount the server at that path: +If you are using a server that supports the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking), Action Cable can run alongside your Rails application. For example, to listen for WebSocket requests on `/websocket`, specify that path to `config.action_cable.mount_path`: ```ruby -# config/routes.rb -Example::Application.routes.draw do - mount ActionCable.server => '/cable' +# config/application.rb +class Application < Rails::Application + config.action_cable.mount_path = '/websocket' end ``` diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb index f654ce0bfa..8b46ac216a 100644 --- a/actioncable/lib/action_cable/channel/streams.rb +++ b/actioncable/lib/action_cable/channel/streams.rb @@ -76,8 +76,11 @@ module ActionCable # Don't send the confirmation until pubsub#subscribe is successful defer_subscription_confirmation! - if handler = callback || block - handler = -> message { handler.(coder.decode(message)) } if coder + if user_handler = callback || block + user_handler = -> message { handler.(coder.decode(message)) } if coder + handler = -> message do + connection.worker_pool.async_invoke(user_handler, :call, message) + end else handler = default_stream_handler(broadcasting, coder: coder) end diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb index d89ab45816..3bcf5f1898 100644 --- a/actioncable/lib/rails/generators/channel/channel_generator.rb +++ b/actioncable/lib/rails/generators/channel/channel_generator.rb @@ -13,6 +13,7 @@ module Rails template "channel.rb", File.join('app/channels', class_path, "#{file_name}_channel.rb") if options[:assets] + template "assets/cable.js", "app/assets/javascripts/cable.js" template "assets/channel.coffee", File.join('app/assets/javascripts/channels', class_path, "#{file_name}.coffee") end diff --git a/actioncable/lib/rails/generators/channel/templates/assets/cable.js b/actioncable/lib/rails/generators/channel/templates/assets/cable.js new file mode 100644 index 0000000000..71ee1e66de --- /dev/null +++ b/actioncable/lib/rails/generators/channel/templates/assets/cable.js @@ -0,0 +1,13 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the rails generate channel command. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb index f51f19eb7d..df129a7c62 100644 --- a/actioncable/test/channel/stream_test.rb +++ b/actioncable/test/channel/stream_test.rb @@ -116,11 +116,22 @@ module ActionCable::StreamTests require 'action_cable/subscription_adapter/inline' + class UserCallbackChannel < ActionCable::Channel::Base + def subscribed + stream_from :channel do + Thread.current[:ran_callback] = true + end + end + end + class StreamEncodingTest < ActionCable::TestCase setup do @server = TestServer.new(subscription_adapter: ActionCable::SubscriptionAdapter::Inline) @server.config.allowed_request_origins = %w( http://rubyonrails.com ) - @server.stubs(:channel_classes).returns(ChatChannel.name => ChatChannel) + @server.stubs(:channel_classes).returns( + ChatChannel.name => ChatChannel, + UserCallbackChannel.name => UserCallbackChannel, + ) end test 'custom encoder' do @@ -134,6 +145,17 @@ module ActionCable::StreamTests end end + test "user supplied callbacks are run through the worker pool" do + run_in_eventmachine do + connection = open_connection + receive(connection, command: 'subscribe', channel: UserCallbackChannel.name, identifiers: { id: 1 }) + + @server.broadcast 'channel', {} + wait_for_async + refute Thread.current[:ran_callback], "User callback was not run through the worker pool" + end + end + private def subscribe_to(connection, identifiers:) receive connection, command: 'subscribe', identifiers: identifiers @@ -151,8 +173,8 @@ module ActionCable::StreamTests end end - def receive(connection, command:, identifiers:) - identifier = JSON.generate(channel: 'ActionCable::StreamTests::ChatChannel', **identifiers) + def receive(connection, command:, identifiers:, channel: 'ActionCable::StreamTests::ChatChannel') + identifier = JSON.generate(channel: channel, **identifiers) connection.dispatch_websocket_message JSON.generate(command: command, identifier: identifier) wait_for_async end diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 3df524580c..4bb7842297 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -144,7 +144,7 @@ module ActionMailer # mail.deliver_now # generates and sends the email now # # The <tt>ActionMailer::MessageDelivery</tt> class is a wrapper around a delegate that will call - # your method to generate the mail. If you want direct access to delegator, or <tt>Mail::Message</tt>, + # your method to generate the mail. If you want direct access to the delegator, or <tt>Mail::Message</tt>, # you can call the <tt>message</tt> method on the <tt>ActionMailer::MessageDelivery</tt> object. # # NotifierMailer.welcome(User.first).message # => a Mail::Message object @@ -163,7 +163,7 @@ module ActionMailer # # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use # multipart templates, where each template is named after the name of the action, followed by the content - # type. Each such detected template will be added as a separate part to the message. + # type. Each such detected template will be added to the message, as a separate part. # # For example, if the following templates exist: # * signup_notification.text.erb @@ -288,7 +288,7 @@ module ActionMailer # end # # Note that the proc is evaluated right at the start of the mail message generation, so if you - # set something in the default using a proc, and then set the same thing inside of your + # set something in the default hash using a proc, and then set the same thing inside of your # mailer method, it will get overwritten by the mailer method. # # It is also possible to set these default options that will be used in all mailers through diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index e0ac6c24b1..370e3a1958 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,7 @@ +* Add extension synonyms `yml` and `yaml` for MIME type `application/x-yaml`. + + *bogdanvlviv* + * Adds support for including ActionController::Cookies in API controllers. Previously, including the module would raise when trying to define a `cookies` helper method. Skip calling #helper_method if it is not @@ -211,14 +215,14 @@ *Derek Prior* -* `ActionController::TestCase` will be moved to its own gem in Rails 5.1 +* `ActionController::TestCase` will be moved to its own gem in Rails 5.1. With the speed improvements made to `ActionDispatch::IntegrationTest` we no longer need to keep two separate code bases for testing controllers. In Rails 5.1 `ActionController::TestCase` will be deprecated and moved into a gem outside of Rails source. - This is a documentation deprecation so that going forward so new tests will use + This is a documentation deprecation so that going forward new tests will use `ActionDispatch::IntegrationTest` instead of `ActionController::TestCase`. *Eileen M. Uchitelle* diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 64672de57e..f9b80dd805 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -756,6 +756,10 @@ module ActionController end end + def non_scalar?(value) + value.is_a?(Array) || value.is_a?(Parameters) + end + EMPTY_ARRAY = [] def hash_filter(params, filter) filter = filter.with_indifferent_access @@ -770,7 +774,7 @@ module ActionController array_of_permitted_scalars?(self[key]) do |val| params[key] = val end - else + elsif non_scalar?(value) # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }. params[key] = each_element(value) do |element| element.permit(*Array.wrap(filter[key])) diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index 66cea88256..8b04174f1f 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -21,7 +21,7 @@ Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe) Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml ) Mime::Type.register "application/rss+xml", :rss Mime::Type.register "application/atom+xml", :atom -Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml ) +Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml ), %w(yml yaml) Mime::Type.register "multipart/form-data", :multipart_form Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index 96048e2868..b75eb0e3bf 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -360,4 +360,13 @@ class ParametersPermitTest < ActiveSupport::TestCase assert @params.include? 'person' assert_not @params.include? :gorilla end + + test "scalar values should be filtered when array or hash is specified" do + params = ActionController::Parameters.new(foo: "bar") + + assert params.permit(:foo).has_key?(:foo) + refute params.permit(foo: []).has_key?(:foo) + refute params.permit(foo: [:bar]).has_key?(:foo) + refute params.permit(foo: :bar).has_key?(:foo) + end end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index fad4de9df5..90047c3c12 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -119,6 +119,9 @@ module ActiveModel extend ActiveSupport::Concern include ActiveModel::AttributeMethods + OPTION_NOT_GIVEN = Object.new # :nodoc: + private_constant :OPTION_NOT_GIVEN + included do attribute_method_suffix '_changed?', '_change', '_will_change!', '_was' attribute_method_suffix '_previously_changed?', '_previous_change' @@ -174,13 +177,10 @@ module ActiveModel end # Handles <tt>*_changed?</tt> for +method_missing+. - def attribute_changed?(attr, options = nil) #:nodoc: - result = changes_include?(attr) - if options - result &&= options[:to] == __send__(attr) if options.key?(:to) - result &&= options[:from] == changed_attributes[attr] if options.key?(:from) - end - result + def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc: + !!changes_include?(attr) && + (to == OPTION_NOT_GIVEN || to == __send__(attr)) && + (from == OPTION_NOT_GIVEN || from == changed_attributes[attr]) end # Handles <tt>*_was</tt> for +method_missing+. @@ -189,7 +189,7 @@ module ActiveModel end # Handles <tt>*_previously_changed?</tt> for +method_missing+. - def attribute_previously_changed?(attr, options = {}) #:nodoc: + def attribute_previously_changed?(attr) #:nodoc: previous_changes_include?(attr) end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 291e5a2513..0e831bfb66 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,9 @@ +* Properly accept all valid JSON primitives in the JSON data type. + + Fixes #24234 + + *Sean Griffin* + * MariaDB 5.3+ supports microsecond datetime precision. *Jeremy Daer* diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 882f1225fc..48437a1c9e 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -64,11 +64,11 @@ module ActiveRecord foreign_key = join_keys.foreign_key value = transform_value(owner[foreign_key]) - scope = scope.where(table.name.to_sym => { key => value }) + scope = scope.where(table.name => { key => value }) if reflection.type polymorphic_type = transform_value(owner.class.base_class.name) - scope = scope.where(table.name.to_sym => { reflection.type => polymorphic_type }) + scope = scope.where(table.name => { reflection.type => polymorphic_type }) end scope diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 2eeefb13d7..860ef17dca 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -146,6 +146,10 @@ module ActiveRecord end end + def quoted_time(value) # :nodoc: + quoted_date(value).sub(/\A2000-01-01 /, '') + end + def prepare_binds_for_database(binds) # :nodoc: binds.map(&:value_for_database) end @@ -166,6 +170,7 @@ module ActiveRecord # BigDecimals need to be put in a non-normalized form and quoted. when BigDecimal then value.to_s('F') when Numeric, ActiveSupport::Duration then value.to_s + when Type::Time::Value then "'#{quoted_time(value)}'" when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" when Class then "'#{value}'" @@ -181,6 +186,7 @@ module ActiveRecord when false then unquoted_false # BigDecimals need to be put in a non-normalized form and quoted. when BigDecimal then value.to_s('F') + when Type::Time::Value then quoted_time(value) when Date, Time then quoted_date(value) when *types_which_need_no_typecasting value 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 1797af4e97..8bd9da4fbc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -77,10 +77,14 @@ module ActiveRecord } end - def version + def version #:nodoc: @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0]) end + def mariadb? # :nodoc: + full_version =~ /mariadb/i + end + # Returns true, since this connection adapter supports migrations. def supports_migrations? true @@ -782,10 +786,6 @@ module ActiveRecord subselect.from subsubselect.as('__active_record_temp') end - def mariadb? - full_version =~ /mariadb/i - end - def supports_rename_index? mariadb? ? false : version >= '5.7.6' end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb index d3a91f73c8..d5a181d3e2 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb @@ -14,6 +14,10 @@ module ActiveRecord @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}") end + def quoted_time(value) + quoted_date(value) + end + private def _quote(value) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 550416238f..ecce949370 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -84,7 +84,6 @@ module ActiveRecord return ["1=0"] if attributes.empty? attributes.flat_map do |key, value| - key = key.to_s if value.is_a?(Hash) associated_predicate_builder(key).expand_from_hash(value) else @@ -137,11 +136,11 @@ module ActiveRecord end def convert_dot_notation_to_hash(attributes) - dot_notation = attributes.keys.select do |s| - s.respond_to?(:include?) && s.include?(".".freeze) + dot_notation = attributes.select do |k, v| + k.include?(".".freeze) && !v.is_a?(Hash) end - dot_notation.each do |key| + dot_notation.each_key do |key| table_name, column_name = key.split(".".freeze) value = attributes.delete(key) attributes[table_name] ||= {} diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index c0ccb00b6f..dbf172a577 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -15,6 +15,7 @@ module ActiveRecord when Hash attributes = predicate_builder.resolve_column_aliases(opts) attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes) + attributes.stringify_keys! attributes, binds = predicate_builder.create_binds(attributes) diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb index 097d1bd363..513c938088 100644 --- a/activerecord/lib/active_record/type/internal/abstract_json.rb +++ b/activerecord/lib/active_record/type/internal/abstract_json.rb @@ -17,11 +17,7 @@ module ActiveRecord end def serialize(value) - if value.is_a?(::Array) || value.is_a?(::Hash) - ::ActiveSupport::JSON.encode(value) - else - value - end + ::ActiveSupport::JSON.encode(value) end def accessor diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb index 70988d84ff..7da49e43c7 100644 --- a/activerecord/lib/active_record/type/time.rb +++ b/activerecord/lib/active_record/type/time.rb @@ -2,6 +2,18 @@ module ActiveRecord module Type class Time < ActiveModel::Type::Time include Internal::Timezone + + class Value < DelegateClass(::Time) # :nodoc: + end + + def serialize(value) + case value = super + when ::Time + Value.new(value) + else + value + end + end end end end diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb index c8c933af5e..9c3fef1b59 100644 --- a/activerecord/test/cases/adapters/mysql2/json_test.rb +++ b/activerecord/test/cases/adapters/mysql2/json_test.rb @@ -161,12 +161,19 @@ class Mysql2JSONTest < ActiveRecord::Mysql2TestCase assert_not json.changed? end - def test_assigning_invalid_json - json = JsonDataType.new + def test_assigning_string_literal + json = JsonDataType.create(payload: "foo") + assert_equal "foo", json.payload + end - json.payload = 'foo' + def test_assigning_number + json = JsonDataType.create(payload: 1.234) + assert_equal 1.234, json.payload + end - assert_nil json.payload + def test_assigning_boolean + json = JsonDataType.create(payload: true) + assert_equal true, json.payload end end end diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index b3b121b4fb..663de680b5 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -38,7 +38,7 @@ module PostgresqlJSONSharedTestCases end def test_default - @connection.add_column 'json_data_type', 'permissions', column_type, default: '{"users": "read", "posts": ["read", "write"]}' + @connection.add_column 'json_data_type', 'permissions', column_type, default: {"users": "read", "posts": ["read", "write"]} JsonDataType.reset_column_information assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.column_defaults['permissions']) @@ -178,12 +178,19 @@ module PostgresqlJSONSharedTestCases assert_not json.changed? end - def test_assigning_invalid_json - json = JsonDataType.new + def test_assigning_string_literal + json = JsonDataType.create(payload: "foo") + assert_equal "foo", json.payload + end - json.payload = 'foo' + def test_assigning_number + json = JsonDataType.create(payload: 1.234) + assert_equal 1.234, json.payload + end - assert_nil json.payload + def test_assigning_boolean + json = JsonDataType.create(payload: true) + assert_equal true, json.payload end end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 87a892db37..f3ec2b98d3 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -8,9 +8,7 @@ module ActiveRecord class SQLite3Adapter class QuotingTest < ActiveRecord::SQLite3TestCase def setup - @conn = Base.sqlite3_connection :database => ':memory:', - :adapter => 'sqlite3', - :timeout => 100 + @conn = ActiveRecord::Base.connection end def test_type_cast_binary_encoding_without_logger @@ -89,6 +87,13 @@ module ActiveRecord assert_equal "'hello'", @conn.quote(type.serialize(value)) end + + def test_quoted_time_returns_date_qualified_time + value = ::Time.utc(2000, 1, 1, 12, 30, 0, 999999) + type = Type::Time.new + + assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value)) + end end end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index cd1967c373..a3f8d26100 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -37,8 +37,8 @@ class DirtyTest < ActiveRecord::TestCase def test_attribute_changes # New record - no changes. pirate = Pirate.new - assert !pirate.catchphrase_changed? - assert_nil pirate.catchphrase_change + assert_equal false, pirate.catchphrase_changed? + assert_equal false, pirate.non_validated_parrot_id_changed? # Change catchphrase. pirate.catchphrase = 'arrr' diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 692c6bf2d0..f03df2d99e 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -652,11 +652,16 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { Topic.where(approved: true).find(1) } end - def test_find_on_hash_conditions_with_explicit_table_name + def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_string assert Topic.where('topics.approved' => false).find(1) assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true).find(1) } end + def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_symbol + assert Topic.where('topics.approved': false).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved': true).find(1) } + end + def test_find_on_hash_conditions_with_hashed_table_name assert Topic.where(topics: { approved: false }).find(1) assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) } diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 8708a502e6..b5667b6ac8 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/time/calculations' + module ActiveSupport # FileUpdateChecker specifies the API used by Rails to watch files # and control reloading. The API depends on four methods: @@ -112,7 +114,24 @@ module ActiveSupport # reloading is not triggered. def max_mtime(paths) time_now = Time.now - paths.map {|path| File.mtime(path)}.reject {|mtime| time_now < mtime}.max + max_mtime = nil + + # Time comparisons are performed with #compare_without_coercion because + # AS redefines these operators in a way that is much slower and does not + # bring any benefit in this particular code. + # + # Read t1.compare_without_coercion(t2) < 0 as t1 < t2. + paths.each do |path| + mtime = File.mtime(path) + + next if time_now.compare_without_coercion(mtime) < 0 + + if max_mtime.nil? || max_mtime.compare_without_coercion(mtime) < 0 + max_mtime = mtime + end + end + + max_mtime end def compile_glob(hash) diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb index 45ae8f1a93..43c5540b6f 100644 --- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb @@ -12,7 +12,7 @@ module ActiveSupport private def parts - left, right = number.to_s.split('.') + left, right = number.to_s.split('.'.freeze) left.gsub!(delimiter_pattern) do |digit_to_delimit| "#{digit_to_delimit}#{options[:delimiter]}" end diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb index 981c562551..9fb7dfb779 100644 --- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb @@ -29,9 +29,11 @@ module ActiveSupport formatted_string = if BigDecimal === rounded_number && rounded_number.finite? - s = rounded_number.to_s('F') + '0'*precision - a, b = s.split('.', 2) - a + '.' + b[0, precision] + s = rounded_number.to_s('F') + s << '0'.freeze * precision + a, b = s.split('.'.freeze, 2) + a << '.'.freeze + a << b[0, precision] else "%00.#{precision}f" % rounded_number end diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb index 5207860a0e..12e67a1e9f 100644 --- a/activesupport/test/file_update_checker_shared_tests.rb +++ b/activesupport/test/file_update_checker_shared_tests.rb @@ -134,6 +134,22 @@ module FileUpdateCheckerSharedTests assert_equal 1, i end + test 'should return max_time for files with mtime = Time.at(0)' do + i = 0 + + FileUtils.touch(tmpfiles) + + time = Time.at(0) # wrong mtime from the future + File.utime(time, time, tmpfiles[0]) + + checker = new_checker(tmpfiles) { i += 1 } + + touch(tmpfiles[1..-1]) + + assert checker.execute_if_updated + assert_equal 1, i + end + test 'should cache updated result until execute' do i = 0 diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index 8132f77b4e..4f4f27d6e4 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,9 +1,16 @@ * Update example of passing a proc to `:message` option for validating records. - This behavior was recently changed in https://github.com/rails/rails/pull/24119 to - pass the object being validated as first argument to the `:message` proc - instead of key of the field being validated. + This behavior was recently changed in [Pull Request #24199](https://github.com/rails/rails/pull/24119) to + pass the object being validated as first argument to the `:message` proc, + instead of the key of the field being validated. + + *Prathamesh Sonpatki* +* Added new guide: Action Cable Overview. + + *David Kuhta* + + ## Rails 5.0.0.beta3 (February 24, 2016) ## * No changes. @@ -16,23 +23,23 @@ ## Rails 5.0.0.beta1 (December 18, 2015) ## -* Add code of conduct to contributing guide +* Add code of conduct to contributing guide. *Jon Moss* -* New section in Configuring: Configuring Active Job +* New section in Configuring: Configuring Active Job. *Eliot Sykes* -* New section in Active Record Association Basics: Single Table Inheritance +* New section in Active Record Association Basics: Single Table Inheritance. *Andrey Nering* -* New section in Active Record Querying: Understanding The Method Chaining +* New section in Active Record Querying: Understanding The Method Chaining. *Andrey Nering* -* New section in Configuring: Search Engines Indexing +* New section in Configuring: Search Engines Indexing. *Andrey Nering* diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index b9444510ea..4615cf18e6 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -108,7 +108,7 @@ In Rails 4.0, several features have been extracted into gems. You can simply add * Mass assignment protection in Active Record models ([GitHub](https://github.com/rails/protected_attributes), [Pull Request](https://github.com/rails/rails/pull/7251)) * ActiveRecord::SessionStore ([GitHub](https://github.com/rails/activerecord-session_store), [Pull Request](https://github.com/rails/rails/pull/7436)) * Active Record Observers ([GitHub](https://github.com/rails/rails-observers), [Commit](https://github.com/rails/rails/commit/39e85b3b90c58449164673909a6f1893cba290b2)) -* Active Resource ([GitHub](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource)) +* Active Resource ([GitHub](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia-blog-blog.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource)) * Action Caching ([GitHub](https://github.com/rails/actionpack-action_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) * Page Caching ([GitHub](https://github.com/rails/actionpack-page_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) * Sprockets ([GitHub](https://github.com/rails/sprockets-rails)) diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md index d1f17fdce5..0c486bb96c 100644 --- a/guides/source/action_cable_overview.md +++ b/guides/source/action_cable_overview.md @@ -549,12 +549,13 @@ You can change that in `config/database.yml` through the `pool` attribute. ### In App Action Cable can run alongside your Rails application. For example, to -listen for WebSocket requests on `/websocket`, mount the server at that path: +listen for WebSocket requests on `/websocket`, specify that path to +`config.action_cable.mount_path`: ```ruby -# config/routes.rb -Example::Application.routes.draw do - mount ActionCable.server => '/cable' +# config/application.rb +class Application < Rails::Application + config.action_cable.mount_path = '/websocket' end ``` diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index cd6b7fdd67..b89485b945 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -353,7 +353,7 @@ end ``` will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table -(when using MySQL, the default is `ENGINE=InnoDB`). +(when using MySQL or MariaDB, the default is `ENGINE=InnoDB`). ### Creating a Join Table @@ -970,7 +970,7 @@ this, then you should set the schema format to `:sql`. Instead of using Active Record's schema dumper, the database's structure will be dumped using a tool specific to the database (via the `db:structure:dump` rails task) into `db/structure.sql`. For example, for PostgreSQL, the `pg_dump` -utility is used. For MySQL, this file will contain the output of +utility is used. For MySQL and MariaDB, this file will contain the output of `SHOW CREATE TABLE` for the various tables. Loading these schemas is simply a question of executing the SQL statements they diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 9d349691b4..e9f6275e55 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -50,7 +50,7 @@ class Role < ApplicationRecord end ``` -Active Record will perform queries on the database for you and is compatible with most database systems (MySQL, PostgreSQL and SQLite to name a few). Regardless of which database system you're using, the Active Record method format will always be the same. +Active Record will perform queries on the database for you and is compatible with most database systems, including MySQL, MariaDB, PostgreSQL and SQLite. Regardless of which database system you're using, the Active Record method format will always be the same. Retrieving Objects from the Database ------------------------------------ @@ -1915,7 +1915,7 @@ EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `articles` ON `articles`.` 2 rows in set (0.00 sec) ``` -under MySQL. +under MySQL and MariaDB. Active Record performs a pretty printing that emulates that of the corresponding database shell. So, the same query running with the @@ -1975,7 +1975,7 @@ EXPLAIN for: SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` IN 1 row in set (0.00 sec) ``` -under MySQL. +under MySQL and MariaDB. ### Interpreting EXPLAIN @@ -1986,4 +1986,6 @@ following pointers may be helpful: * MySQL: [EXPLAIN Output Format](http://dev.mysql.com/doc/refman/5.7/en/explain-output.html) +* MariaDB: [EXPLAIN](https://mariadb.com/kb/en/mariadb/explain/) + * PostgreSQL: [Using EXPLAIN](http://www.postgresql.org/docs/current/static/using-explain.html) diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 62d742fc28..0905c4bd16 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -326,7 +326,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 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 (including MariaDB), PostgreSQL and SQLite3. INFO: You can also use the alias "db" to invoke the dbconsole: `rails db`. diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 6b9b95f1ab..582b14e25f 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -336,7 +336,7 @@ All these configuration options are delegated to the `I18n` library. The MySQL adapter adds one additional configuration option: -* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default. +* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. True by default. The schema dumper adds one additional configuration option: @@ -784,11 +784,11 @@ development: timeout: 5000 ``` -NOTE: Rails uses an SQLite3 database for data storage by default because it is a zero configuration database that just works. Rails also supports MySQL and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it. +NOTE: Rails uses an SQLite3 database for data storage by default because it is a zero configuration database that just works. Rails also supports MySQL (including MariaDB) and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it. -#### Configuring a MySQL Database +#### Configuring a MySQL or MariaDB Database -If you choose to use MySQL instead of the shipped SQLite3 database, your `config/database.yml` will look a little different. Here's the development section: +If you choose to use MySQL or MariaDB instead of the shipped SQLite3 database, your `config/database.yml` will look a little different. Here's the development section: ```yaml development: @@ -801,7 +801,7 @@ development: socket: /tmp/mysql.sock ``` -If your development computer's MySQL installation includes a root user with an empty password, this configuration should work for you. Otherwise, change the username and password in the `development` section as appropriate. +If your development database has a root user with an empty password, this configuration should work for you. Otherwise, change the username and password in the `development` section as appropriate. #### Configuring a PostgreSQL Database @@ -843,9 +843,9 @@ development: database: db/development.sqlite3 ``` -#### Configuring a MySQL Database for JRuby Platform +#### Configuring a MySQL or MariaDB Database for JRuby Platform -If you choose to use MySQL and are using JRuby, your `config/database.yml` will look a little different. Here's the development section: +If you choose to use MySQL or MariaDB and are using JRuby, your `config/database.yml` will look a little different. Here's the development section: ```yaml development: diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 0c1e00100b..d14c9158ca 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -70,7 +70,8 @@ Upgrading from Rails 4.2 to Rails 5.0 ### Ruby 2.2.2+ -ToDo... +From Ruby on Rails 5.0 onwards, Ruby 2.2.2+ is the only supported version. +Make sure you are on Ruby 2.2.2 version or greater, before you proceed. ### Active Record models now inherit from ApplicationRecord by default diff --git a/railties/test/generators/channel_generator_test.rb b/railties/test/generators/channel_generator_test.rb index cda9e8b910..23d0c7b4a4 100644 --- a/railties/test/generators/channel_generator_test.rb +++ b/railties/test/generators/channel_generator_test.rb @@ -38,4 +38,12 @@ class ChannelGeneratorTest < Rails::Generators::TestCase assert_no_file "app/assets/javascripts/channels/chat.coffee" end + + def test_cable_js_is_created_if_not_present_already + run_generator ['chat'] + FileUtils.rm("#{destination_root}/app/assets/javascripts/cable.js") + run_generator ['camp'] + + assert_file "app/assets/javascripts/cable.js" + end end |