diff options
101 files changed, 802 insertions, 347 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/ diff --git a/.rubocop.yml b/.rubocop.yml index 62fb263c9e..fbca606605 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -26,18 +26,26 @@ Style/AndOr: Lint/RequireParentheses: Enabled: true +# Do not use braces for hash literals when they are the last argument of a +# method call. Style/BracesAroundHashParameters: Enabled: true +# Method definitions after `private` or `protected` isolated calls need one +# extra level of indentation. Style/IndentationConsistency: Enabled: true EnforcedStyle: rails +# In a regular class definition, no empty lines around the body. Style/EmptyLinesAroundClassBody: Enabled: true +# In a regular module definition, no empty lines around the body. Style/EmptyLinesAroundModuleBody: Enabled: true +# Align `end` with the matching keyword or starting expression except for +# assignments, where it should be aligned with the LHS. Lint/EndAlignment: AlignWith: variable diff --git a/Gemfile.lock b/Gemfile.lock index 9fb842ac2c..0a3d91ca29 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -103,7 +103,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) @@ -401,4 +401,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 12430ca965..7bb9b62468 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,7 +1,7 @@ -* Fix 'defaults' option for root route +* 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 + fixes the use of 'defaults' (default parameters) in the 'root' routing method. *Chris Arcand* diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 0559fbc6ce..fd7ffcfcd7 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -109,10 +109,10 @@ module ActionController #:nodoc: # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>. # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference. # * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the - # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful - # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth). + # protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful + # when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth). # - # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>. + # If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>. # * <tt>:with</tt> - Set the method to handle unverified request. # # Valid unverified request handling methods are: 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 fd68d4f185..12ddd0f148 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1924,7 +1924,7 @@ to this: def match_root_route(options) name = has_named_route?(:root) ? nil : :root defaults_option = options.delete(:defaults) - args = ['/', { :as => name, :via => :get }.merge!(options)] + args = ['/', { as: name, via: :get }.merge!(options)] if defaults_option defaults(defaults_option) { match(*args) } 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/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 7faf3cd8c6..9c2619dc3d 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -393,9 +393,14 @@ class CollectionCacheController < ActionController::Base @customers = [Customer.new('david', 1)] render partial: 'customers/commented_customer', collection: @customers, as: :customer, cached: true end + + def index_with_callable_cache_key + @customers = [Customer.new('david', 1)] + render partial: 'customers/customer', collection: @customers, cached: -> customer { 'cached_david' } + end end -class AutomaticCollectionCacheTest < ActionController::TestCase +class CollectionCacheTest < ActionController::TestCase def setup super @controller = CollectionCacheController.new @@ -438,6 +443,11 @@ class AutomaticCollectionCacheTest < ActionController::TestCase assert_equal 1, @controller.partial_rendered_times end + def test_caching_with_callable_cache_key + get :index_with_callable_cache_key + assert_customer_cached 'cached_david', 'david, 1' + end + private def assert_customer_cached(key, content) assert_match content, diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 2fe40de583..cac89417a5 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1761,7 +1761,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_keyed_default_string_params_with_root draw do - root :to => 'pages#show', :defaults => { :id => 'home' } + root to: 'pages#show', defaults: { id: 'home' } end get '/' @@ -1770,7 +1770,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_default_string_params_with_root draw do - root :to => 'pages#show', :id => 'home' + root to: 'pages#show', id: 'home' end get '/' 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/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 4eaaa239e2..6c3092cc46 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -69,11 +69,11 @@ module ActionView # render 'comments/comments' # render('comments/comments') # - # render "header" => render("comments/header") + # render "header" translates to render("comments/header") # - # render(@topic) => render("topics/topic") - # render(topics) => render("topics/topic") - # render(message.topics) => render("topics/topic") + # render(@topic) translates to render("topics/topic") + # render(topics) translates to render("topics/topic") + # render(message.topics) translates to render("topics/topic") # # It's not possible to derive all render calls like that, though. # Here are a few examples of things that can't be derived: @@ -130,9 +130,10 @@ module ActionView # # When rendering a collection of objects that each use the same partial, a `cached` # option can be passed. + # # For collections rendered such: # - # <%= render partial: 'notifications/notification', collection: @notifications, cached: true %> + # <%= render partial: 'projects/project', collection: @projects, cached: true %> # # The `cached: true` will make Action View's rendering read several templates # from cache at once instead of one call per template. @@ -142,13 +143,21 @@ module ActionView # Works great alongside individual template fragment caching. # For instance if the template the collection renders is cached like: # - # # notifications/_notification.html.erb - # <% cache notification do %> + # # projects/_project.html.erb + # <% cache project do %> # <%# ... %> # <% end %> # # Any collection renders will find those cached templates when attempting # to read multiple templates at once. + # + # If your collection cache depends on multiple sources (try to avoid this to keep things simple), + # you can name all these dependencies as part of a block that returns an array: + # + # <%= render partial: 'projects/project', collection: @projects, cached: -> project { [ project, current_user ] } %> + # + # This will include both records as part of the cache key and updating either of them will + # expire the cache. def cache(name = {}, options = {}, &block) if controller.respond_to?(:perform_caching) && controller.perform_caching name_options = options.slice(:skip_digest, :virtual_path) 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/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb index f7deba94ce..1fbe209200 100644 --- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb +++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb @@ -25,9 +25,15 @@ module ActionView end end + def callable_cache_key? + @options[:cached].respond_to?(:call) + end + def collection_by_cache_keys + seed = callable_cache_key? ? @options[:cached] : ->(i) { i } + @collection.each_with_object({}) do |item, hash| - hash[expanded_cache_key(item)] = item + hash[expanded_cache_key(seed.call(item))] = item 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/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..d3e1cf68a2 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,8 @@ +* 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/Rakefile b/activerecord/Rakefile index 012db35765..8fbea61335 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -51,7 +51,7 @@ end adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] t.libs << 'test' t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject { - |x| x =~ /\/adapters\// + |x| x.include?('/adapters/') } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")) t.warning = true @@ -64,7 +64,7 @@ end adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] puts [adapter, adapter_short].inspect (Dir["test/cases/**/*_test.rb"].reject { - |x| x =~ /\/adapters\// + |x| x.include?('/adapters/') } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file| sh(Gem.ruby, '-w' ,"-Itest", file) end or raise "Failures" 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/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 2456b8ad8c..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: @@ -20,7 +21,7 @@ module ActiveRecord def load(yaml) return object_class.new if object_class != Object && yaml.nil? - return yaml unless yaml.is_a?(String) && yaml =~ /^---/ + return yaml unless yaml.is_a?(String) && /^---/.match?(yaml) obj = YAML.load(yaml) assert_valid_value(obj) 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..621f737a5e 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 diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 860ef17dca..0a58921549 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 @@ -156,6 +156,10 @@ module ActiveRecord 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_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 3e77b92141..25e3939b62 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 @@ -85,7 +86,7 @@ module ActiveRecord end def mariadb? # :nodoc: - full_version =~ /mariadb/i + /mariadb/i.match?(full_version) end # Returns true, since this connection adapter supports migrations. 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..5b59e39d9f 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb @@ -2,7 +2,7 @@ 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('`', '``')}`" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 6414459cd1..80c219dfd7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -58,7 +58,7 @@ module ActiveRecord def quote_default_expression(value, column) # :nodoc: if value.is_a?(Proc) value.call - elsif column.type == :uuid && value =~ /\(\)/ + elsif column.type == :uuid && value.include?('()') value # Does not quote function default values for UUID columns elsif column.respond_to?(:array?) value = type_cast_from_column(column, value) 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_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 7e23f44ddf..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 @@ -548,7 +548,7 @@ module ActiveRecord columns_string.split(',').each do |column_string| # This regex will match the column name and collation type and will save # the value in $1 and $2 respectively. - collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string) + collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string end basic_structure.map! do |column| diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb index b6dd6814db..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) @@ -29,7 +31,7 @@ module ActiveRecord attr_reader :matchers def match(model, name) - klass = matchers.find { |k| name =~ k.pattern } + klass = matchers.find { |k| k.pattern.match?(name) } klass.new(model, name) if klass end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 81fe053fe1..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: @@ -126,9 +127,9 @@ module ActiveRecord class PendingMigrationError < MigrationError#:nodoc: def initialize(message = nil) if !message && defined?(Rails.env) - super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate RAILS_ENV=#{::Rails.env}") + super("Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate RAILS_ENV=#{::Rails.env}") elsif !message - super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate") + super("Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate") else super end @@ -145,7 +146,7 @@ module ActiveRecord class NoEnvironmentInSchemaError < MigrationError #:nodoc: def initialize - msg = "Environment data not found in the schema. To resolve this issue, run: \n\n\tbin/rails db:environment:set" + msg = "Environment data not found in the schema. To resolve this issue, run: \n\n bin/rails db:environment:set" if defined?(Rails.env) super("#{msg} RAILS_ENV=#{::Rails.env}") else @@ -168,7 +169,7 @@ module ActiveRecord msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n" msg << "You are running in `#{ current }` environment. " msg << "If you are sure you want to continue, first set the environment using:\n\n" - msg << "\tbin/rails db:environment:set" + msg << " bin/rails db:environment:set" if defined?(Rails.env) super("#{msg} RAILS_ENV=#{::Rails.env}\n\n") else @@ -1057,7 +1058,7 @@ module ActiveRecord end def match_to_migration_filename?(filename) # :nodoc: - File.basename(filename) =~ Migration::MigrationFilenameRegexp + Migration::MigrationFilenameRegexp.match?(File.basename(filename)) end def parse_migration_filename(filename) # :nodoc: diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 114686c5d3..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: @@ -309,7 +313,12 @@ module ActiveRecord # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count", # and columns used for single table inheritance have been removed. def content_columns - @content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column } + @content_columns ||= columns.reject do |c| + c.name == primary_key || + c.name == inheritance_column || + c.name.end_with?('_id') || + c.name.end_with?('_count') + end end # Resets all the cached information about columns, which will cause them diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 2484cb3264..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: @@ -58,7 +59,7 @@ module ActiveRecord @delegation_mutex.synchronize do return if method_defined?(method) - if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/ + if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) module_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{method}(*args, &block) scoping { @klass.#{method}(*args, &block) } diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 8a87015e44..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 @@ -1135,10 +1136,10 @@ module ActiveRecord end def does_not_support_reverse?(order) - #uses sql function with multiple arguments - order =~ /\([^()]*,[^()]*\)/ || - # uses "nulls first" like construction - order =~ /nulls (first|last)\Z/i + # Uses SQL function with multiple arguments. + /\([^()]*,[^()]*\)/.match?(order) || + # Uses "nulls first" like construction. + /nulls (first|last)\Z/i.match?(order) end def build_order(arel) diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index a9e1fd0dad..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 @@ -153,7 +155,7 @@ module ActiveRecord # # => "name='foo''bar' and group_id='4'" def sanitize_sql_array(ary) statement, *values = ary - if values.first.is_a?(Hash) && statement =~ /:\w+/ + if values.first.is_a?(Hash) && /:\w+/.match?(statement) replace_named_bind_variables(statement, values.first) elsif statement.include?('?') replace_bind_variables(statement, values) diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index 6c896ccea6..5607be6d12 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 @@ -55,13 +55,12 @@ module ActiveRecord 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/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index b1a0ad0115..8574807fd2 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -90,7 +90,7 @@ module ActiveRecord end def error_class - if configuration['adapter'] =~ /jdbc/ + if configuration['adapter'].include?('jdbc') require 'active_record/railties/jdbcmysql_error' ArJdbcMySQL::Error elsif defined?(Mysql2) 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 4e5872b585..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: @@ -60,7 +61,7 @@ module ActiveRecord # A migration file name can only contain underscores (_), lowercase characters, # and numbers 0-9. Any other file name will raise an IllegalMigrationNameError. def validate_file_name! - unless file_name =~ /^[_a-z0-9]+$/ + unless /^[_a-z0-9]+$/.match?(file_name) raise IllegalMigrationNameError.new(file_name) 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/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index b3fe759ad9..a2158e4f3b 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -86,7 +86,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase end def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions - real_count = Author.all.to_a.select {|a| a.posts.any? {|p| p.title =~ /^Welcome/} }.length + real_count = Author.all.to_a.select {|a| a.posts.any? {|p| p.title.start_with?('Welcome')} }.length authors_with_welcoming_post_titles = Author.all.merge!(joins: :posts, where: "posts.title like 'Welcome%'").distinct.calculate(:count, 'authors.id') assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'" 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 eee135cfb8..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 @@ -20,7 +21,7 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase Person.left_outer_joins(:agents => {:agents => :agents}) .left_outer_joins(:agents => {:agents => {:primary_contact => :agents}}).to_a end - assert queries.any? { |sql| /agents_people_4/i =~ sql } + assert queries.any? { |sql| /agents_people_4/i.match?(sql) } end end @@ -36,12 +37,12 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase def test_construct_finder_sql_ignores_empty_left_outer_joins_hash queries = capture_sql { Author.left_outer_joins({}) } - assert queries.none? { |sql| /LEFT OUTER JOIN/i =~ sql } + assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) } end def test_construct_finder_sql_ignores_empty_left_outer_joins_array queries = capture_sql { Author.left_outer_joins([]) } - assert queries.none? { |sql| /LEFT OUTER JOIN/i =~ sql } + assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) } end def test_left_outer_joins_forbids_to_use_string_as_argument @@ -50,8 +51,8 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase def test_join_conditions_added_to_join_clause queries = capture_sql { Author.left_outer_joins(:essays).to_a } - assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1|\:a1)/i =~ sql } - assert queries.none? { |sql| /WHERE/i =~ sql } + assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1|\:a1)/i.match?(sql) } + assert queries.none? { |sql| /WHERE/i.match?(sql) } end def test_find_with_sti_join diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 01a058918a..5fb7f191ed 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 diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 04126e87e4..9d66c9964c 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -27,34 +27,34 @@ class AttributeMethodsTest < ActiveRecord::TestCase ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers) end - def test_attribute_for_inspect_string + test 'attribute_for_inspect with a string' do t = topics(:first) t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters" assert_equal '"The First Topic Now Has A Title With\nNewlines And ..."', t.attribute_for_inspect(:title) end - def test_attribute_for_inspect_date + test 'attribute_for_inspect with a date' do t = topics(:first) assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on) end - def test_attribute_for_inspect_array + test 'attribute_for_inspect with an array' do t = topics(:first) t.content = [Object.new] assert_match %r(\[#<Object:0x[0-9a-f]+>\]), t.attribute_for_inspect(:content) end - def test_attribute_for_inspect_long_array + test 'attribute_for_inspect with a long array' do t = topics(:first) t.content = (1..11).to_a assert_equal "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]", t.attribute_for_inspect(:content) end - def test_attribute_present + test 'attribute_present' do t = Topic.new t.title = "hello there!" t.written_on = Time.now @@ -65,7 +65,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert !t.attribute_present?("author_name") end - def test_attribute_present_with_booleans + test 'attribute_present with booleans' do b1 = Boolean.new b1.value = false assert b1.attribute_present?(:value) @@ -83,44 +83,44 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert Boolean.find(b4.id).attribute_present?(:value) end - def test_caching_nil_primary_key + test 'caching a nil primary key' do klass = Class.new(Minimalistic) assert_called(klass, :reset_primary_key, returns: nil) do 2.times { klass.primary_key } end end - def test_attribute_keys_on_new_instance + test 'attribute keys on a new instance' do t = Topic.new assert_equal nil, t.title, "The topics table has a title column, so it should be nil" assert_raise(NoMethodError) { t.title2 } end - def test_boolean_attributes + test 'boolean attributes' do assert !Topic.find(1).approved? assert Topic.find(2).approved? end - def test_set_attributes + test 'set attributes' do topic = Topic.find(1) - topic.attributes = { "title" => "Budget", "author_name" => "Jason" } + topic.attributes = { title: 'Budget', author_name: 'Jason' } topic.save - assert_equal("Budget", topic.title) - assert_equal("Jason", topic.author_name) + assert_equal('Budget', topic.title) + assert_equal('Jason', topic.author_name) assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address) end - def test_set_attributes_without_hash + test 'set attributes without a hash' do topic = Topic.new assert_raise(ArgumentError) { topic.attributes = '' } end - def test_integers_as_nil - test = AutoId.create('value' => '') + test 'integers as nil' do + test = AutoId.create(value: '') assert_nil AutoId.find(test.id).value end - def test_set_attributes_with_block + test 'set attributes with a block' do topic = Topic.new do |t| t.title = "Budget" t.author_name = "Jason" @@ -130,7 +130,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal("Jason", topic.author_name) end - def test_respond_to? + test 'respond_to?' do topic = Topic.find(1) assert_respond_to topic, "title" assert_respond_to topic, "title?" @@ -144,7 +144,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert !topic.respond_to?(:nothingness) end - def test_respond_to_with_custom_primary_key + test 'respond_to? with a custom primary key' do keyboard = Keyboard.create assert_not_nil keyboard.key_number assert_equal keyboard.key_number, keyboard.id @@ -152,7 +152,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert keyboard.respond_to?('id') end - def test_id_before_type_cast_with_custom_primary_key + test 'id_before_type_cast with a custom primary key' do keyboard = Keyboard.create keyboard.key_number = '10' assert_equal '10', keyboard.id_before_type_cast @@ -161,8 +161,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal '10', keyboard.read_attribute_before_type_cast(:key_number) end - # Syck calls respond_to? before actually calling initialize - def test_respond_to_with_allocated_object + # Syck calls respond_to? before actually calling initialize. + test 'respond_to? with an allocated object' do klass = Class.new(ActiveRecord::Base) do self.table_name = 'topics' end @@ -174,31 +174,32 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_respond_to topic, :title end - # IRB inspects the return value of "MyModel.allocate". - def test_allocated_object_can_be_inspected + # IRB inspects the return value of MyModel.allocate. + test 'allocated objects can be inspected' do topic = Topic.allocate assert_equal "#<Topic not initialized>", topic.inspect end - def test_array_content + test 'array content' do + content = %w( one two three ) topic = Topic.new - topic.content = %w( one two three ) + topic.content = content topic.save - assert_equal(%w( one two three ), Topic.find(topic.id).content) + assert_equal content, Topic.find(topic.id).content end - def test_read_attributes_before_type_cast - category = Category.new({:name=>"Test category", :type => nil}) - category_attrs = {"name"=>"Test category", "id" => nil, "type" => nil, "categorizations_count" => nil} - assert_equal category_attrs , category.attributes_before_type_cast + test 'read attributes_before_type_cast' do + category = Category.new(name: 'Test category', type: nil) + category_attrs = { 'name' => 'Test category', 'id' => nil, 'type' => nil, 'categorizations_count' => nil } + assert_equal category_attrs, category.attributes_before_type_cast end if current_adapter?(:Mysql2Adapter) - def test_read_attributes_before_type_cast_on_boolean + test 'read attributes_before_type_cast on a boolean' do bool = Boolean.create!({ "value" => false }) - if RUBY_PLATFORM =~ /java/ - # JRuby will return the value before typecast as string + if RUBY_PLATFORM.include?('java') + # JRuby will return the value before typecast as string. assert_equal "0", bool.reload.attributes_before_type_cast["value"] else assert_equal 0, bool.reload.attributes_before_type_cast["value"] @@ -206,7 +207,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_read_attributes_before_type_cast_on_datetime + test 'read attributes_before_type_cast on a datetime' do in_time_zone "Pacific Time (US & Canada)" do record = @target.new @@ -221,7 +222,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_read_attributes_after_type_cast_on_datetime + test 'read attributes_after_type_cast on a date' do tz = "Pacific Time (US & Canada)" in_time_zone tz do @@ -242,7 +243,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_hash_content + test 'hash content' do topic = Topic.new topic.content = { "one" => 1, "two" => 2 } topic.save @@ -256,7 +257,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal 3, Topic.find(topic.id).content["three"] end - def test_update_array_content + test 'update array content' do topic = Topic.new topic.content = %w( one two three ) @@ -270,14 +271,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal(%w( one two three four five ), topic.content) end - def test_case_sensitive_attributes_hash - # DB2 is not case-sensitive + test 'case-sensitive attributes hash' do + # DB2 is not case-sensitive. return true if current_adapter?(:DB2Adapter) assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.first.attributes end - def test_attributes_without_primary_key + test 'attributes without primary key' do klass = Class.new(ActiveRecord::Base) do self.table_name = 'developers_projects' end @@ -286,9 +287,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_not klass.new.has_attribute?('id') end - def test_hashes_not_mangled - new_topic = { :title => "New Topic" } - new_topic_values = { :title => "AnotherTopic" } + test 'hashes are not mangled' do + new_topic = { title: 'New Topic' } + new_topic_values = { title: 'AnotherTopic' } topic = Topic.new(new_topic) assert_equal new_topic[:title], topic.title @@ -297,13 +298,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal new_topic_values[:title], topic.title end - def test_create_through_factory - topic = Topic.create("title" => "New Topic") + test 'create through factory' do + topic = Topic.create(title: 'New Topic') topicReloaded = Topic.find(topic.id) assert_equal(topic, topicReloaded) end - def test_write_attribute + test 'write_attribute' do topic = Topic.new topic.send(:write_attribute, :title, "Still another topic") assert_equal "Still another topic", topic.title @@ -318,7 +319,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "Still another topic: part 4", topic.title end - def test_read_attribute + test 'read_attribute' do topic = Topic.new topic.title = "Don't change the topic" assert_equal "Don't change the topic", topic.read_attribute("title") @@ -328,7 +329,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "Don't change the topic", topic[:title] end - def test_read_attribute_raises_missing_attribute_error_when_not_exists + test 'read_attribute raises ActiveModel::MissingAttributeError when the attribute does not exist' do computer = Computer.select('id').first assert_raises(ActiveModel::MissingAttributeError) { computer[:developer] } assert_raises(ActiveModel::MissingAttributeError) { computer[:extendedWarranty] } @@ -336,7 +337,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_nothing_raised { computer[:developer] = 'Hello!' } end - def test_read_attribute_when_false + test 'read_attribute when false' do topic = topics(:first) topic.approved = false assert !topic.approved?, "approved should be false" @@ -344,7 +345,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert !topic.approved?, "approved should be false" end - def test_read_attribute_when_true + test 'read_attribute when true' do topic = topics(:first) topic.approved = true assert topic.approved?, "approved should be true" @@ -352,7 +353,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert topic.approved?, "approved should be true" end - def test_read_write_boolean_attribute + test 'boolean attributes writing and reading' do topic = Topic.new topic.approved = "false" assert !topic.approved?, "approved should be false" @@ -367,7 +368,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert topic.approved?, "approved should be true" end - def test_overridden_write_attribute + test 'overridden write_attribute' do topic = Topic.new def topic.write_attribute(attr_name, value) super(attr_name, value.downcase) @@ -386,7 +387,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "yet another topic: part 4", topic.title end - def test_overridden_read_attribute + test 'overridden read_attribute' do topic = Topic.new topic.title = "Stop changing the topic" def topic.read_attribute(attr_name) @@ -400,40 +401,40 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "STOP CHANGING THE TOPIC", topic[:title] end - def test_read_overridden_attribute - topic = Topic.new(:title => 'a') + test 'read overridden attribute' do + topic = Topic.new(title: 'a') def topic.title() 'b' end assert_equal 'a', topic[:title] end - def test_query_attribute_string + test 'string attribute predicate' do [nil, "", " "].each do |value| - assert_equal false, Topic.new(:author_name => value).author_name? + assert_equal false, Topic.new(author_name: value).author_name? end - assert_equal true, Topic.new(:author_name => "Name").author_name? + assert_equal true, Topic.new(author_name: 'Name').author_name? end - def test_query_attribute_number - [nil, 0, "0"].each do |value| - assert_equal false, Developer.new(:salary => value).salary? + test 'number attribute predicate' do + [nil, 0, '0'].each do |value| + assert_equal false, Developer.new(salary: value).salary? end - assert_equal true, Developer.new(:salary => 1).salary? - assert_equal true, Developer.new(:salary => "1").salary? + assert_equal true, Developer.new(salary: 1).salary? + assert_equal true, Developer.new(salary: '1').salary? end - def test_query_attribute_boolean + test 'boolean attribute predicate' do [nil, "", false, "false", "f", 0].each do |value| - assert_equal false, Topic.new(:approved => value).approved? + assert_equal false, Topic.new(approved: value).approved? end [true, "true", "1", 1].each do |value| - assert_equal true, Topic.new(:approved => value).approved? + assert_equal true, Topic.new(approved: value).approved? end end - def test_query_attribute_with_custom_fields + test 'custom field attribute predicate' do object = Company.find_by_sql(<<-SQL).first SELECT c1.*, c2.type as string_value, c2.rating as int_value FROM companies c1, companies c2 @@ -454,23 +455,23 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert !object.int_value? end - def test_non_attribute_access_and_assignment + test 'non-attribute read and write' do topic = Topic.new assert !topic.respond_to?("mumbo") assert_raise(NoMethodError) { topic.mumbo } assert_raise(NoMethodError) { topic.mumbo = 5 } end - def test_undeclared_attribute_method_does_not_affect_respond_to_and_method_missing - topic = @target.new(:title => 'Budget') + test 'undeclared attribute method does not affect respond_to? and method_missing' do + topic = @target.new(title: 'Budget') assert topic.respond_to?('title') assert_equal 'Budget', topic.title assert !topic.respond_to?('title_hello_world') assert_raise(NoMethodError) { topic.title_hello_world } end - def test_declared_prefixed_attribute_method_affects_respond_to_and_method_missing - topic = @target.new(:title => 'Budget') + test 'declared prefixed attribute method affects respond_to? and method_missing' do + topic = @target.new(title: 'Budget') %w(default_ title_).each do |prefix| @target.class_eval "def #{prefix}attribute(*args) args end" @target.attribute_method_prefix prefix @@ -483,11 +484,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_declared_suffixed_attribute_method_affects_respond_to_and_method_missing + test 'declared suffixed attribute method affects respond_to? and method_missing' do %w(_default _title_default _it! _candidate= able?).each do |suffix| @target.class_eval "def attribute#{suffix}(*args) args end" @target.attribute_method_suffix suffix - topic = @target.new(:title => 'Budget') + topic = @target.new(title: 'Budget') meth = "title#{suffix}" assert topic.respond_to?(meth) @@ -497,11 +498,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_declared_affixed_attribute_method_affects_respond_to_and_method_missing + test 'declared affixed attribute method affects respond_to? and method_missing' do [['mark_', '_for_update'], ['reset_', '!'], ['default_', '_value?']].each do |prefix, suffix| @target.class_eval "def #{prefix}attribute#{suffix}(*args) args end" - @target.attribute_method_affix({ :prefix => prefix, :suffix => suffix }) - topic = @target.new(:title => 'Budget') + @target.attribute_method_affix(prefix: prefix, suffix: suffix) + topic = @target.new(title: 'Budget') meth = "#{prefix}title#{suffix}" assert topic.respond_to?(meth) @@ -511,38 +512,38 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_should_unserialize_attributes_for_frozen_records - myobj = {:value1 => :value2} - topic = Topic.create("content" => myobj) + test 'should unserialize attributes for frozen records' do + myobj = { value1: :value2 } + topic = Topic.create(content: myobj) topic.freeze assert_equal myobj, topic.content end - def test_typecast_attribute_from_select_to_false - Topic.create(:title => 'Budget') - # Oracle does not support boolean expressions in SELECT + test 'typecast attribute from select to false' do + Topic.create(title: 'Budget') + # Oracle does not support boolean expressions in SELECT. if current_adapter?(:OracleAdapter, :FbAdapter) - topic = Topic.all.merge!(:select => "topics.*, 0 as is_test").first + topic = Topic.all.merge!(select: 'topics.*, 0 as is_test').first else - topic = Topic.all.merge!(:select => "topics.*, 1=2 as is_test").first + topic = Topic.all.merge!(select: 'topics.*, 1=2 as is_test').first end assert !topic.is_test? end - def test_typecast_attribute_from_select_to_true - Topic.create(:title => 'Budget') - # Oracle does not support boolean expressions in SELECT + test 'typecast attribute from select to true' do + Topic.create(title: 'Budget') + # Oracle does not support boolean expressions in SELECT. if current_adapter?(:OracleAdapter, :FbAdapter) - topic = Topic.all.merge!(:select => "topics.*, 1 as is_test").first + topic = Topic.all.merge!(select: 'topics.*, 1 as is_test').first else - topic = Topic.all.merge!(:select => "topics.*, 2=2 as is_test").first + topic = Topic.all.merge!(select: 'topics.*, 2=2 as is_test').first end assert topic.is_test? end - def test_raises_dangerous_attribute_error_when_defining_activerecord_method_in_model + test 'raises ActiveRecord::DangerousAttributeError when defining an AR method in a model' do %w(save create_or_update).each do |method| - klass = Class.new ActiveRecord::Base + klass = Class.new(ActiveRecord::Base) klass.class_eval "def #{method}() 'defined #{method}' end" assert_raise ActiveRecord::DangerousAttributeError do klass.instance_method_already_implemented?(method) @@ -550,7 +551,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_converted_values_are_returned_after_assignment + test 'converted values are returned after assignment' do developer = Developer.new(name: 1337, salary: "50000") assert_equal "50000", developer.salary_before_type_cast @@ -565,7 +566,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "1337", developer.name end - def test_write_nil_to_time_attributes + test 'write nil to time attribute' do in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.written_on = nil @@ -573,7 +574,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_write_time_to_date_attributes + test 'write time to date attribute' do in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.last_read = Time.utc(2010, 1, 1, 10) @@ -581,7 +582,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_time_attributes_are_retrieved_in_current_time_zone + test 'time attributes are retrieved in the current time zone' do in_time_zone "Pacific Time (US & Canada)" do utc_time = Time.utc(2008, 1, 1) record = @target.new @@ -593,7 +594,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_attribute_to_utc + test 'setting a time zone-aware attribute to UTC' do in_time_zone "Pacific Time (US & Canada)" do utc_time = Time.utc(2008, 1, 1) record = @target.new @@ -604,7 +605,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_attribute_in_other_time_zone + test 'setting time zone-aware attribute in other time zone' do utc_time = Time.utc(2008, 1, 1) cst_time = utc_time.in_time_zone("Central Time (US & Canada)") in_time_zone "Pacific Time (US & Canada)" do @@ -616,18 +617,18 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_read_attribute + test 'setting time zone-aware read attribute' do utc_time = Time.utc(2008, 1, 1) cst_time = utc_time.in_time_zone("Central Time (US & Canada)") in_time_zone "Pacific Time (US & Canada)" do - record = @target.create(:written_on => cst_time).reload + record = @target.create(written_on: cst_time).reload assert_equal utc_time, record[:written_on] assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record[:written_on].time_zone assert_equal Time.utc(2007, 12, 31, 16), record[:written_on].time end end - def test_setting_time_zone_aware_attribute_with_string + test 'setting time zone-aware attribute with a string' do utc_time = Time.utc(2008, 1, 1) (-11..13).each do |timezone_offset| time_string = utc_time.in_time_zone(timezone_offset).to_s @@ -641,9 +642,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_time_zone_aware_attribute_saved + test 'time zone-aware attribute saved' do in_time_zone 1 do - record = @target.create(:written_on => '2012-02-20 10:00') + record = @target.create(written_on: '2012-02-20 10:00') record.written_on = '2012-02-20 09:00' record.save @@ -651,7 +652,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_attribute_to_blank_string_returns_nil + test 'setting a time zone-aware attribute to a blank string returns nil' do in_time_zone "Pacific Time (US & Canada)" do record = @target.new record.written_on = ' ' @@ -660,7 +661,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_attribute_interprets_time_zone_unaware_string_in_time_zone + test 'setting a time zone-aware attribute interprets time zone-unaware string in time zone' do time_string = 'Tue Jan 01 00:00:00 2008' (-11..13).each do |timezone_offset| in_time_zone timezone_offset do @@ -673,7 +674,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_datetime_in_current_time_zone + test 'setting a time zone-aware datetime in the current time zone' do utc_time = Time.utc(2008, 1, 1) in_time_zone "Pacific Time (US & Canada)" do record = @target.new @@ -684,7 +685,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_yaml_dumping_record_with_time_zone_aware_attribute + test 'YAML dumping a record with time zone-aware attribute' do in_time_zone "Pacific Time (US & Canada)" do record = Topic.new(id: 1) record.written_on = "Jan 01 00:00:00 2014" @@ -692,7 +693,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_time_in_current_time_zone + test 'setting a time zone-aware time in the current time zone' do in_time_zone "Pacific Time (US & Canada)" do record = @target.new time_string = "10:00:00" @@ -707,7 +708,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_setting_time_zone_aware_time_with_dst + test 'setting a time zone-aware time with DST' do in_time_zone "Pacific Time (US & Canada)" do current_time = Time.zone.local(2014, 06, 15, 10) record = @target.new(bonus_time: current_time) @@ -721,7 +722,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_removing_time_zone_aware_types + test 'removing time zone-aware types' do with_time_zone_aware_types(:datetime) do in_time_zone "Pacific Time (US & Canada)" do record = @target.new(bonus_time: "10:00:00") @@ -733,14 +734,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_time_zone_aware_attributes_dont_recurse_infinitely_on_invalid_values + test 'time zone-aware attributes do not recurse infinitely on invalid values' do in_time_zone "Pacific Time (US & Canada)" do record = @target.new(bonus_time: []) assert_equal nil, record.bonus_time end end - def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable + test 'setting a time_zone_conversion_for_attributes should write the value on a class variable' do Topic.skip_time_zone_conversion_for_attributes = [:field_a] Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b] @@ -748,44 +749,44 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal [:field_b], Minimalistic.skip_time_zone_conversion_for_attributes end - def test_read_attributes_respect_access_control - privatize("title") + test 'attribute readers respect access control' do + privatize('title') - topic = @target.new(:title => "The pros and cons of programming naked.") + topic = @target.new(title: 'The pros and cons of programming naked.') assert !topic.respond_to?(:title) exception = assert_raise(NoMethodError) { topic.title } - assert exception.message.include?("private method") + assert exception.message.include?('private method') assert_equal "I'm private", topic.send(:title) end - def test_write_attributes_respect_access_control - privatize("title=(value)") + test 'attribute writers respect access control' do + privatize('title=(value)') topic = @target.new assert !topic.respond_to?(:title=) - exception = assert_raise(NoMethodError) { topic.title = "Pants"} - assert exception.message.include?("private method") - topic.send(:title=, "Very large pants") + exception = assert_raise(NoMethodError) { topic.title = 'Pants' } + assert exception.message.include?('private method') + topic.send(:title=, 'Very large pants') end - def test_question_attributes_respect_access_control - privatize("title?") + test 'attribute predicates respect access control' do + privatize('title?') - topic = @target.new(:title => "Isaac Newton's pants") + topic = @target.new(title: "Isaac Newton's pants") assert !topic.respond_to?(:title?) exception = assert_raise(NoMethodError) { topic.title? } - assert exception.message.include?("private method") + assert exception.message.include?('private method') assert topic.send(:title?) end - def test_bulk_update_respects_access_control - privatize("title=(value)") + test 'bulk updates respect access control' do + privatize('title=(value)') - assert_raise(ActiveRecord::UnknownAttributeError) { @target.new(:title => "Rants about pants") } - assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } } + assert_raise(ActiveRecord::UnknownAttributeError) { @target.new(title: 'Rants about pants') } + assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { title: 'Ants in pants' } } end - def test_bulk_update_raise_unknown_attribute_error + test 'bulk update raises ActiveRecord::UnknownAttributeError' do error = assert_raises(ActiveRecord::UnknownAttributeError) { Topic.new(hello: "world") } @@ -794,20 +795,20 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "unknown attribute 'hello' for Topic.", error.message end - def test_methods_override_in_multi_level_subclass + test 'method overrides in multi-level subclasses' do klass = Class.new(Developer) do def name "dev:#{read_attribute(:name)}" end end - 2.times { klass = Class.new klass } + 2.times { klass = Class.new(klass) } dev = klass.new(name: 'arthurnn') dev.save! assert_equal 'dev:arthurnn', dev.reload.name end - def test_global_methods_are_overwritten + test 'global methods are overwritten' do klass = Class.new(ActiveRecord::Base) do self.table_name = 'computers' end @@ -817,8 +818,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_nil computer.system end - def test_global_methods_are_overwritten_when_subclassing - klass = Class.new(ActiveRecord::Base) { self.abstract_class = true } + test 'global methods are overwritten when subclassing' do + klass = Class.new(ActiveRecord::Base) do + self.abstract_class = true + end subklass = Class.new(klass) do self.table_name = 'computers' @@ -830,7 +833,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_nil computer.system end - def test_instance_method_should_be_defined_on_the_base_class + test 'instance methods should be defined on the base class' do subklass = Class.new(Topic) Topic.define_attribute_methods @@ -846,14 +849,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert subklass.method_defined?(:id), "subklass is missing id method" end - def test_read_attribute_with_nil_should_not_asplode - assert_equal nil, Topic.new.read_attribute(nil) + test 'read_attribute with nil should not asplode' do + assert_nil Topic.new.read_attribute(nil) end # If B < A, and A defines an accessor for 'foo', we don't want to override # that by defining a 'foo' method in the generated methods module for B. # (That module will be inserted between the two, e.g. [B, <GeneratedAttributes>, A].) - def test_inherited_custom_accessors + test 'inherited custom accessors' do klass = new_topic_like_ar_class do self.abstract_class = true def title; "omg"; end @@ -869,7 +872,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "lol", topic.author_name end - def test_inherited_custom_accessors_with_reserved_names + test 'inherited custom accessors with reserved names' do klass = Class.new(ActiveRecord::Base) do self.table_name = 'computers' self.abstract_class = true @@ -887,7 +890,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal 99, computer.developer end - def test_on_the_fly_super_invokable_generated_attribute_methods_via_method_missing + test 'on_the_fly_super_invokable_generated_attribute_methods_via_method_missing' do klass = new_topic_like_ar_class do def title super + '!' @@ -898,7 +901,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal real_topic.title + '!', klass.find(real_topic.id).title end - def test_on_the_fly_super_invokable_generated_predicate_attribute_methods_via_method_missing + test 'on-the-fly super-invokable generated attribute predicates via method_missing' do klass = new_topic_like_ar_class do def title? !super @@ -909,7 +912,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal !real_topic.title?, klass.find(real_topic.id).title? end - def test_calling_super_when_parent_does_not_define_method_raises_error + test 'calling super when the parent does not define method raises NoMethodError' do klass = new_topic_like_ar_class do def some_method_that_is_not_on_super super @@ -921,38 +924,38 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_attribute_method? + test 'attribute_method?' do assert @target.attribute_method?(:title) assert @target.attribute_method?(:title=) assert_not @target.attribute_method?(:wibble) end - def test_attribute_method_returns_false_if_table_does_not_exist + test 'attribute_method? returns false if the table does not exist' do @target.table_name = 'wibble' assert_not @target.attribute_method?(:title) end - def test_attribute_names_on_new_record + test 'attribute_names on a new record' do model = @target.new assert_equal @target.column_names, model.attribute_names end - def test_attribute_names_on_queried_record + test 'attribute_names on a queried record' do model = @target.last! assert_equal @target.column_names, model.attribute_names end - def test_attribute_names_with_custom_select + test 'attribute_names with a custom select' do model = @target.select('id').last! assert_equal ['id'], model.attribute_names - # Sanity check, make sure other columns exist + # Sanity check, make sure other columns exist. assert_not_equal ['id'], @target.column_names end - def test_came_from_user + test 'came_from_user?' do model = @target.first assert_not model.id_came_from_user? @@ -960,7 +963,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert model.id_came_from_user? end - def test_accessed_fields + test 'accessed_fields' do model = @target.first assert_equal [], model.accessed_fields 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/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index 4f70ae3a1d..8a722b4f22 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -31,7 +31,7 @@ class CallbackDeveloper < ActiveRecord::Base end ActiveRecord::Callbacks::CALLBACKS.each do |callback_method| - next if callback_method.to_s =~ /^around_/ + next if callback_method.to_s.start_with?('around_') define_callback_method(callback_method) ActiveSupport::Deprecation.silence { send(callback_method, callback_string(callback_method)) } send(callback_method, callback_proc(callback_method)) diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index f9794518c7..3c58b6ad09 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -604,7 +604,7 @@ class DirtyTest < ActiveRecord::TestCase jon = Person.create! first_name: 'Jon' end - assert ActiveRecord::SQLCounter.log_all.none? { |sql| sql =~ /followers_count/ } + assert ActiveRecord::SQLCounter.log_all.none? { |sql| sql.include?('followers_count') } jon.reload assert_equal 'Jon', jon.first_name 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 dcd09b6973..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 @@ -201,7 +202,7 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_order_to_unscope_reordering scope = DeveloperOrderedBySalary.order('salary DESC, name ASC').reverse_order.unscope(:order) - assert !(scope.to_sql =~ /order/i) + assert !/order/i.match?(scope.to_sql) end def test_unscope_reverse_order diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index c8adc21bbc..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 @@ -115,7 +116,7 @@ module ActiveRecord return if 'CACHE' == values[:name] self.class.log_all << sql - self.class.log << sql unless ignore =~ sql + self.class.log << sql unless ignore.match?(sql) end end diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 24713f722a..030ad73621 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -88,7 +88,7 @@ _SQL FOR EACH ROW EXECUTE PROCEDURE partitioned_insert_trigger(); _SQL rescue ActiveRecord::StatementInvalid => e - if e.message =~ /language "plpgsql" does not exist/ + if e.message.include?('language "plpgsql" does not exist') execute "CREATE LANGUAGE 'plpgsql';" retry else diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 3749dda9fc..b39ef7bfb9 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,30 @@ +* Introduce `not_in?` on `Object`. + + As an opposite method for `in?`, `not_in?` provides equivalent support for exclusion. This turns this: + + [1,2].exclude?(user_id) + + ...into this: + + user_id.not_in?([1,2]) + + *Jon McCartie* + +* Defines `Regexp.match?` for Ruby versions prior to 2.4. The predicate + has the same interface, but it does not have the performance boost. Its + purpose is to be able to write 2.4 compatible code. + + *Xavier Noria* + +* Allow MessageEncryptor to take advantage of authenticated encryption modes. + + AEAD modes like `aes-256-gcm` provide both confidentiality and data + authenticity, eliminating the need to use MessageVerifier to check if the + encrypted data has been tampered with. This speeds up encryption/decryption + and results in shorter cipher text. + + *Bart de Water* + * Introduce `assert_changes` and `assert_no_changes`. `assert_changes` is a more general `assert_difference` that works with any @@ -46,7 +73,7 @@ *John Gesimondo* -* `travel/travel_to` travel time helpers, now raise on nested calls, +* `travel/travel_to` travel time helpers, now raise on nested calls, as this can lead to confusing time stubbing. Instead of: @@ -60,7 +87,7 @@ preferred way to achieve above is: - travel 2.days do + travel 2.days do # 2 days from today end diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index 8256c325af..c2c99459f2 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -1,6 +1,7 @@ require 'active_support/concern' require 'active_support/ordered_options' require 'active_support/core_ext/array/extract_options' +require 'active_support/core_ext/regexp' module ActiveSupport # Configurable provides a <tt>config</tt> method to store and retrieve @@ -107,7 +108,7 @@ module ActiveSupport options = names.extract_options! names.each do |name| - raise NameError.new('invalid config attribute name') unless name =~ /\A[_A-Za-z]\w*\z/ + raise NameError.new('invalid config attribute name') unless /\A[_A-Za-z]\w*\z/.match?(name) reader, reader_line = "def #{name}; config.#{name}; end", __LINE__ writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__ @@ -145,4 +146,3 @@ module ActiveSupport end end end - diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 567ac825e9..8f761a8f60 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/array/extract_options' +require 'active_support/core_ext/regexp' # Extends the module object with class/module and instance accessors for # class/module attributes, just like the native attr* accessors for instance @@ -53,7 +54,7 @@ class Module def mattr_reader(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /\A[_A-Za-z]\w*\z/ + raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym) class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@#{sym} = nil unless defined? @@#{sym} @@ -119,7 +120,7 @@ class Module def mattr_writer(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /\A[_A-Za-z]\w*\z/ + raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym) class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@#{sym} = nil unless defined? @@#{sym} diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb index 045668c207..7015333f7c 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb @@ -1,9 +1,10 @@ require 'active_support/core_ext/array/extract_options' +require 'active_support/core_ext/regexp' # Extends the module object with class/module and instance accessors for # class/module attributes, just like the native attr* accessors for instance # attributes, but does so on a per-thread basis. -# +# # So the values are scoped within the Thread.current space under the class name # of the module. class Module @@ -37,7 +38,7 @@ class Module options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) class_eval(<<-EOS, __FILE__, __LINE__ + 1) def self.#{sym} Thread.current[:"attr_#{name}_#{sym}"] @@ -76,7 +77,7 @@ class Module def thread_mattr_writer(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) class_eval(<<-EOS, __FILE__, __LINE__ + 1) def self.#{sym}=(obj) Thread.current[:"attr_#{name}_#{sym}"] = obj diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 3f6e8bd26c..946a0f4177 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -1,4 +1,5 @@ require 'set' +require 'active_support/core_ext/regexp' class Module # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+ @@ -153,7 +154,7 @@ class Module raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).' end - if prefix == true && to =~ /^[^a-z_]/ + if prefix == true && /^[^a-z_]/.match?(to) raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.' end @@ -173,7 +174,7 @@ class Module methods.each do |method| # Attribute writer methods only accept one argument. Makes sure []= # methods still accept two arguments. - definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block' + definition = /[^\]]=$/.match?(method) ? 'arg' : '*args, &block' # The following generated method calls the target exactly once, storing # the returned value in a dummy variable. diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index f4f9152d6a..a2c5a472f5 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/object/deep_dup' require 'active_support/core_ext/object/try' require 'active_support/core_ext/object/inclusion' +require 'active_support/core_ext/object/exclusion' require 'active_support/core_ext/object/conversions' require 'active_support/core_ext/object/instance_variables' diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index cb74bad73e..045731d752 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/regexp' + class Object # An object is blank if it's false, empty, or a whitespace string. # For example, +false+, '', ' ', +nil+, [], and {} are all blank. @@ -115,7 +117,7 @@ class String # The regexp that matches blank strings is expensive. For the case of empty # strings we can speed up this method (~3.5x) with an empty? call. The # penalty for the rest of strings is marginal. - empty? || BLANK_RE === self + empty? || BLANK_RE.match?(self) end end diff --git a/activesupport/lib/active_support/core_ext/object/exclusion.rb b/activesupport/lib/active_support/core_ext/object/exclusion.rb new file mode 100644 index 0000000000..58dfb649e5 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/object/exclusion.rb @@ -0,0 +1,15 @@ +class Object + # Returns true if this object is excluded in the argument. Argument must be + # any object which responds to +#include?+. Usage: + # + # characters = ["Konata", "Kagami", "Tsukasa"] + # "MoshiMoshi".not_in?(characters) # => true + # + # This will throw an +ArgumentError+ if the argument doesn't respond + # to +#include?+. + def not_in?(another_object) + !another_object.include?(self) + rescue NoMethodError + raise ArgumentError.new("The parameter passed to #not_in? must respond to #include?") + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/regexp.rb b/activesupport/lib/active_support/core_ext/regexp.rb index 784145f5fb..062d568228 100644 --- a/activesupport/lib/active_support/core_ext/regexp.rb +++ b/activesupport/lib/active_support/core_ext/regexp.rb @@ -2,4 +2,8 @@ class Regexp #:nodoc: def multiline? options & MULTILINE == MULTILINE end + + def match?(string, pos=0) + !!match(string, pos) + end unless //.respond_to?(:match?) end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 0cb2d4d22e..a83c8054ec 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -1,4 +1,5 @@ require 'active_support/inflector/methods' +require 'active_support/core_ext/regexp' module ActiveSupport class Deprecation @@ -10,7 +11,7 @@ module ActiveSupport super end - instance_methods.each { |m| undef_method m unless m =~ /^__|^object_id$/ } + instance_methods.each { |m| undef_method m unless /^__|^object_id$/.match?(m) } # Don't give a deprecation warning on inspect since test/unit and error # logs rely on it for diagnostics. diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb index 07af58ad99..12515633fc 100644 --- a/activesupport/lib/active_support/duration/iso8601_parser.rb +++ b/activesupport/lib/active_support/duration/iso8601_parser.rb @@ -1,4 +1,5 @@ require 'strscan' +require 'active_support/core_ext/regexp' module ActiveSupport class Duration @@ -85,7 +86,7 @@ module ActiveSupport # Parses number which can be a float with either comma or period. def number - scanner[1] =~ PERIOD_OR_COMMA ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i + PERIOD_OR_COMMA.match?(scanner[1]) ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i end def scan(pattern) diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index f14b8b81b7..34131a704a 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,4 +1,5 @@ require 'active_support/inflections' +require 'active_support/core_ext/regexp' module ActiveSupport # The Inflector transforms words from singular to plural, class names to table @@ -87,7 +88,7 @@ module ActiveSupport # # camelize(underscore('SSLError')) # => "SslError" def underscore(camel_cased_word) - return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/ + return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word) word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze) word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" } word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze) @@ -313,7 +314,7 @@ module ActiveSupport raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) || e.name.to_s == camel_cased_word.to_s) rescue ArgumentError => e - raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/ + raise unless /not missing constant #{const_regexp(camel_cased_word)}!$/.match?(e.message) end # Returns the suffix that should be added to a number to denote the position diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 721efea789..87efe117c5 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -1,6 +1,7 @@ require 'openssl' require 'base64' require 'active_support/core_ext/array/extract_options' +require 'active_support/message_verifier' module ActiveSupport # MessageEncryptor is a simple way to encrypt values which get stored @@ -28,6 +29,16 @@ module ActiveSupport end end + module NullVerifier #:nodoc: + def self.verify(value) + value + end + + def self.generate(value) + value + end + end + class InvalidMessage < StandardError; end OpenSSLCipherError = OpenSSL::Cipher::CipherError @@ -40,7 +51,8 @@ module ActiveSupport # Options: # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'. - # * <tt>:digest</tt> - String of digest to use for signing. Default is +SHA1+. + # * <tt>:digest</tt> - String of digest to use for signing. Default is + # +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'. # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+. def initialize(secret, *signature_key_or_options) options = signature_key_or_options.extract_options! @@ -48,7 +60,8 @@ module ActiveSupport @secret = secret @sign_secret = sign_secret @cipher = options[:cipher] || 'aes-256-cbc' - @verifier = MessageVerifier.new(@sign_secret || @secret, digest: options[:digest] || 'SHA1', serializer: NullSerializer) + @digest = options[:digest] || 'SHA1' unless aead_mode? + @verifier = resolve_verifier @serializer = options[:serializer] || Marshal end @@ -73,20 +86,28 @@ module ActiveSupport # Rely on OpenSSL for the initialization vector iv = cipher.random_iv + cipher.auth_data = "" if aead_mode? encrypted_data = cipher.update(@serializer.dump(value)) encrypted_data << cipher.final - "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" + blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" + blob << "--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode? + blob end def _decrypt(encrypted_message) cipher = new_cipher - encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)} + encrypted_data, iv, auth_tag = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)} + raise InvalidMessage if aead_mode? && auth_tag.bytes.length != 16 cipher.decrypt cipher.key = @secret cipher.iv = iv + if aead_mode? + cipher.auth_tag = auth_tag + cipher.auth_data = "" + end decrypted_data = cipher.update(encrypted_data) decrypted_data << cipher.final @@ -103,5 +124,17 @@ module ActiveSupport def verifier @verifier end + + def aead_mode? + @aead_mode ||= new_cipher.authenticated? + end + + def resolve_verifier + if aead_mode? + NullVerifier + else + MessageVerifier.new(@sign_secret || @secret, digest: @digest, serializer: NullSerializer) + end + end end end diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 4c3deffe6e..a421c92441 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -83,7 +83,7 @@ module ActiveSupport data = signed_message.split("--".freeze)[0] @serializer.load(decode(data)) rescue ArgumentError => argument_error - return if argument_error.message =~ %r{invalid base64} + return if argument_error.message.include?('invalid base64') raise end end diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 707cf200b5..83db68a66f 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -2,6 +2,7 @@ require 'active_support/json' require 'active_support/core_ext/string/access' require 'active_support/core_ext/string/behavior' require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/regexp' module ActiveSupport #:nodoc: module Multibyte #:nodoc: @@ -56,7 +57,7 @@ module ActiveSupport #:nodoc: # Forward all undefined methods to the wrapped string. def method_missing(method, *args, &block) result = @wrapped_string.__send__(method, *args, &block) - if method.to_s =~ /!$/ + if /!$/.match?(method) self if result else result.kind_of?(String) ? chars(result) : result diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb index 5dfa14eeba..643b32939d 100644 --- a/activesupport/lib/active_support/testing/deprecation.rb +++ b/activesupport/lib/active_support/testing/deprecation.rb @@ -1,4 +1,5 @@ require 'active_support/deprecation' +require 'active_support/core_ext/regexp' module ActiveSupport module Testing @@ -8,7 +9,7 @@ module ActiveSupport assert !warnings.empty?, "Expected a deprecation warning within the block but received none" if match match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp) - assert warnings.any? { |w| w =~ match }, "No deprecation warning matched #{match}: #{warnings.join(', ')}" + assert warnings.any? { |w| match.match?(w) }, "No deprecation warning matched #{match}: #{warnings.join(', ')}" end result end diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb index 94751bbc04..2b6162f553 100644 --- a/activesupport/lib/active_support/xml_mini/jdom.rb +++ b/activesupport/lib/active_support/xml_mini/jdom.rb @@ -1,4 +1,4 @@ -raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFORM =~ /java/ +raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFORM.include?('java') require 'jruby' include Java diff --git a/activesupport/test/clean_backtrace_test.rb b/activesupport/test/clean_backtrace_test.rb index 05580352a9..14cf65ef2d 100644 --- a/activesupport/test/clean_backtrace_test.rb +++ b/activesupport/test/clean_backtrace_test.rb @@ -26,7 +26,7 @@ end class BacktraceCleanerSilencerTest < ActiveSupport::TestCase def setup @bc = ActiveSupport::BacktraceCleaner.new - @bc.add_silencer { |line| line =~ /mongrel/ } + @bc.add_silencer { |line| line.include?('mongrel') } end test "backtrace should not contain lines that match the silencer" do @@ -44,8 +44,8 @@ end class BacktraceCleanerMultipleSilencersTest < ActiveSupport::TestCase def setup @bc = ActiveSupport::BacktraceCleaner.new - @bc.add_silencer { |line| line =~ /mongrel/ } - @bc.add_silencer { |line| line =~ /yolo/ } + @bc.add_silencer { |line| line.include?('mongrel') } + @bc.add_silencer { |line| line.include?('yolo') } end test "backtrace should not contain lines that match the silencers" do @@ -66,7 +66,7 @@ class BacktraceCleanerFilterAndSilencerTest < ActiveSupport::TestCase def setup @bc = ActiveSupport::BacktraceCleaner.new @bc.add_filter { |line| line.gsub("/mongrel", "") } - @bc.add_silencer { |line| line =~ /mongrel/ } + @bc.add_silencer { |line| line.include?('mongrel') } end test "backtrace should not silence lines that has first had their silence hook filtered out" do diff --git a/activesupport/test/core_ext/object/exclusion_test.rb b/activesupport/test/core_ext/object/exclusion_test.rb new file mode 100644 index 0000000000..487c97d255 --- /dev/null +++ b/activesupport/test/core_ext/object/exclusion_test.rb @@ -0,0 +1,53 @@ +require 'abstract_unit' +require 'active_support/core_ext/object/exclusion' + +class NotInTest < ActiveSupport::TestCase + def test_not_in_array + assert 1.not_in?([2, 3]) + assert_not 2.not_in?([1,2]) + end + + def test_not_in_hash + h = { "a" => 100, "b" => 200 } + assert "z".not_in?(h) + assert_not "a".not_in?(h) + end + + def test_not_in_string + assert "ol".not_in?("hello") + assert_not "lo".not_in?("hello") + assert ?z.not_in?("hello") + end + + def test_not_in_range + assert 75.not_in?(1..50) + assert_not 25.not_in?(1..50) + end + + def test_not_in_set + s = Set.new([1,2]) + assert 3.not_in?(s) + assert_not 1.not_in?(s) + end + + module A + end + class B + include A + end + class C < B + end + class D + end + + def test_not_in_module + assert A.not_in?(D) + assert A.not_in?(A) + assert_not A.not_in?(B) + assert_not A.not_in?(C) + end + + def test_no_method_catching + assert_raise(ArgumentError) { 1.not_in?(1) } + end +end diff --git a/activesupport/test/core_ext/regexp_ext_test.rb b/activesupport/test/core_ext/regexp_ext_test.rb index c2398d31bd..d91e363085 100644 --- a/activesupport/test/core_ext/regexp_ext_test.rb +++ b/activesupport/test/core_ext/regexp_ext_test.rb @@ -7,4 +7,28 @@ class RegexpExtAccessTests < ActiveSupport::TestCase assert_equal false, //.multiline? assert_equal false, /(?m:)/.multiline? end + + # Based on https://github.com/ruby/ruby/blob/trunk/test/ruby/test_regexp.rb. + def test_match_p + /back(...)/ =~ 'backref' + # must match here, but not in a separate method, e.g., assert_send, + # to check if $~ is affected or not. + assert_equal false, //.match?(nil) + assert_equal true, //.match?("") + assert_equal true, /.../.match?(:abc) + assert_raise(TypeError) { /.../.match?(Object.new) } + assert_equal true, /b/.match?('abc') + assert_equal true, /b/.match?('abc', 1) + assert_equal true, /../.match?('abc', 1) + assert_equal true, /../.match?('abc', -2) + assert_equal false, /../.match?("abc", -4) + assert_equal false, /../.match?("abc", 4) + assert_equal true, /\z/.match?("") + assert_equal true, /\z/.match?("abc") + assert_equal true, /R.../.match?("Ruby") + assert_equal false, /R.../.match?("Ruby", 1) + assert_equal false, /P.../.match?("Ruby") + assert_equal 'backref', $& + assert_equal 'ref', $1 + end end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 5fc2e16336..8cb3b1a9bb 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -1,6 +1,7 @@ require 'securerandom' require 'abstract_unit' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/regexp' require 'active_support/json' require 'active_support/time' require 'time_zone_test_helpers' @@ -10,8 +11,11 @@ class TestJSONEncoding < ActiveSupport::TestCase include TimeZoneTestHelpers def sorted_json(json) - return json unless json =~ /^\{.*\}$/ - '{' + json[1..-2].split(',').sort.join(',') + '}' + if json.start_with?('{') && json.end_with?('}') + '{' + json[1..-2].split(',').sort.join(',') + '}' + else + json + end end JSONTest::EncodingTestCases.constants.each do |class_tests| @@ -19,8 +23,10 @@ class TestJSONEncoding < ActiveSupport::TestCase begin prev = ActiveSupport.use_standard_json_time_format - ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/ - ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/ + standard_class_tests = /Standard/.match?(class_tests) + + ActiveSupport.escape_html_entities_in_json = !standard_class_tests + ActiveSupport.use_standard_json_time_format = standard_class_tests JSONTest::EncodingTestCases.const_get(class_tests).each do |pair| assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first)) end diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index a1ff4c1d3e..5dfa187f36 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -70,6 +70,24 @@ class MessageEncryptorTest < ActiveSupport::TestCase assert_not_verified([iv, message] * bad_encoding_characters) end + def test_aead_mode_encryption + encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: 'aes-256-gcm') + message = encryptor.encrypt_and_sign(@data) + assert_equal @data, encryptor.decrypt_and_verify(message) + end + + def test_messing_with_aead_values_causes_failures + encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: 'aes-256-gcm') + text, iv, auth_tag = encryptor.encrypt_and_sign(@data).split("--") + assert_not_decrypted([iv, text, auth_tag] * "--") + assert_not_decrypted([munge(text), iv, auth_tag] * "--") + assert_not_decrypted([text, munge(iv), auth_tag] * "--") + assert_not_decrypted([text, iv, munge(auth_tag)] * "--") + assert_not_decrypted([munge(text), munge(iv), munge(auth_tag)] * "--") + assert_not_decrypted([text, iv] * "--") + assert_not_decrypted([text, iv, auth_tag[0..-2]] * "--") + end + private def assert_not_decrypted(value) diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb index c10133a7a6..50ec6442ff 100644 --- a/activesupport/test/multibyte_conformance_test.rb +++ b/activesupport/test/multibyte_conformance_test.rb @@ -87,7 +87,7 @@ class MultibyteConformanceTest < ActiveSupport::TestCase File.open(File.join(CACHE_DIR, UNIDATA_FILE), 'r') do | f | until f.eof? line = f.gets.chomp! - next if (line.empty? || line =~ /^\#/) + next if line.empty? || line.start_with?('#') cols, comment = line.split("#") cols = cols.split(";").map(&:strip).reject(&:empty?) diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb index 61943b1ab3..1d52a56971 100644 --- a/activesupport/test/multibyte_grapheme_break_conformance_test.rb +++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb @@ -36,7 +36,7 @@ class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase until f.eof? || (max_test_lines > 21 and lines > max_test_lines) lines += 1 line = f.gets.chomp! - next if (line.empty? || line =~ /^\#/) + next if line.empty? || line.start_with?('#') cols, comment = line.split("#") # Cluster breaks are represented by ÷ diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb index 77ed0ce6de..4b940a2054 100644 --- a/activesupport/test/multibyte_normalization_conformance_test.rb +++ b/activesupport/test/multibyte_normalization_conformance_test.rb @@ -91,7 +91,7 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase until f.eof? || (max_test_lines > 38 and lines > max_test_lines) lines += 1 line = f.gets.chomp! - next if (line.empty? || line =~ /^\#/) + next if line.empty? || line.start_with?('#') cols, comment = line.split("#") cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? } diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb index ed4de8aba2..34e213b718 100644 --- a/activesupport/test/xml_mini/jdom_engine_test.rb +++ b/activesupport/test/xml_mini/jdom_engine_test.rb @@ -1,4 +1,4 @@ -if RUBY_PLATFORM =~ /java/ +if RUBY_PLATFORM.include?('java') require 'abstract_unit' require 'active_support/xml_mini' require 'active_support/core_ext/hash/conversions' diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index e0b6f2f820..27478b21a0 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -2916,6 +2916,24 @@ end NOTE: Defined in `active_support/core_ext/regexp.rb`. +### `match?` + +Rails implements `Regexp#match?` for Ruby versions prior to 2.4: + +```ruby +/oo/.match?('foo') # => true +/oo/.match?('bar') # => false +/oo/.match?('foo', 1) # => true +``` + +The backport has the same interface and lack of side-effects in the caller like +not setting `$1` and friends, but it does not have the speed benefits. Its +purpose is to be able to write 2.4 compatible code. Rails itself uses this +predicate internally for example. + +Active Support defines `Regexp#match?` only if not present, so code running +under 2.4 or later does run the original one and gets the performance boost. + Extensions to `Range` --------------------- diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 6c734c1d78..cc84ecb216 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -198,11 +198,11 @@ render "comments/comments" render 'comments/comments' render('comments/comments') -render "header" => render("comments/header") +render "header" translates to render("comments/header") -render(@topic) => render("topics/topic") -render(topics) => render("topics/topic") -render(message.topics) => render("topics/topic") +render(@topic) translates to render("topics/topic") +render(topics) translates to render("topics/topic") +render(message.topics) translates to render("topics/topic") ``` On the other hand, some calls need to be changed to make caching work properly. @@ -270,7 +270,7 @@ simply be explicit in a comment, like: Sometimes you need to cache a particular value or query result instead of caching view fragments. Rails' caching mechanism works great for storing __any__ kind of information. -The most efficient way to implement low-level caching is using the `Rails.cache.fetch` method. This method does both reading and writing to the cache. When passed only a single argument, the key is fetched and value from the cache is returned. If a block is passed, the result of the block will be cached to the given key and the result is returned. +The most efficient way to implement low-level caching is using the `Rails.cache.fetch` method. This method does both reading and writing to the cache. When passed only a single argument, the key is fetched and value from the cache is returned. If a block is passed, that block will be executed in the event of a cache miss. The return value of the block will be written to the cache under the given cache key, and that return value will be returned. In case of cache hit, the cached value will be returned without executing the block. Consider the following example. An application has a `Product` model with an instance method that looks up the product’s price on a competing website. The data returned by this method would be perfect for low-level caching: 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/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 86143ca1f1..7af5fcd3c1 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -35,7 +35,7 @@ group :development do <%- if options.dev? || options.edge? -%> gem 'web-console', github: 'rails/web-console' <%- else -%> - gem 'web-console' + gem 'web-console', '>= 3.3.0' <%- end -%> <%- end -%> <% if depend_on_listen? -%> 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 50751368c8..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 @@ -574,7 +579,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile" do |content| assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content) - assert_no_match(/\Agem 'web-console'\z/, content) + assert_no_match(/\Agem 'web-console', '>= 3.3.0'\z/, content) end end @@ -583,7 +588,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile" do |content| assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content) - assert_no_match(/\Agem 'web-console'\z/, content) + assert_no_match(/\Agem 'web-console', '>= 3.3.0'\z/, content) end end |