diff options
79 files changed, 467 insertions, 150 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml index 877c67873d..17fcaa4360 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -7,20 +7,6 @@ ratings: - "**.rb" exclude_paths: - - actioncable/lib/rails/generators/ - - actioncable/test/ - - actionmailer/lib/rails/generators/ - - actionmailer/test/ - - actionpack/test/ - - actionview/test/ - - activejob/lib/rails/generators/ - - activejob/test/ - - activemodel/test/ - - activerecord/lib/rails/generators/ - - activerecord/test/ - - activesupport/test/ - - railties/lib/rails/generators/ - - railties/test/ - ci/ - guides/ - tasks/ @@ -27,6 +27,9 @@ gem 'uglifier', '>= 1.3.0', require: false # Track stable branch of sass because it doesn't have circular require warnings. gem 'sass', github: 'sass/sass', branch: 'stable', require: false +# FIXME: Remove this fork after https://github.com/nex3/rb-inotify/pull/49 is fixed. +gem 'rb-inotify', github: 'matthewd/rb-inotify', branch: 'close-handling', require: false + group :doc do gem 'sdoc', '~> 0.4.0' gem 'redcarpet', '~> 3.2.3', platforms: :ruby diff --git a/Gemfile.lock b/Gemfile.lock index 9fb842ac2c..cf8bb0fec9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -22,6 +22,14 @@ GIT delayed_job (>= 3.0, < 5) GIT + remote: git://github.com/matthewd/rb-inotify.git + revision: 90553518d1fb79aedc98a3036c59bd2b6731ac40 + branch: close-handling + specs: + rb-inotify (0.9.7) + ffi (>= 0.5.0) + +GIT remote: git://github.com/resque/resque.git revision: 06036388ec61e573c761ac5a25a2ef3c76537ec7 specs: @@ -103,7 +111,7 @@ GEM specs: addressable (2.4.0) amq-protocol (2.0.1) - arel (7.0.0) + arel (7.1.1) backburner (1.3.0) beaneater (~> 1.0) dante (> 0.1.5) @@ -254,8 +262,6 @@ GEM loofah (~> 2.0) rake (11.1.2) rb-fsevent (0.9.7) - rb-inotify (0.9.7) - ffi (>= 0.5.0) rdoc (4.2.2) json (~> 1.4) redcarpet (3.2.3) @@ -381,6 +387,7 @@ DEPENDENCIES rack-cache (~> 1.2) rails! rake (>= 11.1) + rb-inotify! redcarpet (~> 3.2.3) redis resque! @@ -401,4 +408,4 @@ DEPENDENCIES wdm (>= 0.1.0) BUNDLED WITH - 1.11.2 + 1.12.5 diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index d50cbaee38..7bb9b62468 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,10 @@ +* Fix 'defaults' option for root route. + + A regression from some refactoring for the 5.0 release, this change + fixes the use of 'defaults' (default parameters) in the 'root' routing method. + + *Chris Arcand* + * Check `request.path_parameters` encoding at the point they're set. Check for any non-UTF8 characters in path parameters at the point they're diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 3f0e51790c..ea0e2ee41f 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -65,7 +65,7 @@ module ActionDispatch private def parse_formatted_parameters(parsers) - return yield if content_length.zero? + return yield if content_length.zero? || content_mime_type.nil? strategy = parsers.fetch(content_mime_type.symbol) { return yield } diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 73b4864e45..12ddd0f148 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1923,7 +1923,14 @@ to this: def match_root_route(options) name = has_named_route?(:root) ? nil : :root - match '/', { :as => name, :via => :get }.merge!(options) + defaults_option = options.delete(:defaults) + args = ['/', { as: name, via: :get }.merge!(options)] + + if defaults_option + defaults(defaults_option) { match(*args) } + else + match(*args) + end end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 4897f44268..9a76b68ae1 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -717,7 +717,11 @@ module ActionDispatch module ClassMethods def app - defined?(@@app) ? @@app : ActionDispatch.test_app + if defined?(@@app) && @@app + @@app + else + ActionDispatch.test_app + end end def app=(app) diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 5298e63fef..cac89417a5 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1759,6 +1759,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 1, @request.params[:page] end + def test_keyed_default_string_params_with_root + draw do + root to: 'pages#show', defaults: { id: 'home' } + end + + get '/' + assert_equal 'home', @request.params[:id] + end + + def test_default_string_params_with_root + draw do + root to: 'pages#show', id: 'home' + end + + get '/' + assert_equal 'home', @request.params[:id] + end + def test_resource_constraints draw do resources :products, :constraints => { :id => /\d{4}/ } do diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index ab4b46c56e..7754bd8dd9 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,14 @@ +* Changed partial rendering with a collection to allow collections which + implement `to_a`. + + Extracting the collection option had an optimization to avoid unnecessary + queries of ActiveRecord Relations by calling `#to_ary` on the given + collection. Instances of `Enumerator` or `Enumerable` are valid + collections, but they do not implement `#to_ary`. By changing this to + `#to_a`, they will now be extracted and rendered as expected. + + *Steven Harman* + * New syntax for tag helpers. Avoid positional parameters and support HTML5 by default. Example usage of tag helpers before: diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index bcbb3db6a9..d7acf08b45 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/hash/keys' +require 'active_support/core_ext/regexp' require 'action_view/helpers/asset_url_helper' require 'action_view/helpers/tag_helper' @@ -213,8 +214,8 @@ module ActionView src = options[:src] = path_to_image(source) - unless src =~ /^(?:cid|data):/ || src.blank? - options[:alt] = options.fetch(:alt){ image_alt(src) } + unless src.start_with?('cid:') || src.start_with?('data:') || src.blank? + options[:alt] = options.fetch(:alt) { image_alt(src) } end options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] @@ -322,9 +323,9 @@ module ActionView def extract_dimensions(size) size = size.to_s - if size =~ %r{\A\d+x\d+\z} + if /\A\d+x\d+\z/.match?(size) size.split('x') - elsif size =~ %r{\A\d+\z} + elsif /\A\d+\z/.match?(size) [size, size] end end diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb index 717b326740..8af01617fa 100644 --- a/actionview/lib/action_view/helpers/asset_url_helper.rb +++ b/actionview/lib/action_view/helpers/asset_url_helper.rb @@ -1,4 +1,5 @@ require 'zlib' +require 'active_support/core_ext/regexp' module ActionView # = Action View Asset URL Helpers @@ -131,8 +132,8 @@ module ActionView raise ArgumentError, "nil is not a valid asset source" if source.nil? source = source.to_s - return "" unless source.present? - return source if source =~ URI_REGEXP + return '' if source.blank? + return source if URI_REGEXP.match?(source) tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, ''.freeze) @@ -213,19 +214,21 @@ module ActionView host = options[:host] host ||= config.asset_host if defined? config.asset_host - if host.respond_to?(:call) - arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity - args = [source] - args << request if request && (arity > 1 || arity < 0) - host = host.call(*args) - elsif host =~ /%d/ - host = host % (Zlib.crc32(source) % 4) + if host + if host.respond_to?(:call) + arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity + args = [source] + args << request if request && (arity > 1 || arity < 0) + host = host.call(*args) + elsif host.include?('%d') + host = host % (Zlib.crc32(source) % 4) + end end host ||= request.base_url if request && options[:protocol] == :request return unless host - if host =~ URI_REGEXP + if URI_REGEXP.match?(host) host else protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative) diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index 152e1b1211..622bb193ae 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -1,5 +1,6 @@ require 'action_view/helpers/tag_helper' require 'active_support/core_ext/string/access' +require 'active_support/core_ext/regexp' require 'i18n/exceptions' module ActionView @@ -133,7 +134,7 @@ module ActionView end def html_safe_translation_key?(key) - key.to_s =~ /(\b|_|\.)html$/ + /(\b|_|\.)html$/.match?(key.to_s) end end end diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index fb6426b997..5d7940a7b1 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -2,6 +2,7 @@ require 'action_view/helpers/javascript_helper' require 'active_support/core_ext/array/access' require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/string/output_safety' +require 'active_support/core_ext/regexp' module ActionView # = Action View URL Helpers @@ -550,7 +551,7 @@ module ActionView url_string.chomp!("/") if url_string.start_with?("/") && url_string != "/" - if url_string =~ /^\w+:\/\// + if %r{^\w+://}.match?(url_string) url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}" else url_string == request_uri diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index 8db1674187..8e956c47c6 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -1,5 +1,6 @@ -require "action_view/rendering" -require "active_support/core_ext/module/remove_method" +require 'action_view/rendering' +require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/regexp' module ActionView # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in @@ -279,7 +280,7 @@ module ActionView def _write_layout_method # :nodoc: remove_possible_method(:_layout) - prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"] + prefixes = /\blayouts/.match?(_implied_layout_name) ? [] : ["layouts"] default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, [], { formats: formats }).first || super" name_clause = if name default_behavior diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 13b4ec6133..7c2e07185c 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -1,5 +1,6 @@ -require 'action_view/renderer/partial_renderer/collection_caching' require 'concurrent/map' +require 'active_support/core_ext/regexp' +require 'action_view/renderer/partial_renderer/collection_caching' module ActionView class PartialIteration @@ -386,7 +387,7 @@ module ActionView end if as = options[:as] - raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/ + raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s) as = as.to_sym end @@ -403,7 +404,7 @@ module ActionView def collection_from_options if @options.key?(:collection) collection = @options[:collection] - collection.respond_to?(:to_ary) ? collection.to_ary : [] + collection ? collection.to_a : [] end end diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index 1d6afb90fe..9b106cd64a 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -83,7 +83,7 @@ module ActionView case layout when String begin - if layout =~ /^\// + if layout.start_with?('/') with_fallbacks { find_template(layout, nil, false, [], details) } else find_template(layout, nil, false, [], details) diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index 3f38c3d2b9..0f1348b032 100644 --- a/actionview/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb @@ -1,4 +1,5 @@ require "active_support/core_ext/enumerable" +require 'active_support/core_ext/regexp' module ActionView # = Action View Errors @@ -35,7 +36,7 @@ module ActionView prefixes = Array(prefixes) template_type = if partial "partial" - elsif path =~ /layouts/i + elsif /layouts/i.match?(path) 'layout' else 'template' diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb index 85a100ed4c..058b590c56 100644 --- a/actionview/lib/action_view/template/handlers/erb.rb +++ b/actionview/lib/action_view/template/handlers/erb.rb @@ -1,4 +1,5 @@ require 'erubis' +require 'active_support/core_ext/regexp' module ActionView class Template @@ -39,7 +40,7 @@ module ActionView def add_expr_literal(src, code) flush_newline_if_pending(src) - if code =~ BLOCK_EXPR + if BLOCK_EXPR.match?(code) src << '@output_buffer.append= ' << code else src << '@output_buffer.append=(' << code << ');' @@ -48,7 +49,7 @@ module ActionView def add_expr_escaped(src, code) flush_newline_if_pending(src) - if code =~ BLOCK_EXPR + if BLOCK_EXPR.match?(code) src << "@output_buffer.safe_expr_append= " << code else src << "@output_buffer.safe_expr_append=(" << code << ");" diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb index 2664aca991..982ecf9efc 100644 --- a/actionview/lib/action_view/testing/resolvers.rb +++ b/actionview/lib/action_view/testing/resolvers.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/regexp' require 'action_view/template/resolver' module ActionView #:nodoc: @@ -29,7 +30,7 @@ module ActionView #:nodoc: templates = [] @hash.each do |_path, array| source, updated_at = array - next unless _path =~ query + next unless query.match?(_path) handler, format, variant = extract_handler_and_format_and_variant(_path, formats) templates << Template.new(source, _path, handler, :virtual_path => path.virtual, @@ -50,4 +51,3 @@ module ActionView #:nodoc: end end end - diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb index 64bc4c41d6..ecfef3dc8c 100644 --- a/actionview/test/actionpack/controller/layout_test.rb +++ b/actionview/test/actionpack/controller/layout_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'active_support/core_ext/array/extract_options' +require 'active_support/core_ext/regexp' # The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited # method has access to the view_paths array when looking for a layout to automatically assign. @@ -252,7 +253,7 @@ class LayoutStatusIsRenderedTest < ActionController::TestCase end end -unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ +unless /mswin|mingw/.match?(RbConfig::CONFIG['host_os']) class LayoutSymlinkedTest < LayoutTest layout "symlinked/symlinked_layout" end diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 25b21850b1..68574d4adb 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -309,6 +309,14 @@ module RenderTestCases assert_nil @view.render(:partial => "test/customer", :collection => nil) end + def test_render_partial_collection_for_non_array + customers = Enumerator.new do |y| + y.yield(Customer.new("david")) + y.yield(Customer.new("mary")) + end + assert_equal "Hello: davidHello: mary", @view.render(partial: "test/customer", collection: customers) + end + def test_render_partial_without_object_does_not_put_partial_name_to_local_assigns assert_equal 'false', @view.render(partial: 'test/partial_name_in_local_assigns') end diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb index f7f882c998..f0606b3834 100644 --- a/activejob/lib/active_job/core.rb +++ b/activejob/lib/active_job/core.rb @@ -105,6 +105,7 @@ module ActiveJob # end def deserialize(job_data) self.job_id = job_data['job_id'] + self.provider_job_id = job_data['provider_job_id'] self.queue_name = job_data['queue_name'] self.priority = job_data['priority'] self.serialized_arguments = job_data['arguments'] diff --git a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb index c321776bf5..3d33b3923b 100644 --- a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb @@ -37,7 +37,7 @@ module ActiveJob include Sidekiq::Worker def perform(job_data) - Base.execute job_data + Base.execute job_data.merge('provider_job_id' => jid) end end end diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb index 40f27500a5..ab3de3a8b9 100644 --- a/activejob/test/integration/queuing_test.rb +++ b/activejob/test/integration/queuing_test.rb @@ -1,6 +1,7 @@ require 'helper' require 'jobs/logging_job' require 'jobs/hello_job' +require 'jobs/provider_jid_job' require 'active_support/core_ext/numeric/time' class QueuingTest < ActiveSupport::TestCase @@ -34,6 +35,14 @@ class QueuingTest < ActiveSupport::TestCase end end + test 'should access provider_job_id inside Sidekiq job' do + skip unless adapter_is?(:sidekiq) + Sidekiq::Testing.inline! do + job = ::ProviderJidJob.perform_later + assert_equal "Provider Job ID: #{job.provider_job_id}", JobBuffer.last_value + end + end + test 'should not run job enqueued in the future' do begin TestJob.set(wait: 10.minutes).perform_later @id diff --git a/activejob/test/jobs/provider_jid_job.rb b/activejob/test/jobs/provider_jid_job.rb new file mode 100644 index 0000000000..e4f585fa94 --- /dev/null +++ b/activejob/test/jobs/provider_jid_job.rb @@ -0,0 +1,7 @@ +require_relative '../support/job_buffer' + +class ProviderJidJob < ActiveJob::Base + def perform + JobBuffer.add("Provider Job ID: #{provider_job_id}") + end +end diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index cc6285f932..140eb9420e 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -1,5 +1,6 @@ require 'concurrent/map' require 'mutex_m' +require 'active_support/core_ext/regexp' module ActiveModel # Raised when an attribute is not defined. @@ -366,7 +367,7 @@ module ActiveModel # using the given `extra` args. This falls back on `define_method` # and `send` if the given names cannot be compiled. def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc: - defn = if name =~ NAME_COMPILABLE_REGEXP + defn = if NAME_COMPILABLE_REGEXP.match?(name) "def #{name}(*args)" else "define_method(:'#{name}') do |*args|" @@ -374,7 +375,7 @@ module ActiveModel extra = (extra.map!(&:inspect) << "*args").join(", ".freeze) - target = if send =~ CALL_COMPILABLE_REGEXP + target = if CALL_COMPILABLE_REGEXP.match?(send) "#{"self." unless include_private}#{send}(#{extra})" else "send(:'#{send}', #{extra})" diff --git a/activemodel/lib/active_model/type/time.rb b/activemodel/lib/active_model/type/time.rb index 34e09f0aba..08d127d187 100644 --- a/activemodel/lib/active_model/type/time.rb +++ b/activemodel/lib/active_model/type/time.rb @@ -29,7 +29,7 @@ module ActiveModel return value unless value.is_a?(::String) return if value.empty? - if value =~ /^2000-01-01/ + if value.start_with?('2000-01-01') dummy_time_value = value else dummy_time_value = "2000-01-01 #{value}" diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb index 46a2e54fba..70799aaf23 100644 --- a/activemodel/lib/active_model/validations/format.rb +++ b/activemodel/lib/active_model/validations/format.rb @@ -1,5 +1,6 @@ -module ActiveModel +require 'active_support/core_ext/regexp' +module ActiveModel module Validations class FormatValidator < EachValidator # :nodoc: def validate_each(record, attribute, value) @@ -8,7 +9,7 @@ module ActiveModel record_error(record, attribute, :with, value) if value.to_s !~ regexp elsif options[:without] regexp = option_call(record, :without) - record_error(record, attribute, :without, value) if value.to_s =~ regexp + record_error(record, attribute, :without, value) if regexp.match?(value.to_s) end end diff --git a/activemodel/test/validators/email_validator.rb b/activemodel/test/validators/email_validator.rb index cff47ac230..5dc1fc5ae2 100644 --- a/activemodel/test/validators/email_validator.rb +++ b/activemodel/test/validators/email_validator.rb @@ -1,6 +1,8 @@ +require 'active_support/core_ext/regexp' + class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors[attribute] << (options[:message] || "is not an email") unless - value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i + /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i.match?(value) end -end
\ No newline at end of file +end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 590fe2f587..14a6c3c9f7 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,12 @@ +* Fix the SELECT statement in `#table_comment` for MySQL. + + *Takeshi Akima* + +* Virtual attributes will no longer raise when read on models loaded from the + database + + *Sean Griffin* + * Support calling the method `merge` in `scope`'s lambda. *Yasuhiro Sugino* diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 56dee10df2..806a905323 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -1070,6 +1070,12 @@ module ActiveRecord proxy_association.reset_scope self end + + private + + def exec_queries + load_target + end end end end diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb index 08e0ec691f..604904abcc 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -15,7 +15,7 @@ module ActiveRecord ensure_not_nested through_proxy = owner.association(through_reflection.name) - through_record = through_proxy.send(:load_target) + through_record = through_proxy.load_target if through_record && !record through_record.destroy 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 c5fbe0d1d1..bdf77009eb 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -56,7 +56,9 @@ module ActiveRecord klass_scope = if klass.current_scope - klass.current_scope.clone + klass.current_scope.clone.tap { |scope| + scope.joins_values = [] + } else relation = ActiveRecord::Relation.create( klass, diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 9530f134d0..2f41e9e45b 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -152,7 +152,7 @@ module ActiveRecord end def _original_value_for_database - value_for_database + type.serialize(original_value) end class FromDatabase < Attribute # :nodoc: @@ -180,7 +180,7 @@ module ActiveRecord value end - def changed_in_place_from?(old_value) + def changed_in_place? false end end diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index 720d5f8b7c..f868f23845 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -3,7 +3,7 @@ require 'active_record/attribute_set/yaml_encoder' module ActiveRecord class AttributeSet # :nodoc: - delegate :each_value, to: :attributes + delegate :each_value, :fetch, to: :attributes def initialize(attributes) @attributes = attributes diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index 24a255efc1..4ffd39d82d 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -3,11 +3,12 @@ require 'active_record/attribute' module ActiveRecord class AttributeSet # :nodoc: class Builder # :nodoc: - attr_reader :types, :always_initialized + attr_reader :types, :always_initialized, :default - def initialize(types, always_initialized = nil) + def initialize(types, always_initialized = nil, &default) @types = types @always_initialized = always_initialized + @default = default end def build_from_database(values = {}, additional_types = {}) @@ -15,21 +16,22 @@ module ActiveRecord values[always_initialized] = nil end - attributes = LazyAttributeHash.new(types, values, additional_types) + attributes = LazyAttributeHash.new(types, values, additional_types, &default) AttributeSet.new(attributes) end end end class LazyAttributeHash # :nodoc: - delegate :transform_values, :each_key, :each_value, to: :materialize + delegate :transform_values, :each_key, :each_value, :fetch, to: :materialize - def initialize(types, values, additional_types) + def initialize(types, values, additional_types, &default) @types = types @values = values @additional_types = additional_types @materialized = false @delegate_hash = {} + @default = default || proc {} end def key?(key) @@ -76,9 +78,21 @@ module ActiveRecord end end + def marshal_dump + materialize + end + + def marshal_load(delegate_hash) + @delegate_hash = delegate_hash + @types = {} + @values = {} + @additional_types = {} + @materialized = true + end + protected - attr_reader :types, :values, :additional_types, :delegate_hash + attr_reader :types, :values, :additional_types, :delegate_hash, :default def materialize unless @materialized @@ -101,7 +115,7 @@ module ActiveRecord if value_present delegate_hash[name] = Attribute.from_database(name, value, type) elsif types.key?(name) - delegate_hash[name] = Attribute.uninitialized(name, type) + delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type) end end end diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 3211b6eaeb..ed0302e763 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -253,7 +253,7 @@ module ActiveRecord name, value, type, - _default_attributes[name], + _default_attributes.fetch(name.to_s) { nil }, ) else default_attribute = Attribute.from_database(name, value, type) diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb index 606abb7e24..5ac1e0c001 100644 --- a/activerecord/lib/active_record/coders/yaml_column.rb +++ b/activerecord/lib/active_record/coders/yaml_column.rb @@ -1,4 +1,5 @@ require 'yaml' +require 'active_support/core_ext/regexp' module ActiveRecord module Coders # :nodoc: 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 74aae3a1e4..e667e16964 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -18,11 +18,12 @@ module ActiveRecord # This is used in the StatementCache object. It returns an object that # can be used to query the database repeatedly. - def cacheable_query(arel) # :nodoc: + def cacheable_query(klass, arel) # :nodoc: + collected = visitor.accept(arel.ast, collector) if prepared_statements - ActiveRecord::StatementCache.query visitor, arel.ast + klass.query(collected.value) else - ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector + klass.partial_query(collected.value) end end @@ -315,7 +316,7 @@ module ActiveRecord end end key_list = fixture.keys.map { |name| quote_column_name(name) } - value_list = prepare_binds_for_database(binds).map do |value| + value_list = binds.map(&:value_for_database).map do |value| begin quote(value) rescue TypeError diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 860ef17dca..eda6af08e3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -112,19 +112,19 @@ module ActiveRecord end def quoted_true - "'t'" + "'t'".freeze end def unquoted_true - 't' + 't'.freeze end def quoted_false - "'f'" + "'f'".freeze end def unquoted_false - 'f' + 'f'.freeze end # Quote date/time values for use in SQL input. Includes microseconds @@ -150,12 +150,12 @@ module ActiveRecord quoted_date(value).sub(/\A2000-01-01 /, '') end - def prepare_binds_for_database(binds) # :nodoc: - binds.map(&:value_for_database) - end - private + def type_casted_binds(binds) + binds.map { |attr| type_cast(attr.value_for_database) } + end + def types_which_need_no_typecasting [nil, Numeric, String] end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 4f8490fa2b..0b8bacff4e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -130,7 +130,7 @@ module ActiveRecord class BindCollector < Arel::Collectors::Bind def compile(bvs, conn) - casted_binds = conn.prepare_binds_for_database(bvs) + casted_binds = bvs.map(&:value_for_database) super(casted_binds.map { |value| conn.quote(value) }) end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 0a98a8e9c1..610d78245f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -9,6 +9,7 @@ require 'active_record/connection_adapters/mysql/schema_dumper' require 'active_record/connection_adapters/mysql/type_metadata' require 'active_support/core_ext/string/strip' +require 'active_support/core_ext/regexp' module ActiveRecord module ConnectionAdapters @@ -409,10 +410,13 @@ module ActiveRecord end def table_comment(table_name) # :nodoc: + schema, name = extract_schema_qualified_name(table_name) + select_value(<<-SQL.strip_heredoc, 'SCHEMA') SELECT table_comment FROM information_schema.tables - WHERE table_name=#{quote(table_name)} + WHERE table_schema = #{quote(schema)} + AND table_name = #{quote(name)} SQL end @@ -543,7 +547,9 @@ module ActiveRecord end end - def table_options(table_name) + def table_options(table_name) # :nodoc: + table_options = {} + create_table_info = create_table_info(table_name) # strip create_definitions and partition_options @@ -552,10 +558,14 @@ module ActiveRecord # strip AUTO_INCREMENT raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1') + table_options[:options] = raw_table_options + # strip COMMENT - raw_table_options.sub!(/ COMMENT='.+'/, '') + if raw_table_options.sub!(/ COMMENT='.+'/, '') + table_options[:comment] = table_comment(table_name) + end - raw_table_options + table_options end # Maps logical Rails types to MySQL-specific data types. diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb index cc19d95669..6d1215df2a 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -77,7 +77,7 @@ module ActiveRecord @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone end - type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } + type_casted_binds = type_casted_binds(binds) log(sql, name, binds, type_casted_binds) do if cache_stmt diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb index fbab654112..af1db30047 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb @@ -2,14 +2,14 @@ module ActiveRecord module ConnectionAdapters module MySQL module Quoting # :nodoc: - QUOTED_TRUE, QUOTED_FALSE = '1', '0' + QUOTED_TRUE, QUOTED_FALSE = '1'.freeze, '0'.freeze def quote_column_name(name) - @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`" + @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`".freeze end def quote_table_name(name) - @quoted_table_names[name] ||= super.gsub('.', '`.`') + @quoted_table_names[name] ||= super.gsub('.', '`.`').freeze end def quoted_true diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 80c219dfd7..9fcf8dbb95 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -28,7 +28,7 @@ module ActiveRecord # - "schema.name".table_name # - "schema.name"."table.name" def quote_table_name(name) # :nodoc: - @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted + @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze end # Quotes schema names for use in SQL queries. @@ -42,7 +42,7 @@ module ActiveRecord # Quotes column names for use in SQL queries. def quote_column_name(name) # :nodoc: - @quoted_column_names[name] ||= PGconn.quote_ident(super) + @quoted_column_names[name] ||= PGconn.quote_ident(super).freeze end # Quote date/time values for use in SQL input. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index cc606e4828..4cf6d4b14a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -238,6 +238,10 @@ module ActiveRecord PostgreSQLColumn.new(*args) end + def table_options(table_name) # :nodoc: + { comment: table_comment(table_name) } + end + # Returns a comment stored in database for given table def table_comment(table_name) # :nodoc: name = Utils.extract_schema_qualified_name(table_name.to_s) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 487165d511..61a980fda9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -597,13 +597,13 @@ module ActiveRecord end def exec_no_cache(sql, name, binds) - type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } + type_casted_binds = type_casted_binds(binds) log(sql, name, binds, type_casted_binds) { @connection.async_exec(sql, type_casted_binds) } end def exec_cache(sql, name, binds) stmt_key = prepare_statement(sql) - type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } + type_casted_binds = type_casted_binds(binds) log(sql, name, binds, type_casted_binds, stmt_key) do @connection.exec_prepared(stmt_key, type_casted_binds) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb index d5a181d3e2..fa20175b0e 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb @@ -11,7 +11,7 @@ module ActiveRecord end def quote_column_name(name) - @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}") + @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze end def quoted_time(value) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index ed6bff4665..41ed784d2e 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -188,7 +188,7 @@ module ActiveRecord end def exec_query(sql, name = nil, binds = [], prepare: false) - type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } + type_casted_binds = type_casted_binds(binds) log(sql, name, binds, type_casted_binds) do # Don't cache statements if they are not prepared @@ -203,7 +203,6 @@ module ActiveRecord ensure stmt.close end - stmt = records else cache = @statements[sql] ||= { :stmt => @connection.prepare(sql) @@ -212,9 +211,10 @@ module ActiveRecord cols = cache[:cols] ||= stmt.columns stmt.reset! stmt.bind_params(type_casted_binds) + records = stmt.to_a end - ActiveRecord::Result.new(cols, stmt.to_a) + ActiveRecord::Result.new(cols, records) end end diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb index 88cb66d977..0bdebb3989 100644 --- a/activerecord/lib/active_record/dynamic_matchers.rb +++ b/activerecord/lib/active_record/dynamic_matchers.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/regexp' + module ActiveRecord module DynamicMatchers #:nodoc: def respond_to?(name, include_private = false) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 1ad209cbbf..1bb4688717 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1,5 +1,6 @@ -require "active_support/core_ext/module/attribute_accessors" require 'set' +require "active_support/core_ext/module/attribute_accessors" +require 'active_support/core_ext/regexp' module ActiveRecord class MigrationError < ActiveRecordError#:nodoc: diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 99facb4b25..41cebb0e34 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -249,7 +249,11 @@ module ActiveRecord end def attributes_builder # :nodoc: - @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) + @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) do |name| + unless columns_hash.key?(name) + _default_attributes[name].dup + end + end end def columns_hash # :nodoc: diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 13f80270a4..ad74659cba 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -1,4 +1,5 @@ require 'active_support/concern' +require 'active_support/core_ext/regexp' module ActiveRecord module Delegation # :nodoc: diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 62821cacc9..0749bb30b5 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -4,6 +4,7 @@ require "active_record/relation/where_clause" require "active_record/relation/where_clause_factory" require 'active_model/forbidden_attributes_protection' require 'active_support/core_ext/string/filters' +require 'active_support/core_ext/regexp' module ActiveRecord module QueryMethods diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 6ecb9ce3a7..f007e9e733 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/regexp' + module ActiveRecord module Sanitization extend ActiveSupport::Concern diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index e8c176d603..cc3bb1cd06 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -127,10 +127,10 @@ HEADER tbl.print ", force: :cascade" table_options = @connection.table_options(table) - tbl.print ", options: #{table_options.inspect}" unless table_options.blank? - - if comment = @connection.table_comment(table).presence - tbl.print ", comment: #{comment.inspect}" + if table_options.present? + table_options.each do |key, value| + tbl.print ", #{key}: #{value.inspect}" if value.present? + end end tbl.puts " do |t|" diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index 6c896ccea6..8a29547bda 100644 --- a/activerecord/lib/active_record/statement_cache.rb +++ b/activerecord/lib/active_record/statement_cache.rb @@ -40,7 +40,7 @@ module ActiveRecord end class PartialQuery < Query # :nodoc: - def initialize values + def initialize(values) @values = values @indexes = values.each_with_index.find_all { |thing,i| Arel::Nodes::BindParam === thing @@ -49,19 +49,18 @@ module ActiveRecord def sql_for(binds, connection) val = @values.dup - binds = connection.prepare_binds_for_database(binds) - @indexes.each { |i| val[i] = connection.quote(binds.shift) } + casted_binds = binds.map(&:value_for_database) + @indexes.each { |i| val[i] = connection.quote(casted_binds.shift) } val.join end end - def self.query(visitor, ast) - Query.new visitor.accept(ast, Arel::Collectors::SQLString.new).value + def self.query(sql) + Query.new(sql) end - def self.partial_query(visitor, ast, collector) - collected = visitor.accept(ast, collector).value - PartialQuery.new collected + def self.partial_query(values) + PartialQuery.new(values) end class Params # :nodoc: @@ -92,7 +91,7 @@ module ActiveRecord def self.create(connection, block = Proc.new) relation = block.call Params.new bind_map = BindMap.new relation.bound_attributes - query_builder = connection.cacheable_query relation.arel + query_builder = connection.cacheable_query(self, relation.arel) new query_builder, bind_map end diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index a1326aa359..5fe0d8b5e4 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -42,11 +42,11 @@ module ActiveRecord end def associated_table(table_name) - return self if table_name == arel_table.name - association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.singularize) - if association && !association.polymorphic? + if !association && table_name == arel_table.name + return self + elsif association && !association.polymorphic? association_klass = association.klass arel_table = association_klass.arel_table.alias(table_name) else diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb index 54c958e30b..de03550ec2 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -1,4 +1,5 @@ require 'rails/generators/active_record' +require 'active_support/core_ext/regexp' module ActiveRecord module Generators # :nodoc: 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 e27f16b6e4..a959f3c06a 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 @@ -19,6 +19,7 @@ require 'models/professor' require 'models/treasure' require 'models/price_estimate' require 'models/club' +require 'models/user' require 'models/member' require 'models/membership' require 'models/sponsor' @@ -995,4 +996,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase Project.first.developers_required_by_default.create!(name: "Sean", salary: 50000) end end + + def test_association_name_is_the_same_as_join_table_name + user = User.create! + assert_nothing_raised { user.jobs_pool.clear } + end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 325a06d724..113131b28c 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -459,10 +459,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_predicate person.references, :exists? end - def force_signal37_to_load_all_clients_of_firm - companies(:first_firm).clients_of_firm.each {|f| } - end - # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first def test_counting_with_counter_sql assert_equal 3, Firm.all.merge!(:order => "id").first.clients.count @@ -625,6 +621,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_ids_and_inverse_of force_signal37_to_load_all_clients_of_firm + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + firm = companies(:first_firm) client = firm.clients_of_firm.find(3) assert_kind_of Client, client @@ -749,6 +747,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_adding force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + natural = Client.new("name" => "Natural Company") companies(:first_firm).clients_of_firm << natural assert_equal 3, companies(:first_firm).clients_of_firm.size # checking via the collection @@ -805,6 +806,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_adding_a_collection force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")]) assert_equal 4, companies(:first_firm).clients_of_firm.size assert_equal 4, companies(:first_firm).clients_of_firm.reload.size @@ -948,6 +952,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert new_client.persisted? assert_equal new_client, companies(:first_firm).clients_of_firm.last @@ -967,6 +974,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_deleting force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first) assert_equal 1, companies(:first_firm).clients_of_firm.size assert_equal 1, companies(:first_firm).clients_of_firm.reload.size @@ -1121,6 +1131,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_deleting_a_collection force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert_equal 3, companies(:first_firm).clients_of_firm.size companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1], companies(:first_firm).clients_of_firm[2]]) @@ -1130,6 +1143,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_delete_all force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + companies(:first_firm).dependent_clients_of_firm.create("name" => "Another Client") clients = companies(:first_firm).dependent_clients_of_firm.to_a assert_equal 3, clients.count @@ -1141,6 +1157,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_delete_all_with_not_yet_loaded_association_collection force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert_equal 3, companies(:first_firm).clients_of_firm.size companies(:first_firm).clients_of_firm.reset @@ -1329,6 +1348,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_deleting_a_item_which_is_not_in_the_collection force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + summit = Client.find_by_name('Summit') companies(:first_firm).clients_of_firm.delete(summit) assert_equal 2, companies(:first_firm).clients_of_firm.size @@ -1365,6 +1387,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_destroying force_signal37_to_load_all_clients_of_firm + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + assert_difference "Client.count", -1 do companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first) end @@ -1376,6 +1400,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_destroying_by_integer_id force_signal37_to_load_all_clients_of_firm + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + assert_difference "Client.count", -1 do companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id) end @@ -1387,6 +1413,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_destroying_by_string_id force_signal37_to_load_all_clients_of_firm + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + assert_difference "Client.count", -1 do companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id.to_s) end @@ -1397,6 +1425,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_destroying_a_collection force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert_equal 3, companies(:first_firm).clients_of_firm.size @@ -1410,6 +1441,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_destroy_all force_signal37_to_load_all_clients_of_firm + + assert_predicate companies(:first_firm).clients_of_firm, :loaded? + clients = companies(:first_firm).clients_of_firm.to_a assert !clients.empty?, "37signals has clients after load" destroyed = companies(:first_firm).clients_of_firm.destroy_all @@ -2428,4 +2462,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [bulb.id], car.bulb_ids assert_no_queries { car.bulb_ids } end + + private + + def force_signal37_to_load_all_clients_of_firm + companies(:first_firm).clients_of_firm.load_target + end end diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb index fcf1e0b040..e3b257efb2 100644 --- a/activerecord/test/cases/associations/left_outer_join_association_test.rb +++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb @@ -5,6 +5,7 @@ require 'models/author' require 'models/essay' require 'models/categorization' require 'models/person' +require 'active_support/core_ext/regexp' class LeftOuterJoinAssociationTest < ActiveRecord::TestCase fixtures :authors, :essays, :posts, :comments, :categorizations, :people diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 7412d48fe8..7efacb44f3 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -45,7 +45,6 @@ class AssociationsTest < ActiveRecord::TestCase ship = Ship.create!(:name => "The good ship Dollypop") part = ship.parts.create!(:name => "Mast") part.mark_for_destruction - ship.parts.send(:load_target) assert ship.parts[0].marked_for_destruction? end @@ -54,7 +53,6 @@ class AssociationsTest < ActiveRecord::TestCase part = ship.parts.create!(:name => "Mast") part.mark_for_destruction ShipPart.find(part.id).update_columns(name: 'Deck') - ship.parts.send(:load_target) assert_equal 'Deck', ship.parts[0].name end @@ -183,6 +181,14 @@ class AssociationProxyTest < ActiveRecord::TestCase assert !david.projects.loaded? end + def test_load_does_load_target + david = developers(:david) + + assert !david.projects.loaded? + david.projects.load + assert david.projects.loaded? + end + def test_inspect_does_not_reload_a_not_yet_loaded_target andreas = Developer.new :name => 'Andreas', :log => 'new developer added' assert !andreas.audit_logs.loaded? diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb index 7bcaa53aa2..604411da97 100644 --- a/activerecord/test/cases/attributes_test.rb +++ b/activerecord/test/cases/attributes_test.rb @@ -205,5 +205,49 @@ module ActiveRecord assert_equal(:bar, child.new(foo: :bar).foo) end + + test "attributes not backed by database columns are not dirty when unchanged" do + refute OverloadedType.new.non_existent_decimal_changed? + end + + test "attributes not backed by database columns are always initialized" do + OverloadedType.create! + model = OverloadedType.first + + assert_nil model.non_existent_decimal + model.non_existent_decimal = "123" + assert_equal 123, model.non_existent_decimal + end + + test "attributes not backed by database columns return the default on models loaded from database" do + child = Class.new(OverloadedType) do + attribute :non_existent_decimal, :decimal, default: 123 + end + child.create! + model = child.first + + assert_equal 123, model.non_existent_decimal + end + + test "attributes not backed by database columns properly interact with mutation and dirty" do + child = Class.new(ActiveRecord::Base) do + self.table_name = "topics" + attribute :foo, :string, default: "lol" + end + child.create! + model = child.first + + assert_equal "lol", model.foo + + model.foo << "asdf" + assert_equal "lolasdf", model.foo + assert model.foo_changed? + + model.reload + assert_equal "lol", model.foo + + model.foo = "lol" + refute model.changed? + end end end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 11fb164d50..8a08056bbe 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -642,13 +642,13 @@ module NestedAttributesOnACollectionAssociationTests def test_should_not_overwrite_unsaved_updates_when_loading_association @pirate.reload @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }]) - assert_equal 'Grace OMalley', @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.name + assert_equal 'Grace OMalley', @pirate.send(@association_name).load_target.find { |r| r.id == @child_1.id }.name end def test_should_preserve_order_when_not_overwriting_unsaved_updates @pirate.reload @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }]) - assert_equal @child_1.id, @pirate.send(@association_name).send(:load_target).first.id + assert_equal @child_1.id, @pirate.send(@association_name).load_target.first.id end def test_should_refresh_saved_records_when_not_overwriting_unsaved_updates @@ -657,13 +657,13 @@ module NestedAttributesOnACollectionAssociationTests @pirate.send(@association_name) << record record.save! @pirate.send(@association_name).last.update!(name: 'Polly') - assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name + assert_equal 'Polly', @pirate.send(@association_name).load_target.last.name end def test_should_not_remove_scheduled_destroys_when_loading_association @pirate.reload @pirate.send(association_setter, [{ :id => @child_1.id, :_destroy => '1' }]) - assert @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.marked_for_destruction? + assert @pirate.send(@association_name).load_target.find { |r| r.id == @child_1.id }.marked_for_destruction? end def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index c01c82f4f5..225e23bc83 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -149,5 +149,21 @@ module ActiveRecord assert_equal "1800", @quoter.quote(30.minutes) end end + + class QuoteBooleanTest < ActiveRecord::TestCase + def setup + @connection = ActiveRecord::Base.connection + end + + def test_quote_returns_frozen_string + assert_predicate @connection.quote(true), :frozen? + assert_predicate @connection.quote(false), :frozen? + end + + def test_type_cast_returns_frozen_value + assert_predicate @connection.type_cast(true), :frozen? + assert_predicate @connection.type_cast(false), :frozen? + end + end end end diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 30d342ac70..6679f9415b 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -5,6 +5,7 @@ require 'models/developer' require 'models/computer' require 'models/vehicle' require 'models/cat' +require 'active_support/core_ext/regexp' class DefaultScopingTest < ActiveRecord::TestCase fixtures :developers, :posts, :comments diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index c15d57460b..ef46fd5d9a 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -228,6 +228,13 @@ class RelationScopingTest < ActiveRecord::TestCase assert SpecialComment.all.any? end end + + def test_circular_joins_with_current_scope_does_not_crash + posts = Post.joins(comments: :post).scoping do + Post.current_scope.first(10) + end + assert_equal posts, Post.joins(comments: :post).first(10) + end end class NestedRelationScopingTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index cde5488315..fcb6552e5b 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -1,5 +1,6 @@ require 'active_support/test_case' require 'active_support/testing/stream' +require 'active_support/core_ext/regexp' module ActiveRecord # = Active Record Test Case diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb index f5dc93e994..97107a35a0 100644 --- a/activerecord/test/models/user.rb +++ b/activerecord/test/models/user.rb @@ -1,6 +1,12 @@ +require 'models/job' + class User < ActiveRecord::Base has_secure_token has_secure_token :auth_token + + has_and_belongs_to_many :jobs_pool, + class_name: Job, + join_table: 'jobs_pool' end class UserWithNotification < User diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 2f2993ce18..a5155e9cdb 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -390,6 +390,11 @@ ActiveRecord::Schema.define do t.integer :ideal_reference_id end + create_table :jobs_pool, force: true, id: false do |t| + t.references :job, null: false, index: true + t.references :user, null: false, index: true + end + create_table :keyboards, force: true, id: false do |t| t.primary_key :key_number t.string :name diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 87efe117c5..1f2736388d 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -99,6 +99,10 @@ module ActiveSupport def _decrypt(encrypted_message) cipher = new_cipher encrypted_data, iv, auth_tag = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)} + + # Currently the OpenSSL bindings do not raise an error if auth_tag is + # truncated, which would allow an attacker to easily forge it. See + # https://github.com/ruby/openssl/issues/63 raise InvalidMessage if aead_mode? && auth_tag.bytes.length != 16 cipher.decrypt diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 90f200133b..8ffd0d033d 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -601,6 +601,7 @@ If you want to call `order` multiple times, subsequent orders will be appended t Client.order("orders_count ASC").order("created_at DESC") # SELECT * FROM clients ORDER BY orders_count ASC, created_at DESC ``` +WARNING: If you are using **MySQL 5.7.5** and above, then on selecting fields from a result set using methods like `select`, `pluck` and `ids`; the `order` method will raise an `ActiveRecord::StatementInvalid` exception unless the field(s) used in `order` clause are included in the select list. See the next section for selecting fields from the result set. Selecting Specific Fields ------------------------- diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 0f1c3735e8..73dbb2bc40 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -465,29 +465,24 @@ The first part identifies which template is missing. In this case, it's the then it will attempt to load a template called `application/new`. It looks for one here because the `ArticlesController` inherits from `ApplicationController`. -The next part of the message contains a hash. The `:locale` key in this hash -simply indicates which spoken language template should be retrieved. By default, -this is the English - or "en" - template. The next key, `:formats` specifies the -format of the template to be served in response. The default format is `:html`, and -so Rails is looking for an HTML template. The final key, `:handlers`, is telling -us what _template handlers_ could be used to render our template. `:erb` is most -commonly used for HTML templates, `:builder` is used for XML templates, and -`:coffee` uses CoffeeScript to build JavaScript templates. - -The message also contains `request.formats` which specifies the format of template to be -served in response. It is set to `text/html` as we requested this page via browser, so Rails -is looking for an HTML template. +The next part of the message contains `request.formats` which specifies +the format of template to be served in response. It is set to `text/html` as we +requested this page via browser, so Rails is looking for an HTML template. +`request.variants` specifies what kind of physical devices would be served by +the response and helps Rails determine which template to use in the response. +It is empty because no information has been provided. The simplest template that would work in this case would be one located at `app/views/articles/new.html.erb`. The extension of this file name is important: the first extension is the _format_ of the template, and the second extension -is the _handler_ that will be used. Rails is attempting to find a template -called `articles/new` within `app/views` for the application. The format for -this template can only be `html` and the handler must be one of `erb`, -`builder` or `coffee`. `:erb` is most commonly used for HTML templates, `:builder` is -used for XML templates, and `:coffee` uses CoffeeScript to build JavaScript templates. -Because you want to create a new HTML form, you will be -using the `ERB` language which is designed to embed Ruby in HTML. +is the _handler_ that will be used to render the template. Rails is attempting +to find a template called `articles/new` within `app/views` for the +application. The format for this template can only be `html` and the default +handler for HTML is `erb`. Rails uses other handlers for other formats. +`builder` handler is used to build XML templates and `coffee` handler uses +CoffeeScript to build JavaScript templates. Because you want to create a new +HTML form, you will be using the `ERB` language which is designed to embed Ruby +in HTML. Therefore the file should be called `articles/new.html.erb` and needs to be located inside the `app/views` directory of the application. diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 1694abb61a..70f4b84237 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* A generated app should not include Uglifier with `--skip-javascript` option. + + *Ben Pickles* + * Set session store to cookie store internally and remove the initializer from the generated app. diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index af3c6dead3..8d4056c202 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -299,9 +299,11 @@ module Rails gems << GemfileEntry.github('sass-rails', 'rails/sass-rails', nil, 'Use SCSS for stylesheets') - gems << GemfileEntry.version('uglifier', - '>= 1.3.0', - 'Use Uglifier as compressor for JavaScript assets') + if !options[:skip_javascript] + gems << GemfileEntry.version('uglifier', + '>= 1.3.0', + 'Use Uglifier as compressor for JavaScript assets') + end gems end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 363af05459..7deab5dbb1 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -19,8 +19,12 @@ Rails.application.configure do config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? <%- unless options.skip_sprockets? -%> + <%- if options.skip_javascript? -%> + # Compress CSS. + <%- else -%> # Compress JavaScripts and CSS. config.assets.js_compressor = :uglifier + <%- end -%> # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. diff --git a/railties/test/application/integration_test_case_test.rb b/railties/test/application/integration_test_case_test.rb index 52df5a2e5a..68e2de80c4 100644 --- a/railties/test/application/integration_test_case_test.rb +++ b/railties/test/application/integration_test_case_test.rb @@ -42,4 +42,32 @@ module ApplicationTests assert_match(/0 failures, 0 errors/, output) end end + + class IntegrationTestDefaultApp < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + setup do + build_app + end + + teardown do + teardown_app + end + + test "app method of integration tests returns test_app by default" do + app_file 'test/integration/default_app_test.rb', <<-RUBY + require 'test_helper' + + class DefaultAppIntegrationTest < ActionDispatch::IntegrationTest + def test_app_returns_action_dispatch_test_app_by_default + assert_equal ActionDispatch.test_app, app + end + end + RUBY + + output = Dir.chdir(app_path) { `bin/rails test 2>&1` } + assert_equal 0, $?.to_i, output + assert_match(/0 failures, 0 errors/, output) + end + end end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index b968f9f207..09309b5cd0 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -468,6 +468,11 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile" do |content| assert_no_match(/coffee-rails/, content) assert_no_match(/jquery-rails/, content) + assert_no_match(/uglifier/, content) + end + + assert_file "config/environments/production.rb" do |content| + assert_no_match(/config\.assets\.js_compressor = :uglifier/, content) end end |