diff options
200 files changed, 2501 insertions, 2206 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index 8f619b7956..139e6a7efb 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -20,6 +20,10 @@ Style/BracesAroundHashParameters: Style/CaseIndentation: Enabled: true +# Align comments with method definitions. +Style/CommentIndentation: + Enabled: true + # No extra empty lines. Style/EmptyLines: Enabled: true @@ -1,5 +1,10 @@ source "https://rubygems.org" +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" +end + gemspec # We need a newish Rake since Active Job sets its test tasks' descriptions. diff --git a/Gemfile.lock b/Gemfile.lock index cf8bb0fec9..68c75997b1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,28 +1,28 @@ GIT - remote: git://github.com/QueueClassic/queue_classic.git - revision: c26f2c9f6f6133b946fbcdd7b7ec905a4aca9f94 + remote: https://github.com/QueueClassic/queue_classic.git + revision: 1ef197b9db8149a895e59077badcb5b94d4c8b44 branch: master specs: queue_classic (3.2.0.RC1) pg (>= 0.17, < 0.19) GIT - remote: git://github.com/collectiveidea/delayed_job.git - revision: 71f1d5faf934d3057abca942f0d410327bc69087 + remote: https://github.com/collectiveidea/delayed_job.git + revision: e3772d4f0c8470d0fcba00c86ca3bc4f5e876830 specs: - delayed_job (4.1.1) + delayed_job (4.1.2) activesupport (>= 3.0, < 5.1) GIT - remote: git://github.com/collectiveidea/delayed_job_active_record.git - revision: 61e688e03b2ef4004b08de6d1e0a123fda8fffad + remote: https://github.com/collectiveidea/delayed_job_active_record.git + revision: 36f434c4fd660e8f11ce932be117e9c71dde7212 specs: - delayed_job_active_record (4.1.0) + delayed_job_active_record (4.1.1) activerecord (>= 3.0, < 5.1) delayed_job (>= 3.0, < 5) GIT - remote: git://github.com/matthewd/rb-inotify.git + remote: https://github.com/matthewd/rb-inotify.git revision: 90553518d1fb79aedc98a3036c59bd2b6731ac40 branch: close-handling specs: @@ -30,8 +30,8 @@ GIT ffi (>= 0.5.0) GIT - remote: git://github.com/resque/resque.git - revision: 06036388ec61e573c761ac5a25a2ef3c76537ec7 + remote: https://github.com/resque/resque.git + revision: a3a66389618b830de0e6acf862b0dc9fde05cf49 specs: resque (1.27.0) mono_logger (~> 1.0) @@ -41,8 +41,8 @@ GIT vegas (~> 0.1.2) GIT - remote: git://github.com/sass/sass.git - revision: 3fda1cbe70d615e7ef96e28db4fd1f8a3ebb5505 + remote: https://github.com/sass/sass.git + revision: 7716e67f3507c6f65878c69aa49ec358ebf675c7 branch: stable specs: sass (3.4.22) @@ -111,7 +111,7 @@ GEM specs: addressable (2.4.0) amq-protocol (2.0.1) - arel (7.1.1) + arel (7.1.2) backburner (1.3.0) beaneater (~> 1.0) dante (> 0.1.5) @@ -119,7 +119,7 @@ GEM bcrypt (3.1.11-x64-mingw32) bcrypt (3.1.11-x86-mingw32) beaneater (1.0.0) - benchmark-ips (2.6.1) + benchmark-ips (2.7.2) blade (0.5.6) activesupport (>= 3.0.0) blade-qunit_adapter (~> 1.20.0) @@ -134,14 +134,14 @@ GEM thor (~> 0.19.1) useragent (~> 0.16.7) blade-qunit_adapter (1.20.0) - blade-sauce_labs_plugin (0.5.2) + blade-sauce_labs_plugin (0.5.3) childprocess faraday selenium-webdriver builder (3.2.2) bunny (2.2.2) amq-protocol (>= 2.0.1) - byebug (8.2.5) + byebug (9.0.5) childprocess (0.5.9) ffi (~> 1.0, >= 1.0.11) coffee-rails (4.2.1) @@ -153,17 +153,17 @@ GEM coffee-script-source (1.10.0) concurrent-ruby (1.0.2) connection_pool (2.2.0) - cookiejar (0.3.0) + cookiejar (0.3.3) curses (1.0.2) - daemons (1.2.3) + daemons (1.2.4) dalli (2.7.6) dante (0.2.0) em-hiredis (0.3.1) eventmachine (~> 1.0) hiredis (~> 0.6.0) - em-http-request (1.1.3) + em-http-request (1.1.5) addressable (>= 2.3.4) - cookiejar (<= 0.3.0) + cookiejar (!= 0.3.1) em-socksify (>= 0.3) eventmachine (>= 1.0.3) http_parser.rb (>= 0.6.0) @@ -187,15 +187,15 @@ GEM faye-websocket (0.10.4) eventmachine (>= 0.12.0) websocket-driver (>= 0.5.1) - ffi (1.9.10) - ffi (1.9.10-x64-mingw32) - ffi (1.9.10-x86-mingw32) - globalid (0.3.6) + ffi (1.9.14) + ffi (1.9.14-x64-mingw32) + ffi (1.9.14-x86-mingw32) + globalid (0.3.7) activesupport (>= 4.1.0) hiredis (0.6.1) http_parser.rb (0.6.0) i18n (0.7.0) - jquery-rails (4.1.1) + jquery-rails (4.2.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) @@ -203,18 +203,18 @@ GEM kindlerb (0.1.1) mustache nokogiri - listen (3.0.7) - rb-fsevent (>= 0.9.3) - rb-inotify (>= 0.9.7) + listen (3.0.8) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) loofah (2.0.3) nokogiri (>= 1.5.9) mail (2.6.4) mime-types (>= 1.16, < 4) metaclass (0.0.4) method_source (0.8.2) - mime-types (3.0) + mime-types (3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2016.0221) + mime-types-data (3.2016.0521) mini_portile2 (2.1.0) minitest (5.3.3) mocha (0.14.0) @@ -240,42 +240,44 @@ GEM pg (0.18.4-x64-mingw32) pg (0.18.4-x86-mingw32) pkg-config (1.1.7) - psych (2.1.0) - puma (3.4.0) + psych (2.1.1) + puma (3.6.0) qu (0.2.0) multi_json qu-redis (0.2.0) qu (= 0.2.0) redis-namespace simple_uuid - que (0.11.4) + que (0.12.0) racc (1.4.14) rack (2.0.1) rack-cache (1.6.1) rack (>= 0.4) + rack-protection (1.5.3) + rack rack-test (0.6.3) rack (>= 1.0) - rails-dom-testing (2.0.0) + rails-dom-testing (2.0.1) activesupport (>= 4.2.0, < 6.0) nokogiri (~> 1.6.0) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - rake (11.1.2) + rake (11.2.2) rb-fsevent (0.9.7) rdoc (4.2.2) json (~> 1.4) redcarpet (3.2.3) - redis (3.3.0) + redis (3.3.1) redis-namespace (1.5.2) redis (~> 3.0, >= 3.0.4) - resque-scheduler (4.2.0) + resque-scheduler (4.3.0) mono_logger (~> 1.0) - redis (~> 3.0) - resque (~> 1.25) + redis (~> 3.3) + resque (~> 1.26) rufus-scheduler (~> 3.2) rubyzip (1.2.0) - rufus-scheduler (3.2.1) - sass-rails (5.0.5) + rufus-scheduler (3.2.2) + sass-rails (5.0.6) railties (>= 4.0.0, < 6) sass (~> 3.1) sprockets (>= 2.8, < 4.0) @@ -284,16 +286,17 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) - selenium-webdriver (2.53.0) + selenium-webdriver (2.53.4) childprocess (~> 0.5) rubyzip (~> 1.0) websocket (~> 1.0) - sequel (4.34.0) + sequel (4.38.0) serverengine (1.5.11) sigdump (~> 0.2.2) - sidekiq (4.1.2) + sidekiq (4.2.0) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) + rack-protection (~> 1.5) redis (~> 3.2, >= 3.2.1) sigdump (0.2.4) simple_uuid (0.4.0) @@ -304,11 +307,11 @@ GEM serverengine (~> 1.5.11) thor thread (~> 0.1.7) - sprockets (3.6.1) + sprockets (3.7.0) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-export (0.9.1) - sprockets-rails (3.1.1) + sprockets-rails (3.2.0) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) @@ -326,16 +329,16 @@ GEM thread (0.1.7) thread_safe (0.3.5) tilt (2.0.5) - turbolinks (5.0.0) + turbolinks (5.0.1) turbolinks-source (~> 5) turbolinks-source (5.0.0) tzinfo (1.2.2) thread_safe (~> 0.1) - tzinfo-data (1.2016.4) + tzinfo-data (1.2016.6) tzinfo (>= 1.0.0) - uglifier (3.0.0) + uglifier (3.0.2) execjs (>= 0.3.0, < 3) - useragent (0.16.7) + useragent (0.16.8) vegas (0.1.11) rack (>= 1.0.0) w3c_validators (1.2) @@ -408,4 +411,4 @@ DEPENDENCIES wdm (>= 0.1.0) BUNDLED WITH - 1.12.5 + 1.13.1 diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb index 561750d713..13deb62662 100644 --- a/actioncable/lib/action_cable/channel/streams.rb +++ b/actioncable/lib/action_cable/channel/streams.rb @@ -69,8 +69,8 @@ module ActionCable # Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used # instead of the default of just transmitting the updates straight to the subscriber. - # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to the callback. - # Defaults to `coder: nil` which does no decoding, passes raw messages. + # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback. + # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages. def stream_from(broadcasting, callback = nil, coder: nil, &block) broadcasting = String(broadcasting) @@ -94,8 +94,8 @@ module ActionCable # <tt>callback</tt> that'll be used instead of the default of just transmitting the updates straight # to the subscriber. # - # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to the callback. - # Defaults to `coder: nil` which does no decoding, passes raw messages. + # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback. + # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages. def stream_for(model, callback = nil, coder: nil, &block) stream_from(broadcasting_for([ channel_name, model ]), callback || block, coder: coder) end diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index b0615b08a1..4c7fcc1434 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -105,7 +105,7 @@ module ActionCable worker_pool.async_invoke(self, method, *arguments) end - # Return a basic hash of statistics for the connection keyed with `identifier`, `started_at`, and `subscriptions`. + # Return a basic hash of statistics for the connection keyed with <tt>identifier</tt>, <tt>started_at</tt>, <tt>subscriptions</tt>, and <tt>request_id</tt>. # This can be returned by a health check against the connection. def statistics { diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb index c700297a8d..dd059a553b 100644 --- a/actioncable/lib/action_cable/server/base.rb +++ b/actioncable/lib/action_cable/server/base.rb @@ -54,7 +54,7 @@ module ActionCable # The worker pool is where we run connection callbacks and channel actions. We do as little as possible on the server's main thread. # The worker pool is an executor service that's backed by a pool of threads working from a task queue. The thread pool size maxes out - # at 4 worker threads by default. Tune the size yourself with `config.action_cable.worker_pool_size`. + # at 4 worker threads by default. Tune the size yourself with <tt>config.action_cable.worker_pool_size</tt>. # # Using Active Record, Redis, etc within your channel actions means you'll get a separate connection from each thread in the worker pool. # Plan your deployment accordingly: 5 servers each running 5 Puma workers each running an 8-thread worker pool means at least 200 database diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index fb6ff819c9..e9966c7ff5 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -832,15 +832,15 @@ module ActionMailer protected - # Used by #mail to set the content type of the message. - # - # It will use the given +user_content_type+, or multipart if the mail - # message has any attachments. If the attachments are inline, the content - # type will be "multipart/related", otherwise "multipart/mixed". - # - # If there is no content type passed in via headers, and there are no - # attachments, or the message is multipart, then the default content type is - # used. + # Used by #mail to set the content type of the message. + # + # It will use the given +user_content_type+, or multipart if the mail + # message has any attachments. If the attachments are inline, the content + # type will be "multipart/related", otherwise "multipart/mixed". + # + # If there is no content type passed in via headers, and there are no + # attachments, or the message is multipart, then the default content type is + # used. def set_content_type(m, user_content_type, class_default) params = m.content_type_parameters || {} case @@ -859,16 +859,16 @@ module ActionMailer end end - # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope. - # If it does not find a translation for the +subject+ under the specified scope it will default to a - # humanized version of the <tt>action_name</tt>. - # If the subject has interpolations, you can pass them through the +interpolations+ parameter. + # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope. + # If it does not find a translation for the +subject+ under the specified scope it will default to a + # humanized version of the <tt>action_name</tt>. + # If the subject has interpolations, you can pass them through the +interpolations+ parameter. def default_i18n_subject(interpolations = {}) mailer_scope = self.class.mailer_name.tr("/", ".") I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize)) end - # Emails do not support relative path links. + # Emails do not support relative path links. def self.supports_path? false end @@ -950,7 +950,7 @@ module ActionMailer container.add_part(part) end - # This and #instrument_name is for caching instrument + # This and #instrument_name is for caching instrument def instrument_payload(key) { mailer: mailer_name, diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index cb76898f59..ef3be7af83 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -171,12 +171,12 @@ module AbstractController end private - # Makes all the (instance) methods in the helper module available to templates - # rendered through this controller. - # - # ==== Parameters - # * <tt>module</tt> - The module to include into the current helper module - # for the class + # Makes all the (instance) methods in the helper module available to templates + # rendered through this controller. + # + # ==== Parameters + # * <tt>module</tt> - The module to include into the current helper module + # for the class def add_template_helper(mod) _helpers.module_eval { include mod } end diff --git a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb index 49b5f1090e..6c103bb042 100644 --- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb +++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb @@ -39,10 +39,10 @@ module ActionController end end - # Pick the template digest to include in the ETag. If the +:template+ option - # is present, use the named template. If +:template+ is nil or absent, use - # the default controller/action template. If +:template+ is false, omit the - # template digest from the ETag. + # Pick the template digest to include in the ETag. If the +:template+ option + # is present, use the named template. If +:template+ is nil or absent, use + # the default controller/action template. If +:template+ is false, omit the + # template digest from the ETag. def pick_template_for_etag(options) unless options[:template] == false options[:template] || "#{controller_path}/#{action_name}" diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 7257bbfa95..476d081239 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -108,7 +108,7 @@ module ActionController end private - # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt> + # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt> def all_application_helpers all_helpers_from_path(helpers_path) end diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 745f449a05..9d1b740025 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -128,13 +128,13 @@ module ActionController end private - # Determine the wrapper model from the controller's name. By convention, - # this could be done by trying to find the defined model that has the - # same singular name as the controller. For example, +UsersController+ - # will try to find if the +User+ model exists. - # - # This method also does namespace lookup. Foo::Bar::UsersController will - # try to find Foo::Bar::User, Foo::User and finally User. + # Determine the wrapper model from the controller's name. By convention, + # this could be done by trying to find the defined model that has the + # same singular name as the controller. For example, +UsersController+ + # will try to find if the +User+ model exists. + # + # This method also does namespace lookup. Foo::Bar::UsersController will + # try to find Foo::Bar::User, Foo::User and finally User. def _default_wrap_model #:nodoc: return nil if klass.anonymous? model_name = klass.name.sub(/Controller$/, "").classify diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index ac17d61b96..f8f91ed41c 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -72,14 +72,14 @@ module ActionController end end - # Normalize arguments by catching blocks and setting them on :update. + # Normalize arguments by catching blocks and setting them on :update. def _normalize_args(action=nil, options={}, &blk) #:nodoc: options = super options[:update] = blk if block_given? options end - # Normalize both text and status options. + # Normalize both text and status options. def _normalize_options(options) #:nodoc: _normalize_text(options) @@ -90,7 +90,7 @@ module ActionController render as `text/plain`, `render html: '<strong>HTML</strong>'` to render as `text/html`, or `render body: 'raw'` to match the deprecated behavior and render with the default Content-Type, which is - `text/plain`. + `text/html`. WARNING end @@ -118,7 +118,7 @@ module ActionController end end - # Process controller specific options, as status, content-type and location. + # Process controller specific options, as status, content-type and location. def _process_options(options) #:nodoc: status, content_type, location = options.values_at(:status, :content_type, :location) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 16ddd3b304..09f2a79d85 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -51,7 +51,7 @@ module ActionController super(env) self.session = session - self.session_options = TestSession::DEFAULT_OPTIONS + self.session_options = TestSession::DEFAULT_OPTIONS.dup @controller_class = controller_class @custom_param_parsers = { xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] } diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 0528a6ad08..3c03976f03 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -115,8 +115,8 @@ module ActionDispatch private - # Converts an HTTP header name to an environment variable name if it is - # not contained within the headers hash. + # Converts an HTTP header name to an environment variable name if it is + # not contained within the headers hash. def env_name(key) key = key.to_s if key =~ HTTP_HEADER diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb index 7ec9d63859..01ff2109cb 100644 --- a/actionpack/lib/action_dispatch/journey/parser.rb +++ b/actionpack/lib/action_dispatch/journey/parser.rb @@ -10,7 +10,7 @@ require "action_dispatch/journey/parser_extras" module ActionDispatch module Journey class Parser < Racc::Parser -##### State transition tables begin ### + ##### State transition tables begin ### racc_action_table = [ 13, 15, 14, 7, 21, 16, 8, 19, 13, 15, @@ -128,9 +128,9 @@ module ActionDispatch Racc_debug_parser = false -##### State transition tables end ##### + ##### State transition tables end ##### -# reduce 0 omitted + # reduce 0 omitted def _reduce_1(val, _values) Cat.new(val.first, val.last) @@ -140,13 +140,13 @@ module ActionDispatch val.first end -# reduce 3 omitted + # reduce 3 omitted -# reduce 4 omitted + # reduce 4 omitted -# reduce 5 omitted + # reduce 5 omitted -# reduce 6 omitted + # reduce 6 omitted def _reduce_7(val, _values) Group.new(val[1]) @@ -164,13 +164,13 @@ module ActionDispatch Star.new(Symbol.new(val.last)) end -# reduce 11 omitted + # reduce 11 omitted -# reduce 12 omitted + # reduce 12 omitted -# reduce 13 omitted + # reduce 13 omitted -# reduce 14 omitted + # reduce 14 omitted def _reduce_15(val, _values) Slash.new("/") diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 8d0bf680c8..5abf59402d 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -264,19 +264,19 @@ module ActionDispatch end private - # Create a url helper allowing ordered parameters to be associated - # with corresponding dynamic segments, so you can do: - # - # foo_url(bar, baz, bang) - # - # Instead of: - # - # foo_url(bar: bar, baz: baz, bang: bang) - # - # Also allow options hash, so you can do: - # - # foo_url(bar, baz, bang, sort_by: 'baz') - # + # Create a url helper allowing ordered parameters to be associated + # with corresponding dynamic segments, so you can do: + # + # foo_url(bar, baz, bang) + # + # Instead of: + # + # foo_url(bar: bar, baz: baz, bang: bang) + # + # Also allow options hash, so you can do: + # + # foo_url(bar, baz, bang, sort_by: 'baz') + # def define_url_helper(mod, route, name, opts, route_key, url_strategy) helper = UrlHelper.create(route, opts, route_key, url_strategy) mod.module_eval do diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index a00602ed70..a2eaccd9ef 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -79,7 +79,12 @@ module ActionDispatch def generate_response_message(expected, actual = @response.response_code) "Expected response to be a <#{code_with_name(expected)}>,"\ " but was a <#{code_with_name(actual)}>" - .concat location_if_redirected + .concat(location_if_redirected).concat(response_body_if_short) + end + + def response_body_if_short + return "" if @response.body.size > 500 + "\nResponse body: #{@response.body}" end def location_if_redirected diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index c5b2493e06..6d28753947 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -246,7 +246,7 @@ module ActionDispatch class DebugExceptions private remove_method :stderr_logger - # Silence logger + # Silence logger def stderr_logger nil end diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb index 17517f8965..14a04ccdb1 100644 --- a/actionpack/test/assertions/response_assertions_test.rb +++ b/actionpack/test/assertions/response_assertions_test.rb @@ -6,10 +6,11 @@ module ActionDispatch class ResponseAssertionsTest < ActiveSupport::TestCase include ResponseAssertions - FakeResponse = Struct.new(:response_code, :location) do + FakeResponse = Struct.new(:response_code, :location, :body) do def initialize(*) super self.location ||= "http://test.example.com/posts" + self.body ||= "" end [:successful, :not_found, :redirection, :server_error].each do |sym| @@ -112,6 +113,27 @@ module ActionDispatch " redirect to <http://test.host/posts/redirect/2>" assert_match expected, error.message end + + def test_error_message_shows_short_response_body + @response = ActionDispatch::Response.new + @response.status = 400 + @response.body = "not too long" + error = assert_raises(Minitest::Assertion) { assert_response 200 } + expected = "Expected response to be a <200: OK>,"\ + " but was a <400: Bad Request>" \ + "\nResponse body: not too long" + assert_match expected, error.message + end + + def test_error_message_does_not_show_long_response_body + @response = ActionDispatch::Response.new + @response.status = 400 + @response.body = "not too long" * 50 + error = assert_raises(Minitest::Assertion) { assert_response 200 } + expected = "Expected response to be a <200: OK>,"\ + " but was a <400: Bad Request>" + assert_match expected, error.message + end end end end diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 2edcbc7433..42a5157010 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -194,7 +194,7 @@ class UrlOptionsTest < ActionController::TestCase get "account/overview" end - assert !@controller.class.action_methods.include?("account_overview_path") + assert_not_includes @controller.class.action_methods, "account_overview_path" end end end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index e5f24f1a3a..dc641c19ab 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -227,7 +227,7 @@ class FlashTest < ActionController::TestCase add_flash_types :foo end subclass_controller_with_no_flash_type = Class.new(test_controller_with_flash_type_foo) - assert subclass_controller_with_no_flash_type._flash_types.include?(:foo) + assert_includes subclass_controller_with_no_flash_type._flash_types, :foo end def test_does_not_add_flash_type_to_parent_class diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index 981b67f685..4c6a772062 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -125,13 +125,13 @@ class HelperTest < ActiveSupport::TestCase def test_helper_method assert_nothing_raised { @controller_class.helper_method :delegate_method } - assert master_helper_methods.include?(:delegate_method) + assert_includes master_helper_methods, :delegate_method end def test_helper_attr assert_nothing_raised { @controller_class.helper_attr :delegate_attr } - assert master_helper_methods.include?(:delegate_attr) - assert master_helper_methods.include?(:delegate_attr=) + assert_includes master_helper_methods, :delegate_attr + assert_includes master_helper_methods, :delegate_attr= end def call_controller(klass, action) @@ -168,13 +168,13 @@ class HelperTest < ActiveSupport::TestCase methods = AllHelpersController._helpers.instance_methods # abc_helper.rb - assert methods.include?(:bare_a) + assert_includes methods, :bare_a # fun/games_helper.rb - assert methods.include?(:stratego) + assert_includes methods, :stratego # fun/pdf_helper.rb - assert methods.include?(:foobar) + assert_includes methods, :foobar end def test_all_helpers_with_alternate_helper_dir @@ -185,26 +185,26 @@ class HelperTest < ActiveSupport::TestCase @controller_class.helper :all # helpers/abc_helper.rb should not be included - assert !master_helper_methods.include?(:bare_a) + assert_not_includes master_helper_methods, :bare_a # alternate_helpers/foo_helper.rb - assert master_helper_methods.include?(:baz) + assert_includes master_helper_methods, :baz end def test_helper_proxy methods = AllHelpersController.helpers.methods # Action View - assert methods.include?(:pluralize) + assert_includes methods, :pluralize # abc_helper.rb - assert methods.include?(:bare_a) + assert_includes methods, :bare_a # fun/games_helper.rb - assert methods.include?(:stratego) + assert_includes methods, :stratego # fun/pdf_helper.rb - assert methods.include?(:foobar) + assert_includes methods, :foobar end def test_helper_proxy_in_instance diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb index 40149a41d3..b64468a94e 100644 --- a/actionpack/test/controller/new_base/render_context_test.rb +++ b/actionpack/test/controller/new_base/render_context_test.rb @@ -28,7 +28,7 @@ module RenderContext protected - # 3) Set view_context to self + # 3) Set view_context to self def view_context self end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 399c7489b7..70e5760764 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -746,7 +746,7 @@ class HeadRenderTest < ActionController::TestCase get :head_with_symbolic_status, params: { status: "no_content" } assert_equal 204, @response.status - assert !@response.headers.include?("Content-Length") + assert_not_includes @response.headers, "Content-Length" assert_response :no_content Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code| diff --git a/actionpack/test/controller/request/test_request_test.rb b/actionpack/test/controller/request/test_request_test.rb index 6e0ca957c3..fa049fbc9f 100644 --- a/actionpack/test/controller/request/test_request_test.rb +++ b/actionpack/test/controller/request/test_request_test.rb @@ -6,6 +6,11 @@ class ActionController::TestRequestTest < ActionController::TestCase assert @request.session_options end + def test_mutating_session_options_does_not_affect_default_options + @request.session_options[:myparam] = 123 + assert_equal nil, ActionController::TestSession::DEFAULT_OPTIONS[:myparam] + end + ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS.each_key do |option| test "rack default session options #{option} exists in session options and is default" do assert_equal(ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS[option], diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 29471939d0..b572e7e8d5 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -1313,7 +1313,7 @@ class ResourcesTest < ActionController::TestCase def assert_resource_methods(expected, resource, action_method, method) assert_equal expected.length, resource.send("#{action_method}_methods")[method].size, "#{resource.send("#{action_method}_methods")[method].inspect}" expected.each do |action| - assert resource.send("#{action_method}_methods")[method].include?(action), + assert_includes resource.send("#{action_method}_methods")[method], action, "#{method} not in #{action_method} methods: #{resource.send("#{action_method}_methods")[method].inspect}" end end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 38cf0a2346..6dcd62572a 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -68,7 +68,7 @@ class CookieJarTest < ActiveSupport::TestCase def test_write_doesnt_set_a_nil_header headers = {} request.cookie_jar.write(headers) - assert !headers.include?("Set-Cookie") + assert_not_includes headers, "Set-Cookie" end end @@ -1115,11 +1115,11 @@ class CookiesTest < ActionController::TestCase assert_equal "david", cookies[:user_name] get :noop - assert !@response.headers.include?("Set-Cookie") + assert_not_includes @response.headers, "Set-Cookie" assert_equal "david", cookies[:user_name] get :noop - assert !@response.headers.include?("Set-Cookie") + assert_not_includes @response.headers, "Set-Cookie" assert_equal "david", cookies[:user_name] end diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb index 374e618b42..6febd5cb68 100644 --- a/actionpack/test/dispatch/header_test.rb +++ b/actionpack/test/dispatch/header_test.rb @@ -76,9 +76,9 @@ class HeaderTest < ActiveSupport::TestCase test "key?" do assert @headers.key?("CONTENT_TYPE") - assert @headers.include?("CONTENT_TYPE") + assert_includes @headers, "CONTENT_TYPE" assert @headers.key?("Content-Type") - assert @headers.include?("Content-Type") + assert_includes @headers, "Content-Type" end test "fetch with block" do diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb index 0967245855..e0de2ff4d6 100644 --- a/actionview/lib/action_view/helpers/asset_url_helper.rb +++ b/actionview/lib/action_view/helpers/asset_url_helper.rb @@ -37,7 +37,7 @@ module ActionView # some asset downloads to wait for previous assets to finish before they can # begin. You can use the <tt>%d</tt> wildcard in the +asset_host+ to # distribute the requests over four hosts. For example, - # <tt>assets%d.example.com<tt> will spread the asset requests over + # <tt>assets%d.example.com</tt> will spread the asset requests over # "assets0.example.com", ..., "assets3.example.com". # # image_tag("rails.png") diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 61ce1d36e0..04c5fd4218 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -303,7 +303,7 @@ module ActionView # # the sunrise attribute. # time_select("article", "start_time", include_seconds: true) # - # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30 and 45. + # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30, and 45. # time_select 'game', 'game_time', {minute_step: 15} # # # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts. diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb index 70d7c484eb..36575b2fd0 100644 --- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb +++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb @@ -41,8 +41,8 @@ module ActionView sanitize_attribute_name(value), text, value, html_options) end - # Generate default options for collection helpers, such as :checked and - # :disabled. + # Generate default options for collection helpers, such as :checked and + # :disabled. def default_html_options_for_collection(item, value) #:nodoc: html_options = @html_options.dup diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb index 8cc34e3180..667c7e945a 100644 --- a/actionview/lib/action_view/helpers/tags/select.rb +++ b/actionview/lib/action_view/helpers/tags/select.rb @@ -28,10 +28,10 @@ module ActionView private - # Grouped choices look like this: - # - # [nil, []] - # { nil => [] } + # Grouped choices look like this: + # + # [nil, []] + # { nil => [] } def grouped_choices? !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last end diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index dd5f6e7300..344893f41a 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -224,12 +224,12 @@ module ActionView module LayoutConditions # :nodoc: private - # Determines whether the current action has a layout definition by - # checking the action name against the :only and :except conditions - # set by the <tt>layout</tt> method. - # - # ==== Returns - # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise. + # Determines whether the current action has a layout definition by + # checking the action name against the :only and :except conditions + # set by the <tt>layout</tt> method. + # + # ==== Returns + # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise. def _conditional_layout? return unless super @@ -334,11 +334,11 @@ module ActionView private - # If no layout is supplied, look for a template named the return - # value of this method. - # - # ==== Returns - # * <tt>String</tt> - A template name + # If no layout is supplied, look for a template named the return + # value of this method. + # + # ==== Returns + # * <tt>String</tt> - A template name def _implied_layout_name # :nodoc: controller_path end diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 4b6aecd187..dfe38c488f 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -350,13 +350,13 @@ module ActionView end end - # Sets up instance variables needed for rendering a partial. This method - # finds the options and details and extracts them. The method also contains - # logic that handles the type of object passed in as the partial. - # - # If +options[:partial]+ is a string, then the +@path+ instance variable is - # set to that string. Otherwise, the +options[:partial]+ object must - # respond to +to_partial_path+ in order to setup the path. + # Sets up instance variables needed for rendering a partial. This method + # finds the options and details and extracts them. The method also contains + # logic that handles the type of object passed in as the partial. + # + # If +options[:partial]+ is a string, then the +@path+ instance variable is + # set to that string. Otherwise, the +options[:partial]+ object must + # respond to +to_partial_path+ in order to setup the path. def setup(context, options, block) @view = context @options = options @@ -466,13 +466,13 @@ module ActionView end end - # Obtains the path to where the object's partial is located. If the object - # responds to +to_partial_path+, then +to_partial_path+ will be called and - # will provide the path. If the object does not respond to +to_partial_path+, - # then an +ArgumentError+ is raised. - # - # If +prefix_partial_path_with_controller_namespace+ is true, then this - # method will prefix the partial paths with a namespace. + # Obtains the path to where the object's partial is located. If the object + # responds to +to_partial_path+, then +to_partial_path+ will be called and + # will provide the path. If the object does not respond to +to_partial_path+, + # then an +ArgumentError+ is raised. + # + # If +prefix_partial_path_with_controller_namespace+ is true, then this + # method will prefix the partial paths with a namespace. def partial_path(object = @object) object = object.to_model if object.respond_to?(:to_model) diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb index 0323ebef6a..2434250b2d 100644 --- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb @@ -27,8 +27,8 @@ module ActionView private - # This is the same logging logic as in ShowExceptions middleware. - # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron. + # This is the same logging logic as in ShowExceptions middleware. + # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron. def log_error(exception) #:nodoc: logger = ActionView::Base.logger return unless logger diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index 331a5ea228..4bcf009e27 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -16,7 +16,7 @@ module ActionView private - # Determine the template to be rendered using the given options. + # Determine the template to be rendered using the given options. def determine_template(options) keys = options.has_key?(:locals) ? options[:locals].keys : [] @@ -44,8 +44,8 @@ module ActionView end end - # Renders the given template. A string representing the layout can be - # supplied as well. + # Renders the given template. A string representing the layout can be + # supplied as well. def render_template(template, layout_name = nil, locals = nil) #:nodoc: view, locals = @view, locals || {} @@ -69,9 +69,9 @@ module ActionView end end - # This is the method which actually finds the layout using details in the lookup - # context object. If no layout is found, it checks if at least a layout with - # the given name exists across all details before raising the error. + # This is the method which actually finds the layout using details in the lookup + # context object. If no layout is found, it checks if at least a layout with + # the given name exists across all details before raising the error. def find_layout(layout, keys, formats) resolve_layout(layout, keys, formats) end diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb index ddbd2ea36a..0ffae10432 100644 --- a/actionview/lib/action_view/template/html.rb +++ b/actionview/lib/action_view/template/html.rb @@ -14,9 +14,7 @@ module ActionView #:nodoc: "html template" end - def inspect - "html template" - end + alias_method :inspect, :identifier def to_str ERB::Util.h(@string) diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 20c2d5c782..5a2948d5a9 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -256,7 +256,7 @@ module ActionView filename.start_with?(path) end - # Helper for building query glob string based on resolver's pattern. + # Helper for building query glob string based on resolver's pattern. def build_query(path, details) query = @pattern.dup @@ -281,14 +281,14 @@ module ActionView entry.gsub(/[*?{}\[\]]/, '\\\\\\&'.freeze) end - # Returns the file mtime from the filesystem. + # Returns the file mtime from the filesystem. def mtime(p) File.mtime(p) end - # Extract handler, formats and variant from path. If a format cannot be found neither - # from the path, or the handler, we should return the array of formats given - # to the resolver. + # Extract handler, formats and variant from path. If a format cannot be found neither + # from the path, or the handler, we should return the array of formats given + # to the resolver. def extract_handler_and_format_and_variant(path, default_formats) pieces = File.basename(path).split(".".freeze) pieces.shift diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb index 898593e702..e8d4e18f04 100644 --- a/actionview/lib/action_view/template/text.rb +++ b/actionview/lib/action_view/template/text.rb @@ -14,9 +14,7 @@ module ActionView #:nodoc: "text template" end - def inspect - "text template" - end + alias_method :inspect, :identifier def to_str @string diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb index 2ed62ca88f..b5cde5b43f 100644 --- a/actionview/lib/action_view/view_paths.rb +++ b/actionview/lib/action_view/view_paths.rb @@ -22,8 +22,8 @@ module ActionView private - # Override this method in your controller if you want to change paths prefixes for finding views. - # Prefixes defined here will still be added to parents' <tt>._prefixes</tt>. + # Override this method in your controller if you want to change paths prefixes for finding views. + # Prefixes defined here will still be added to parents' <tt>._prefixes</tt>. def local_prefixes [controller_path] end diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb index 3bb8d21e86..88c7189d22 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -264,7 +264,7 @@ module ActionDispatch class DebugExceptions private remove_method :stderr_logger - # Silence logger + # Silence logger def stderr_logger nil end diff --git a/actionview/test/actionpack/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb index 863efd15fa..a2cd3deb58 100644 --- a/actionview/test/actionpack/abstract/abstract_controller_test.rb +++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb @@ -227,7 +227,7 @@ module AbstractController end class ActionMissingRespondToActionController < AbstractController::Base - # No actions + # No actions private def action_missing(action_name) self.response_body = "success" diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb index 952ed72aa9..8495949975 100644 --- a/actionview/test/activerecord/polymorphic_routes_test.rb +++ b/actionview/test/activerecord/polymorphic_routes_test.rb @@ -598,7 +598,7 @@ class PolymorphicRoutesTest < ActionController::TestCase end end - # Tests for uncountable names + # Tests for uncountable names def test_uncountable_resource with_test_routes do @series.save diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index e82905175a..395c4e4f41 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -1808,7 +1808,7 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_form_for_with_symbol_object_name + def test_form_for_with_symbol_as form_for(@post, as: "other_name", html: { id: "create-post" }) do |f| concat f.label(:title, class: "post_title") concat f.text_field(:title) @@ -3120,7 +3120,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_builder_does_not_have_form_for_method - assert !ActionView::Helpers::FormBuilder.instance_methods.include?(:form_for) + assert_not_includes ActionView::Helpers::FormBuilder.instance_methods, :form_for end def test_form_for_and_fields_for diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb index 40d8d6f3b8..b47d92df34 100644 --- a/actionview/test/template/lookup_context_test.rb +++ b/actionview/test/template/lookup_context_test.rb @@ -120,8 +120,8 @@ class LookupContextTest < ActiveSupport::TestCase @lookup_context.with_fallbacks do assert_equal 3, @lookup_context.view_paths.size - assert @lookup_context.view_paths.include?(ActionView::FallbackFileSystemResolver.new("")) - assert @lookup_context.view_paths.include?(ActionView::FallbackFileSystemResolver.new("/")) + assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("") + assert_includes @lookup_context.view_paths, ActionView::FallbackFileSystemResolver.new("/") end end diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 7c8a6aae47..e7e0b147c7 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -460,7 +460,7 @@ module RenderTestCases def test_render_knows_about_types_registered_when_extensions_are_checked_earlier_in_initialization ActionView::Template::Handlers.extensions ActionView::Template.register_template_handler :foo, CustomHandler - assert ActionView::Template::Handlers.extensions.include?(:foo) + assert_includes ActionView::Template::Handlers.extensions, :foo ensure ActionView::Template.unregister_template_handler :foo end diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb index 597b2aa8dd..3f51636603 100644 --- a/actionview/test/template/test_case_test.rb +++ b/actionview/test/template/test_case_test.rb @@ -25,7 +25,7 @@ module ActionView def self.included(test_case) test_case.class_eval do test "helpers defined on ActionView::TestCase are available" do - assert test_case.ancestors.include?(ASharedTestHelper) + assert_includes test_case.ancestors, ASharedTestHelper assert_equal "Holla!", from_shared_helper end end @@ -64,7 +64,7 @@ module ActionView helper AnotherTestHelper test "additional helper classes can be specified as in a controller" do - assert test_case.ancestors.include?(AnotherTestHelper) + assert_includes test_case.ancestors, AnotherTestHelper assert_equal "Howdy!", from_another_helper end @@ -96,12 +96,12 @@ module ActionView tests ATestHelper test "tests the specified helper module" do assert_equal ATestHelper, test_case.helper_class - assert test_case.ancestors.include?(ATestHelper) + assert_includes test_case.ancestors, ATestHelper end helper AnotherTestHelper test "additional helper classes can be specified as in a controller" do - assert test_case.ancestors.include?(AnotherTestHelper) + assert_includes test_case.ancestors, AnotherTestHelper assert_equal "Howdy!", from_another_helper test_case.helper_class.module_eval do @@ -161,7 +161,7 @@ module ActionView test "view_assigns excludes internal ivars" do INTERNAL_IVARS.each do |ivar| assert defined?(ivar), "expected #{ivar} to be defined" - assert !view_assigns.keys.include?(ivar.to_s.tr("@", "").to_sym), "expected #{ivar} to be excluded from view_assigns" + assert_not_includes view_assigns.keys, ivar.to_s.tr("@", "").to_sym, "expected #{ivar} to be excluded from view_assigns" end end end @@ -200,7 +200,7 @@ module ActionView test "inflects the name of the helper module to test from the test case class" do assert_equal ATestHelper, test_case.helper_class - assert test_case.ancestors.include?(ATestHelper) + assert_includes test_case.ancestors, ATestHelper end test "a configured test controller is available" do @@ -209,7 +209,7 @@ module ActionView end test "no additional helpers should shared across test cases" do - assert !test_case.ancestors.include?(AnotherTestHelper) + assert_not_includes test_case.ancestors, AnotherTestHelper assert_raise(NoMethodError) { send :from_another_helper } end diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb index 86cc880b14..c8eedb6156 100644 --- a/activejob/lib/active_job/queue_adapters.rb +++ b/activejob/lib/active_job/queue_adapters.rb @@ -8,7 +8,7 @@ module ActiveJob # * {Qu}[https://github.com/bkeepers/qu] # * {Que}[https://github.com/chanks/que] # * {queue_classic}[https://github.com/QueueClassic/queue_classic] - # * {Resque 1.x}[https://github.com/resque/resque/tree/1-x-stable] + # * {Resque}[https://github.com/resque/resque] # * {Sidekiq}[http://sidekiq.org] # * {Sneakers}[https://github.com/jondot/sneakers] # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch] diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index 8bcad6db0f..12687c70d3 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -46,9 +46,13 @@ module ActiveModel # class Person # include ActiveModel::Conversion # attr_accessor :id + # + # def initialize(id) + # @id = id + # end # end # - # person = Person.create(id: 1) + # person = Person.new(1) # person.to_key # => [1] def to_key key = respond_to?(:id) && id @@ -61,12 +65,17 @@ module ActiveModel # class Person # include ActiveModel::Conversion # attr_accessor :id + # + # def initialize(id) + # @id = id + # end + # # def persisted? # true # end # end # - # person = Person.create(id: 1) + # person = Person.new(1) # person.to_param # => "1" def to_param (persisted? && key = to_key) ? key.join("-") : nil diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index f87973301b..6e0af99ad7 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -26,8 +26,8 @@ module ActiveModel # # define_attribute_methods :name # - # def initialize(name) - # @name = name + # def initialize + # @name = nil # end # # def name @@ -58,7 +58,7 @@ module ActiveModel # # A newly instantiated +Person+ object is unchanged: # - # person = Person.new("Uncle Bob") + # person = Person.new # person.changed? # => false # # Change the name: @@ -66,11 +66,11 @@ module ActiveModel # person.name = 'Bob' # person.changed? # => true # person.name_changed? # => true - # person.name_changed?(from: "Uncle Bob", to: "Bob") # => true - # person.name_was # => "Uncle Bob" - # person.name_change # => ["Uncle Bob", "Bob"] + # person.name_changed?(from: nil, to: "Bob") # => true + # person.name_was # => nil + # person.name_change # => [nil, "Bob"] # person.name = 'Bill' - # person.name_change # => ["Uncle Bob", "Bill"] + # person.name_change # => [nil, "Bill"] # # Save the changes: # @@ -80,9 +80,9 @@ module ActiveModel # # Reset the changes: # - # person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]} + # person.previous_changes # => {"name" => [nil, "Bill"]} # person.name_previously_changed? # => true - # person.name_previous_change # => ["Uncle Bob", "Bill"] + # person.name_previous_change # => [nil, "Bill"] # person.reload! # person.previous_changes # => {} # diff --git a/activemodel/lib/active_model/type/date_time.rb b/activemodel/lib/active_model/type/date_time.rb index f88c9e5071..5cb0077e45 100644 --- a/activemodel/lib/active_model/type/date_time.rb +++ b/activemodel/lib/active_model/type/date_time.rb @@ -19,8 +19,8 @@ module ActiveModel fast_string_to_time(value) || fallback_string_to_time(value) end - # '0.123456' -> 123456 - # '1.123456' -> 123456 + # '0.123456' -> 123456 + # '1.123456' -> 123456 def microseconds(time) time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0 end diff --git a/activemodel/lib/active_model/type/helpers/time_value.rb b/activemodel/lib/active_model/type/helpers/time_value.rb index 64780ebbdc..d8c5d929a3 100644 --- a/activemodel/lib/active_model/type/helpers/time_value.rb +++ b/activemodel/lib/active_model/type/helpers/time_value.rb @@ -64,7 +64,7 @@ module ActiveModel ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ - # Doesn't handle time zones. + # Doesn't handle time zones. def fast_string_to_time(string) if string =~ ISO_DATETIME microsec = ($7.to_r * 1_000_000).to_i diff --git a/activemodel/lib/active_model/type/value.rb b/activemodel/lib/active_model/type/value.rb index 4d3a1b29d6..7e9ae92245 100644 --- a/activemodel/lib/active_model/type/value.rb +++ b/activemodel/lib/active_model/type/value.rb @@ -105,9 +105,9 @@ module ActiveModel private - # Convenience method for types which do not need separate type casting - # behavior for user and database inputs. Called by Value#cast for - # values except +nil+. + # Convenience method for types which do not need separate type casting + # behavior for user and database inputs. Called by Value#cast for + # values except +nil+. def cast_value(value) # :doc: value end diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index da99a0eca0..3288b5543c 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -37,7 +37,7 @@ class ErrorsTest < ActiveModel::TestCase def test_include? errors = ActiveModel::Errors.new(self) errors[:foo] << "omg" - assert errors.include?(:foo), "errors should include :foo" + assert_includes errors, :foo, "errors should include :foo" end def test_dup @@ -125,7 +125,7 @@ class ErrorsTest < ActiveModel::TestCase person.errors[:foo] assert person.errors.empty? assert person.errors.blank? - assert !person.errors.include?(:foo) + assert_not_includes person.errors, :foo end test "include? does not add a key to messages hash" do diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb index 45c166f33a..d15ba64eb0 100644 --- a/activemodel/test/cases/serializers/json_serialization_test.rb +++ b/activemodel/test/cases/serializers/json_serialization_test.rb @@ -18,7 +18,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_no_match %r{^\{"contact":\{}, json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -32,7 +32,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{^\{"contact":\{}, json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json ensure @@ -58,7 +58,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{^\{"json_contact":\{}, json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -68,7 +68,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -79,7 +79,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json assert_no_match %r{"awesome":true}, json - assert !json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_not_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_no_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -89,7 +89,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_no_match %r{"name":"Konata Izumi"}, json assert_no_match %r{"age":16}, json assert_match %r{"awesome":true}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"preferences":\{"shows":"anime"\}}, json end diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index 85212a80fc..f28cfc0ef5 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -46,7 +46,7 @@ class I18nValidationTest < ActiveModel::TestCase # are used to generate tests to keep things DRY # COMMON_CASES = [ - # [ case, validation_options, generate_message_options] + # [ case, validation_options, generate_message_options] [ "given no options", {}, {}], [ "given custom message", { message: "custom" }, { message: "custom" }], [ "given if condition", { if: lambda { true } }, {}], diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb index e5690da89a..25c37a572f 100644 --- a/activemodel/test/cases/validations/validations_context_test.rb +++ b/activemodel/test/cases/validations/validations_context_test.rb @@ -38,7 +38,7 @@ class ValidationsContextTest < ActiveModel::TestCase Topic.validates_with(ValidatorThatAddsErrors, on: :create) topic = Topic.new assert topic.invalid?(:create), "Validation does run on create if 'on' is set to create" - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE end test "with a class that adds errors on multiple contexts and validating a new model" do @@ -48,10 +48,10 @@ class ValidationsContextTest < ActiveModel::TestCase assert topic.valid?, "Validation ran with no context given when 'on' is set to context1 and context2" assert topic.invalid?(:context1), "Validation did not run on context1 when 'on' is set to context1 and context2" - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE assert topic.invalid?(:context2), "Validation did not run on context2 when 'on' is set to context1 and context2" - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE end test "with a class that validating a model for a multiple contexts" do @@ -62,7 +62,7 @@ class ValidationsContextTest < ActiveModel::TestCase assert topic.valid?, "Validation ran with no context given when 'on' is set to context1 and context2" assert topic.invalid?([:context1, :context2]), "Validation did not run on context1 when 'on' is set to context1 and context2" - assert topic.errors[:base].include?(ERROR_MESSAGE) - assert topic.errors[:base].include?(ANOTHER_ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE + assert_includes topic.errors[:base], ANOTHER_ERROR_MESSAGE end end diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index 7af51c5cc5..20c11dd852 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -51,7 +51,7 @@ class ValidatesWithTest < ActiveModel::TestCase Topic.validates_with(ValidatorThatAddsErrors) topic = Topic.new assert topic.invalid?, "A class that adds errors causes the record to be invalid" - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE end test "with a class that returns valid" do @@ -64,8 +64,8 @@ class ValidatesWithTest < ActiveModel::TestCase Topic.validates_with(ValidatorThatAddsErrors, OtherValidatorThatAddsErrors) topic = Topic.new assert topic.invalid? - assert topic.errors[:base].include?(ERROR_MESSAGE) - assert topic.errors[:base].include?(OTHER_ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE + assert_includes topic.errors[:base], OTHER_ERROR_MESSAGE end test "with if statements that return false" do @@ -78,7 +78,7 @@ class ValidatesWithTest < ActiveModel::TestCase Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 1") topic = Topic.new assert topic.invalid? - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE end test "with unless statements that return true" do @@ -91,7 +91,7 @@ class ValidatesWithTest < ActiveModel::TestCase Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 2") topic = Topic.new assert topic.invalid? - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE end test "passes all configuration options to the validator class" do @@ -111,7 +111,7 @@ class ValidatesWithTest < ActiveModel::TestCase Topic.validates_with(ValidatorThatValidatesOptions, field: :first_name) topic = Topic.new assert topic.invalid? - assert topic.errors[:base].include?(ERROR_MESSAGE) + assert_includes topic.errors[:base], ERROR_MESSAGE end test "validates_with each validator" do diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index 7aaafc428e..6647191205 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -53,8 +53,8 @@ class ValidationsTest < ActiveModel::TestCase errors = r.errors.collect { |attr, messages| [attr.to_s, messages] } - assert errors.include?(["title", "is Empty"]) - assert errors.include?(["content", "is Empty"]) + assert_includes errors, ["title", "is Empty"] + assert_includes errors, ["content", "is Empty"] end def test_multiple_errors_per_attr_iteration_with_full_error_composition @@ -86,8 +86,8 @@ class ValidationsTest < ActiveModel::TestCase assert_equal ["Reply is not dignifying"], r.errors[:base] - assert errors.include?("Title is Empty") - assert errors.include?("Reply is not dignifying") + assert_includes errors, "Title is Empty" + assert_includes errors, "Reply is not dignifying" assert_equal 2, r.errors.count end @@ -101,8 +101,8 @@ class ValidationsTest < ActiveModel::TestCase assert_equal ["is invalid"], r.errors[:base] - assert errors.include?("Title is Empty") - assert errors.include?("is invalid") + assert_includes errors, "Title is Empty" + assert_includes errors, "is invalid" assert_equal 2, r.errors.count end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 3eac34d65e..6cc667b754 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,30 @@ +* Always store errors details information with symbols. + + When the association is autosaved we were storing the details with + string keys. This was creating inconsistency with other details that are + added using the `Errors#add` method. It was also inconsistent with the + `Errors#messages` storage. + + To fix this inconsistency we are always storing with symbols. This will + cause a small breaking change because in those cases the details could + be accessed as strings keys but now it can not. + + Fix #26499. + + *Rafael Mendonça França*, *Marcus Vieira* + +* Calling `touch` on a model using optimistic locking will now leave the model + in a non-dirty state with no attribute changes. + + Fixes #26496. + + *Jakob Skjerning* + +* Using a mysql2 connection after it fails to reconnect will now have an error message + saying the connection is closed rather than an undefined method error message. + + *Dylan Thacker-Smith* + * PostgreSQL array columns will now respect the encoding of strings contained in the array. diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 8979b13286..5ca8fe576e 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -24,161 +24,161 @@ module ActiveRecord super end - # Active Record implements aggregation through a macro-like class method called #composed_of - # for representing attributes as value objects. It expresses relationships like "Account [is] - # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call - # to the macro adds a description of how the value objects are created from the attributes of - # the entity object (when the entity is initialized either as a new object or from finding an - # existing object) and how it can be turned back into attributes (when the entity is saved to - # the database). - # - # class Customer < ActiveRecord::Base - # composed_of :balance, class_name: "Money", mapping: %w(amount currency) - # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] - # end - # - # The customer class now has the following methods to manipulate the value objects: - # * <tt>Customer#balance, Customer#balance=(money)</tt> - # * <tt>Customer#address, Customer#address=(address)</tt> - # - # These methods will operate with value objects like the ones described below: - # - # class Money - # include Comparable - # attr_reader :amount, :currency - # EXCHANGE_RATES = { "USD_TO_DKK" => 6 } - # - # def initialize(amount, currency = "USD") - # @amount, @currency = amount, currency - # end - # - # def exchange_to(other_currency) - # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor - # Money.new(exchanged_amount, other_currency) - # end - # - # def ==(other_money) - # amount == other_money.amount && currency == other_money.currency - # end - # - # def <=>(other_money) - # if currency == other_money.currency - # amount <=> other_money.amount - # else - # amount <=> other_money.exchange_to(currency).amount - # end - # end - # end - # - # class Address - # attr_reader :street, :city - # def initialize(street, city) - # @street, @city = street, city - # end - # - # def close_to?(other_address) - # city == other_address.city - # end - # - # def ==(other_address) - # city == other_address.city && street == other_address.street - # end - # end - # - # Now it's possible to access attributes from the database through the value objects instead. If - # you choose to name the composition the same as the attribute's name, it will be the only way to - # access that attribute. That's the case with our +balance+ attribute. You interact with the value - # objects just like you would with any other attribute: - # - # customer.balance = Money.new(20) # sets the Money value object and the attribute - # customer.balance # => Money value object - # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK") - # customer.balance > Money.new(10) # => true - # customer.balance == Money.new(20) # => true - # customer.balance < Money.new(5) # => false - # - # Value objects can also be composed of multiple attributes, such as the case of Address. The order - # of the mappings will determine the order of the parameters. - # - # customer.address_street = "Hyancintvej" - # customer.address_city = "Copenhagen" - # customer.address # => Address.new("Hyancintvej", "Copenhagen") - # - # customer.address = Address.new("May Street", "Chicago") - # customer.address_street # => "May Street" - # customer.address_city # => "Chicago" - # - # == Writing value objects - # - # Value objects are immutable and interchangeable objects that represent a given value, such as - # a Money object representing $5. Two Money objects both representing $5 should be equal (through - # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is - # unlike entity objects where equality is determined by identity. An entity class such as Customer can - # easily have two different objects that both have an address on Hyancintvej. Entity identity is - # determined by object or relational unique identifiers (such as primary keys). Normal - # ActiveRecord::Base classes are entity objects. - # - # It's also important to treat the value objects as immutable. Don't allow the Money object to have - # its amount changed after creation. Create a new Money object with the new value instead. The - # <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing - # its own values. Active Record won't persist value objects that have been changed through means - # other than the writer method. - # - # The immutable requirement is enforced by Active Record by freezing any object assigned as a value - # object. Attempting to change it afterwards will result in a +RuntimeError+. - # - # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not - # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable - # - # == Custom constructors and converters - # - # By default value objects are initialized by calling the <tt>new</tt> constructor of the value - # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt> - # option, as arguments. If the value class doesn't support this convention then #composed_of allows - # a custom constructor to be specified. - # - # When a new value is assigned to the value object, the default assumption is that the new value - # is an instance of the value class. Specifying a custom converter allows the new value to be automatically - # converted to an instance of value class if necessary. - # - # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be - # aggregated using the +NetAddr::CIDR+ value class (http://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR). - # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter. - # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string - # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet - # these requirements: - # - # class NetworkResource < ActiveRecord::Base - # composed_of :cidr, - # class_name: 'NetAddr::CIDR', - # mapping: [ %w(network_address network), %w(cidr_range bits) ], - # allow_nil: true, - # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") }, - # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) } - # end - # - # # This calls the :constructor - # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24) - # - # # These assignments will both use the :converter - # network_resource.cidr = [ '192.168.2.1', 8 ] - # network_resource.cidr = '192.168.0.1/24' - # - # # This assignment won't use the :converter as the value is already an instance of the value class - # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8') - # - # # Saving and then reloading will use the :constructor on reload - # network_resource.save - # network_resource.reload - # - # == Finding records by a value object - # - # Once a #composed_of relationship is specified for a model, records can be loaded from the database - # by specifying an instance of the value object in the conditions hash. The following example - # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD": - # - # Customer.where(balance: Money.new(20, "USD")) - # + # Active Record implements aggregation through a macro-like class method called #composed_of + # for representing attributes as value objects. It expresses relationships like "Account [is] + # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call + # to the macro adds a description of how the value objects are created from the attributes of + # the entity object (when the entity is initialized either as a new object or from finding an + # existing object) and how it can be turned back into attributes (when the entity is saved to + # the database). + # + # class Customer < ActiveRecord::Base + # composed_of :balance, class_name: "Money", mapping: %w(amount currency) + # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] + # end + # + # The customer class now has the following methods to manipulate the value objects: + # * <tt>Customer#balance, Customer#balance=(money)</tt> + # * <tt>Customer#address, Customer#address=(address)</tt> + # + # These methods will operate with value objects like the ones described below: + # + # class Money + # include Comparable + # attr_reader :amount, :currency + # EXCHANGE_RATES = { "USD_TO_DKK" => 6 } + # + # def initialize(amount, currency = "USD") + # @amount, @currency = amount, currency + # end + # + # def exchange_to(other_currency) + # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor + # Money.new(exchanged_amount, other_currency) + # end + # + # def ==(other_money) + # amount == other_money.amount && currency == other_money.currency + # end + # + # def <=>(other_money) + # if currency == other_money.currency + # amount <=> other_money.amount + # else + # amount <=> other_money.exchange_to(currency).amount + # end + # end + # end + # + # class Address + # attr_reader :street, :city + # def initialize(street, city) + # @street, @city = street, city + # end + # + # def close_to?(other_address) + # city == other_address.city + # end + # + # def ==(other_address) + # city == other_address.city && street == other_address.street + # end + # end + # + # Now it's possible to access attributes from the database through the value objects instead. If + # you choose to name the composition the same as the attribute's name, it will be the only way to + # access that attribute. That's the case with our +balance+ attribute. You interact with the value + # objects just like you would with any other attribute: + # + # customer.balance = Money.new(20) # sets the Money value object and the attribute + # customer.balance # => Money value object + # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK") + # customer.balance > Money.new(10) # => true + # customer.balance == Money.new(20) # => true + # customer.balance < Money.new(5) # => false + # + # Value objects can also be composed of multiple attributes, such as the case of Address. The order + # of the mappings will determine the order of the parameters. + # + # customer.address_street = "Hyancintvej" + # customer.address_city = "Copenhagen" + # customer.address # => Address.new("Hyancintvej", "Copenhagen") + # + # customer.address = Address.new("May Street", "Chicago") + # customer.address_street # => "May Street" + # customer.address_city # => "Chicago" + # + # == Writing value objects + # + # Value objects are immutable and interchangeable objects that represent a given value, such as + # a Money object representing $5. Two Money objects both representing $5 should be equal (through + # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is + # unlike entity objects where equality is determined by identity. An entity class such as Customer can + # easily have two different objects that both have an address on Hyancintvej. Entity identity is + # determined by object or relational unique identifiers (such as primary keys). Normal + # ActiveRecord::Base classes are entity objects. + # + # It's also important to treat the value objects as immutable. Don't allow the Money object to have + # its amount changed after creation. Create a new Money object with the new value instead. The + # <tt>Money#exchange_to</tt> method is an example of this. It returns a new value object instead of changing + # its own values. Active Record won't persist value objects that have been changed through means + # other than the writer method. + # + # The immutable requirement is enforced by Active Record by freezing any object assigned as a value + # object. Attempting to change it afterwards will result in a +RuntimeError+. + # + # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not + # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable + # + # == Custom constructors and converters + # + # By default value objects are initialized by calling the <tt>new</tt> constructor of the value + # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt> + # option, as arguments. If the value class doesn't support this convention then #composed_of allows + # a custom constructor to be specified. + # + # When a new value is assigned to the value object, the default assumption is that the new value + # is an instance of the value class. Specifying a custom converter allows the new value to be automatically + # converted to an instance of value class if necessary. + # + # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be + # aggregated using the +NetAddr::CIDR+ value class (http://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR). + # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter. + # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string + # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet + # these requirements: + # + # class NetworkResource < ActiveRecord::Base + # composed_of :cidr, + # class_name: 'NetAddr::CIDR', + # mapping: [ %w(network_address network), %w(cidr_range bits) ], + # allow_nil: true, + # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") }, + # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) } + # end + # + # # This calls the :constructor + # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24) + # + # # These assignments will both use the :converter + # network_resource.cidr = [ '192.168.2.1', 8 ] + # network_resource.cidr = '192.168.0.1/24' + # + # # This assignment won't use the :converter as the value is already an instance of the value class + # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8') + # + # # Saving and then reloading will use the :constructor on reload + # network_resource.save + # network_resource.reload + # + # == Finding records by a value object + # + # Once a #composed_of relationship is specified for a model, records can be loaded from the database + # by specifying an instance of the value object in the conditions hash. The following example + # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD": + # + # Customer.where(balance: Money.new(20, "USD")) + # module ClassMethods # Adds reader and writer methods for manipulating a value object: # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods. diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index dc6fe1640e..b5f1f1980a 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -274,882 +274,882 @@ module ActiveRecord @association_cache[name] = association end - # \Associations are a set of macro-like class methods for tying objects together through - # foreign keys. They express relationships like "Project has one Project Manager" - # or "Project belongs to a Portfolio". Each macro adds a number of methods to the - # class which are specialized according to the collection or association symbol and the - # options hash. It works much the same way as Ruby's own <tt>attr*</tt> - # methods. - # - # class Project < ActiveRecord::Base - # belongs_to :portfolio - # has_one :project_manager - # has_many :milestones - # has_and_belongs_to_many :categories - # end - # - # The project class now has the following methods (and more) to ease the traversal and - # manipulation of its relationships: - # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt> - # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt> - # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt> - # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),</tt> - # <tt>Project#milestones.build, Project#milestones.create</tt> - # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt> - # <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt> - # - # === A word of warning - # - # Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveRecord::Core] of - # <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to - # its model, using an association with the same name as one provided by <tt>ActiveRecord::Base</tt> will override the method inherited through <tt>ActiveRecord::Base</tt> and will break things. - # For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of <tt>ActiveRecord::Base</tt> instance methods. - # - # == Auto-generated methods - # See also Instance Public methods below for more details. - # - # === Singular associations (one-to-one) - # | | belongs_to | - # generated methods | belongs_to | :polymorphic | has_one - # ----------------------------------+------------+--------------+--------- - # other(force_reload=false) | X | X | X - # other=(other) | X | X | X - # build_other(attributes={}) | X | | X - # create_other(attributes={}) | X | | X - # create_other!(attributes={}) | X | | X - # - # === Collection associations (one-to-many / many-to-many) - # | | | has_many - # generated methods | habtm | has_many | :through - # ----------------------------------+-------+----------+---------- - # others(force_reload=false) | X | X | X - # others=(other,other,...) | X | X | X - # other_ids | X | X | X - # other_ids=(id,id,...) | X | X | X - # others<< | X | X | X - # others.push | X | X | X - # others.concat | X | X | X - # others.build(attributes={}) | X | X | X - # others.create(attributes={}) | X | X | X - # others.create!(attributes={}) | X | X | X - # others.size | X | X | X - # others.length | X | X | X - # others.count | X | X | X - # others.sum(*args) | X | X | X - # others.empty? | X | X | X - # others.clear | X | X | X - # others.delete(other,other,...) | X | X | X - # others.delete_all | X | X | X - # others.destroy(other,other,...) | X | X | X - # others.destroy_all | X | X | X - # others.find(*args) | X | X | X - # others.exists? | X | X | X - # others.distinct | X | X | X - # others.reset | X | X | X - # - # === Overriding generated methods - # - # Association methods are generated in a module that is included into the model class, - # which allows you to easily override with your own methods and call the original - # generated method with +super+. For example: - # - # class Car < ActiveRecord::Base - # belongs_to :owner - # belongs_to :old_owner - # def owner=(new_owner) - # self.old_owner = self.owner - # super - # end - # end - # - # If your model class is <tt>Project</tt>, then the module is - # named <tt>Project::GeneratedAssociationMethods</tt>. The +GeneratedAssociationMethods+ module is - # included in the model class immediately after the (anonymous) generated attributes methods - # module, meaning an association will override the methods for an attribute with the same name. - # - # == Cardinality and associations - # - # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many - # relationships between models. Each model uses an association to describe its role in - # the relation. The #belongs_to association is always used in the model that has - # the foreign key. - # - # === One-to-one - # - # Use #has_one in the base, and #belongs_to in the associated model. - # - # class Employee < ActiveRecord::Base - # has_one :office - # end - # class Office < ActiveRecord::Base - # belongs_to :employee # foreign key - employee_id - # end - # - # === One-to-many - # - # Use #has_many in the base, and #belongs_to in the associated model. - # - # class Manager < ActiveRecord::Base - # has_many :employees - # end - # class Employee < ActiveRecord::Base - # belongs_to :manager # foreign key - manager_id - # end - # - # === Many-to-many - # - # There are two ways to build a many-to-many relationship. - # - # The first way uses a #has_many association with the <tt>:through</tt> option and a join model, so - # there are two stages of associations. - # - # class Assignment < ActiveRecord::Base - # belongs_to :programmer # foreign key - programmer_id - # belongs_to :project # foreign key - project_id - # end - # class Programmer < ActiveRecord::Base - # has_many :assignments - # has_many :projects, through: :assignments - # end - # class Project < ActiveRecord::Base - # has_many :assignments - # has_many :programmers, through: :assignments - # end - # - # For the second way, use #has_and_belongs_to_many in both models. This requires a join table - # that has no corresponding model or primary key. - # - # class Programmer < ActiveRecord::Base - # has_and_belongs_to_many :projects # foreign keys in the join table - # end - # class Project < ActiveRecord::Base - # has_and_belongs_to_many :programmers # foreign keys in the join table - # end - # - # Choosing which way to build a many-to-many relationship is not always simple. - # If you need to work with the relationship model as its own entity, - # use #has_many <tt>:through</tt>. Use #has_and_belongs_to_many when working with legacy schemas or when - # you never work directly with the relationship itself. - # - # == Is it a #belongs_to or #has_one association? - # - # Both express a 1-1 relationship. The difference is mostly where to place the foreign - # key, which goes on the table for the class declaring the #belongs_to relationship. - # - # class User < ActiveRecord::Base - # # I reference an account. - # belongs_to :account - # end - # - # class Account < ActiveRecord::Base - # # One user references me. - # has_one :user - # end - # - # The tables for these classes could look something like: - # - # CREATE TABLE users ( - # id int NOT NULL auto_increment, - # account_id int default NULL, - # name varchar default NULL, - # PRIMARY KEY (id) - # ) - # - # CREATE TABLE accounts ( - # id int NOT NULL auto_increment, - # name varchar default NULL, - # PRIMARY KEY (id) - # ) - # - # == Unsaved objects and associations - # - # You can manipulate objects and associations before they are saved to the database, but - # there is some special behavior you should be aware of, mostly involving the saving of - # associated objects. - # - # You can set the <tt>:autosave</tt> option on a #has_one, #belongs_to, - # #has_many, or #has_and_belongs_to_many association. Setting it - # to +true+ will _always_ save the members, whereas setting it to +false+ will - # _never_ save the members. More details about <tt>:autosave</tt> option is available at - # AutosaveAssociation. - # - # === One-to-one associations - # - # * Assigning an object to a #has_one association automatically saves that object and - # the object being replaced (if there is one), in order to update their foreign - # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>). - # * If either of these saves fail (due to one of the objects being invalid), an - # ActiveRecord::RecordNotSaved exception is raised and the assignment is - # cancelled. - # * If you wish to assign an object to a #has_one association without saving it, - # use the <tt>#build_association</tt> method (documented below). The object being - # replaced will still be saved to update its foreign key. - # * Assigning an object to a #belongs_to association does not save the object, since - # the foreign key field belongs on the parent. It does not save the parent either. - # - # === Collections - # - # * Adding an object to a collection (#has_many or #has_and_belongs_to_many) automatically - # saves that object, except if the parent object (the owner of the collection) is not yet - # stored in the database. - # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar) - # fails, then <tt>push</tt> returns +false+. - # * If saving fails while replacing the collection (via <tt>association=</tt>), an - # ActiveRecord::RecordNotSaved exception is raised and the assignment is - # cancelled. - # * You can add an object to a collection without automatically saving it by using the - # <tt>collection.build</tt> method (documented below). - # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically - # saved when the parent is saved. - # - # == Customizing the query - # - # \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax - # to customize them. For example, to add a condition: - # - # class Blog < ActiveRecord::Base - # has_many :published_posts, -> { where(published: true) }, class_name: 'Post' - # end - # - # Inside the <tt>-> { ... }</tt> block you can use all of the usual Relation methods. - # - # === Accessing the owner object - # - # Sometimes it is useful to have access to the owner object when building the query. The owner - # is passed as a parameter to the block. For example, the following association would find all - # events that occur on the user's birthday: - # - # class User < ActiveRecord::Base - # has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event' - # end - # - # Note: Joining, eager loading and preloading of these associations is not fully possible. - # These operations happen before instance creation and the scope will be called with a +nil+ argument. - # This can lead to unexpected behavior and is deprecated. - # - # == Association callbacks - # - # Similar to the normal callbacks that hook into the life cycle of an Active Record object, - # you can also define callbacks that get triggered when you add an object to or remove an - # object from an association collection. - # - # class Project - # has_and_belongs_to_many :developers, after_add: :evaluate_velocity - # - # def evaluate_velocity(developer) - # ... - # end - # end - # - # It's possible to stack callbacks by passing them as an array. Example: - # - # class Project - # has_and_belongs_to_many :developers, - # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] - # end - # - # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+. - # - # If any of the +before_add+ callbacks throw an exception, the object will not be - # added to the collection. - # - # Similarly, if any of the +before_remove+ callbacks throw an exception, the object - # will not be removed from the collection. - # - # == Association extensions - # - # The proxy objects that control the access to associations can be extended through anonymous - # modules. This is especially beneficial for adding new finders, creators, and other - # factory-type methods that are only used as part of this association. - # - # class Account < ActiveRecord::Base - # has_many :people do - # def find_or_create_by_name(name) - # first_name, last_name = name.split(" ", 2) - # find_or_create_by(first_name: first_name, last_name: last_name) - # end - # end - # end - # - # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson") - # person.first_name # => "David" - # person.last_name # => "Heinemeier Hansson" - # - # If you need to share the same extensions between many associations, you can use a named - # extension module. - # - # module FindOrCreateByNameExtension - # def find_or_create_by_name(name) - # first_name, last_name = name.split(" ", 2) - # find_or_create_by(first_name: first_name, last_name: last_name) - # end - # end - # - # class Account < ActiveRecord::Base - # has_many :people, -> { extending FindOrCreateByNameExtension } - # end - # - # class Company < ActiveRecord::Base - # has_many :people, -> { extending FindOrCreateByNameExtension } - # end - # - # Some extensions can only be made to work with knowledge of the association's internals. - # Extensions can access relevant state using the following methods (where +items+ is the - # name of the association): - # - # * <tt>record.association(:items).owner</tt> - Returns the object the association is part of. - # * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association. - # * <tt>record.association(:items).target</tt> - Returns the associated object for #belongs_to and #has_one, or - # the collection of associated objects for #has_many and #has_and_belongs_to_many. - # - # However, inside the actual extension code, you will not have access to the <tt>record</tt> as - # above. In this case, you can access <tt>proxy_association</tt>. For example, - # <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return - # the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside - # association extensions. - # - # == Association Join Models - # - # Has Many associations can be configured with the <tt>:through</tt> option to use an - # explicit join model to retrieve the data. This operates similarly to a - # #has_and_belongs_to_many association. The advantage is that you're able to add validations, - # callbacks, and extra attributes on the join model. Consider the following schema: - # - # class Author < ActiveRecord::Base - # has_many :authorships - # has_many :books, through: :authorships - # end - # - # class Authorship < ActiveRecord::Base - # belongs_to :author - # belongs_to :book - # end - # - # @author = Author.first - # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to - # @author.books # selects all books by using the Authorship join model - # - # You can also go through a #has_many association on the join model: - # - # class Firm < ActiveRecord::Base - # has_many :clients - # has_many :invoices, through: :clients - # end - # - # class Client < ActiveRecord::Base - # belongs_to :firm - # has_many :invoices - # end - # - # class Invoice < ActiveRecord::Base - # belongs_to :client - # end - # - # @firm = Firm.first - # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm - # @firm.invoices # selects all invoices by going through the Client join model - # - # Similarly you can go through a #has_one association on the join model: - # - # class Group < ActiveRecord::Base - # has_many :users - # has_many :avatars, through: :users - # end - # - # class User < ActiveRecord::Base - # belongs_to :group - # has_one :avatar - # end - # - # class Avatar < ActiveRecord::Base - # belongs_to :user - # end - # - # @group = Group.first - # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group - # @group.avatars # selects all avatars by going through the User join model. - # - # An important caveat with going through #has_one or #has_many associations on the - # join model is that these associations are *read-only*. For example, the following - # would not work following the previous example: - # - # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around - # @group.avatars.delete(@group.avatars.last) # so would this - # - # == Setting Inverses - # - # If you are using a #belongs_to on the join model, it is a good idea to set the - # <tt>:inverse_of</tt> option on the #belongs_to, which will mean that the following example - # works correctly (where <tt>tags</tt> is a #has_many <tt>:through</tt> association): - # - # @post = Post.first - # @tag = @post.tags.build name: "ruby" - # @tag.save - # - # The last line ought to save the through record (a <tt>Tagging</tt>). This will only work if the - # <tt>:inverse_of</tt> is set: - # - # class Tagging < ActiveRecord::Base - # belongs_to :post - # belongs_to :tag, inverse_of: :taggings - # end - # - # If you do not set the <tt>:inverse_of</tt> record, the association will - # do its best to match itself up with the correct inverse. Automatic - # inverse detection only works on #has_many, #has_one, and - # #belongs_to associations. - # - # Extra options on the associations, as defined in the - # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will - # also prevent the association's inverse from being found automatically. - # - # The automatic guessing of the inverse association uses a heuristic based - # on the name of the class, so it may not work for all associations, - # especially the ones with non-standard names. - # - # You can turn off the automatic detection of inverse associations by setting - # the <tt>:inverse_of</tt> option to <tt>false</tt> like so: - # - # class Tagging < ActiveRecord::Base - # belongs_to :tag, inverse_of: false - # end - # - # == Nested \Associations - # - # You can actually specify *any* association with the <tt>:through</tt> option, including an - # association which has a <tt>:through</tt> option itself. For example: - # - # class Author < ActiveRecord::Base - # has_many :posts - # has_many :comments, through: :posts - # has_many :commenters, through: :comments - # end - # - # class Post < ActiveRecord::Base - # has_many :comments - # end - # - # class Comment < ActiveRecord::Base - # belongs_to :commenter - # end - # - # @author = Author.first - # @author.commenters # => People who commented on posts written by the author - # - # An equivalent way of setting up this association this would be: - # - # class Author < ActiveRecord::Base - # has_many :posts - # has_many :commenters, through: :posts - # end - # - # class Post < ActiveRecord::Base - # has_many :comments - # has_many :commenters, through: :comments - # end - # - # class Comment < ActiveRecord::Base - # belongs_to :commenter - # end - # - # When using a nested association, you will not be able to modify the association because there - # is not enough information to know what modification to make. For example, if you tried to - # add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the - # intermediate <tt>Post</tt> and <tt>Comment</tt> objects. - # - # == Polymorphic \Associations - # - # Polymorphic associations on models are not restricted on what types of models they - # can be associated with. Rather, they specify an interface that a #has_many association - # must adhere to. - # - # class Asset < ActiveRecord::Base - # belongs_to :attachable, polymorphic: true - # end - # - # class Post < ActiveRecord::Base - # has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use. - # end - # - # @asset.attachable = @post - # - # This works by using a type column in addition to a foreign key to specify the associated - # record. In the Asset example, you'd need an +attachable_id+ integer column and an - # +attachable_type+ string column. - # - # Using polymorphic associations in combination with single table inheritance (STI) is - # a little tricky. In order for the associations to work as expected, ensure that you - # store the base model for the STI models in the type column of the polymorphic - # association. To continue with the asset example above, suppose there are guest posts - # and member posts that use the posts table for STI. In this case, there must be a +type+ - # column in the posts table. - # - # Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+. - # The +class_name+ of the +attachable+ is passed as a String. - # - # class Asset < ActiveRecord::Base - # belongs_to :attachable, polymorphic: true - # - # def attachable_type=(class_name) - # super(class_name.constantize.base_class.to_s) - # end - # end - # - # class Post < ActiveRecord::Base - # # because we store "Post" in attachable_type now dependent: :destroy will work - # has_many :assets, as: :attachable, dependent: :destroy - # end - # - # class GuestPost < Post - # end - # - # class MemberPost < Post - # end - # - # == Caching - # - # All of the methods are built on a simple caching principle that will keep the result - # of the last query around unless specifically instructed not to. The cache is even - # shared across methods to make it even cheaper to use the macro-added methods without - # worrying too much about performance at the first go. - # - # project.milestones # fetches milestones from the database - # project.milestones.size # uses the milestone cache - # project.milestones.empty? # uses the milestone cache - # project.milestones(true).size # fetches milestones from the database - # project.milestones # uses the milestone cache - # - # == Eager loading of associations - # - # Eager loading is a way to find objects of a certain class and a number of named associations. - # It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100 - # posts that each need to display their author triggers 101 database queries. Through the - # use of eager loading, the number of queries will be reduced from 101 to 2. - # - # class Post < ActiveRecord::Base - # belongs_to :author - # has_many :comments - # end - # - # Consider the following loop using the class above: - # - # Post.all.each do |post| - # puts "Post: " + post.title - # puts "Written by: " + post.author.name - # puts "Last comment on: " + post.comments.first.created_on - # end - # - # To iterate over these one hundred posts, we'll generate 201 database queries. Let's - # first just optimize it for retrieving the author: - # - # Post.includes(:author).each do |post| - # - # This references the name of the #belongs_to association that also used the <tt>:author</tt> - # symbol. After loading the posts, +find+ will collect the +author_id+ from each one and load - # all of the referenced authors with one query. Doing so will cut down the number of queries - # from 201 to 102. - # - # We can improve upon the situation further by referencing both associations in the finder with: - # - # Post.includes(:author, :comments).each do |post| - # - # This will load all comments with a single query. This reduces the total number of queries - # to 3. In general, the number of queries will be 1 plus the number of associations - # named (except if some of the associations are polymorphic #belongs_to - see below). - # - # To include a deep hierarchy of associations, use a hash: - # - # Post.includes(:author, { comments: { author: :gravatar } }).each do |post| - # - # The above code will load all the comments and all of their associated - # authors and gravatars. You can mix and match any combination of symbols, - # arrays, and hashes to retrieve the associations you want to load. - # - # All of this power shouldn't fool you into thinking that you can pull out huge amounts - # of data with no performance penalty just because you've reduced the number of queries. - # The database still needs to send all the data to Active Record and it still needs to - # be processed. So it's no catch-all for performance problems, but it's a great way to - # cut down on the number of queries in a situation as the one described above. - # - # Since only one table is loaded at a time, conditions or orders cannot reference tables - # other than the main one. If this is the case, Active Record falls back to the previously - # used <tt>LEFT OUTER JOIN</tt> based strategy. For example: - # - # Post.includes([:author, :comments]).where(['comments.approved = ?', true]) - # - # This will result in a single SQL query with joins along the lines of: - # <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and - # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions - # like this can have unintended consequences. - # In the above example, posts with no approved comments are not returned at all because - # the conditions apply to the SQL statement as a whole and not just to the association. - # - # You must disambiguate column references for this fallback to happen, for example - # <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not. - # - # If you want to load all posts (including posts with no approved comments), then write - # your own <tt>LEFT OUTER JOIN</tt> query using <tt>ON</tt>: - # - # Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'") - # - # In this case, it is usually more natural to include an association which has conditions defined on it: - # - # class Post < ActiveRecord::Base - # has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment' - # end - # - # Post.includes(:approved_comments) - # - # This will load posts and eager load the +approved_comments+ association, which contains - # only those comments that have been approved. - # - # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored, - # returning all the associated objects: - # - # class Picture < ActiveRecord::Base - # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment' - # end - # - # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments. - # - # Eager loading is supported with polymorphic associations. - # - # class Address < ActiveRecord::Base - # belongs_to :addressable, polymorphic: true - # end - # - # A call that tries to eager load the addressable model - # - # Address.includes(:addressable) - # - # This will execute one query to load the addresses and load the addressables with one - # query per addressable type. - # For example, if all the addressables are either of class Person or Company, then a total - # of 3 queries will be executed. The list of addressable types to load is determined on - # the back of the addresses loaded. This is not supported if Active Record has to fallback - # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. - # The reason is that the parent model's type is a column value so its corresponding table - # name cannot be put in the +FROM+/+JOIN+ clauses of that query. - # - # == Table Aliasing - # - # Active Record uses table aliasing in the case that a table is referenced multiple times - # in a join. If a table is referenced only once, the standard table name is used. The - # second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. - # Indexes are appended for any more successive uses of the table name. - # - # Post.joins(:comments) - # # => SELECT ... FROM posts INNER JOIN comments ON ... - # Post.joins(:special_comments) # STI - # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment' - # Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name - # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts - # - # Acts as tree example: - # - # TreeMixin.joins(:children) - # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... - # TreeMixin.joins(children: :parent) - # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... - # INNER JOIN parents_mixins ... - # TreeMixin.joins(children: {parent: :children}) - # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... - # INNER JOIN parents_mixins ... - # INNER JOIN mixins childrens_mixins_2 - # - # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix: - # - # Post.joins(:categories) - # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... - # Post.joins(categories: :posts) - # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... - # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories - # Post.joins(categories: {posts: :categories}) - # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... - # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories - # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2 - # - # If you wish to specify your own custom joins using ActiveRecord::QueryMethods#joins method, those table - # names will take precedence over the eager associations: - # - # Post.joins(:comments).joins("inner join comments ...") - # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ... - # Post.joins(:comments, :special_comments).joins("inner join comments ...") - # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ... - # INNER JOIN comments special_comments_posts ... - # INNER JOIN comments ... - # - # Table aliases are automatically truncated according to the maximum length of table identifiers - # according to the specific database. - # - # == Modules - # - # By default, associations will look for objects within the current module scope. Consider: - # - # module MyApplication - # module Business - # class Firm < ActiveRecord::Base - # has_many :clients - # end - # - # class Client < ActiveRecord::Base; end - # end - # end - # - # When <tt>Firm#clients</tt> is called, it will in turn call - # <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>. - # If you want to associate with a class in another module scope, this can be done by - # specifying the complete class name. - # - # module MyApplication - # module Business - # class Firm < ActiveRecord::Base; end - # end - # - # module Billing - # class Account < ActiveRecord::Base - # belongs_to :firm, class_name: "MyApplication::Business::Firm" - # end - # end - # end - # - # == Bi-directional associations - # - # When you specify an association, there is usually an association on the associated model - # that specifies the same relationship in reverse. For example, with the following models: - # - # class Dungeon < ActiveRecord::Base - # has_many :traps - # has_one :evil_wizard - # end - # - # class Trap < ActiveRecord::Base - # belongs_to :dungeon - # end - # - # class EvilWizard < ActiveRecord::Base - # belongs_to :dungeon - # end - # - # The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are - # the inverse of each other, and the inverse of the +dungeon+ association on +EvilWizard+ - # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default, - # Active Record can guess the inverse of the association based on the name - # of the class. The result is the following: - # - # d = Dungeon.first - # t = d.traps.first - # d.object_id == t.dungeon.object_id # => true - # - # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to - # the same in-memory instance since the association matches the name of the class. - # The result would be the same if we added +:inverse_of+ to our model definitions: - # - # class Dungeon < ActiveRecord::Base - # has_many :traps, inverse_of: :dungeon - # has_one :evil_wizard, inverse_of: :dungeon - # end - # - # class Trap < ActiveRecord::Base - # belongs_to :dungeon, inverse_of: :traps - # end - # - # class EvilWizard < ActiveRecord::Base - # belongs_to :dungeon, inverse_of: :evil_wizard - # end - # - # There are limitations to <tt>:inverse_of</tt> support: - # - # * does not work with <tt>:through</tt> associations. - # * does not work with <tt>:polymorphic</tt> associations. - # * inverse associations for #belongs_to associations #has_many are ignored. - # - # For more information, see the documentation for the +:inverse_of+ option. - # - # == Deleting from associations - # - # === Dependent associations - # - # #has_many, #has_one, and #belongs_to associations support the <tt>:dependent</tt> option. - # This allows you to specify that associated records should be deleted when the owner is - # deleted. - # - # For example: - # - # class Author - # has_many :posts, dependent: :destroy - # end - # Author.find(1).destroy # => Will destroy all of the author's posts, too - # - # The <tt>:dependent</tt> option can have different values which specify how the deletion - # is done. For more information, see the documentation for this option on the different - # specific association types. When no option is given, the behavior is to do nothing - # with the associated records when destroying a record. - # - # Note that <tt>:dependent</tt> is implemented using Rails' callback - # system, which works by processing callbacks in order. Therefore, other - # callbacks declared either before or after the <tt>:dependent</tt> option - # can affect what it does. - # - # Note that <tt>:dependent</tt> option is ignored for #has_one <tt>:through</tt> associations. - # - # === Delete or destroy? - # - # #has_many and #has_and_belongs_to_many associations have the methods <tt>destroy</tt>, - # <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>. - # - # For #has_and_belongs_to_many, <tt>delete</tt> and <tt>destroy</tt> are the same: they - # cause the records in the join table to be removed. - # - # For #has_many, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the - # record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either - # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or - # if no <tt>:dependent</tt> option is given, then it will follow the default strategy. - # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for - # #has_many <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete - # the join records, without running their callbacks). - # - # There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that - # it returns the association rather than the records which have been deleted. - # - # === What gets deleted? - # - # There is a potential pitfall here: #has_and_belongs_to_many and #has_many <tt>:through</tt> - # associations have records in join tables, as well as the associated records. So when we - # call one of these deletion methods, what exactly should be deleted? - # - # The answer is that it is assumed that deletion on an association is about removing the - # <i>link</i> between the owner and the associated object(s), rather than necessarily the - # associated objects themselves. So with #has_and_belongs_to_many and #has_many - # <tt>:through</tt>, the join records will be deleted, but the associated records won't. - # - # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt> - # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself - # to be removed from the database. - # - # However, there are examples where this strategy doesn't make sense. For example, suppose - # a person has many projects, and each project has many tasks. If we deleted one of a person's - # tasks, we would probably not want the project to be deleted. In this scenario, the delete method - # won't actually work: it can only be used if the association on the join model is a - # #belongs_to. In other situations you are expected to perform operations directly on - # either the associated records or the <tt>:through</tt> association. - # - # With a regular #has_many there is no distinction between the "associated records" - # and the "link", so there is only one choice for what gets deleted. - # - # With #has_and_belongs_to_many and #has_many <tt>:through</tt>, if you want to delete the - # associated records themselves, you can always do something along the lines of - # <tt>person.tasks.each(&:destroy)</tt>. - # - # == Type safety with ActiveRecord::AssociationTypeMismatch - # - # If you attempt to assign an object to an association that doesn't match the inferred - # or specified <tt>:class_name</tt>, you'll get an ActiveRecord::AssociationTypeMismatch. - # - # == Options - # - # All of the association macros can be specialized through options. This makes cases - # more complex than the simple and guessable ones possible. + # \Associations are a set of macro-like class methods for tying objects together through + # foreign keys. They express relationships like "Project has one Project Manager" + # or "Project belongs to a Portfolio". Each macro adds a number of methods to the + # class which are specialized according to the collection or association symbol and the + # options hash. It works much the same way as Ruby's own <tt>attr*</tt> + # methods. + # + # class Project < ActiveRecord::Base + # belongs_to :portfolio + # has_one :project_manager + # has_many :milestones + # has_and_belongs_to_many :categories + # end + # + # The project class now has the following methods (and more) to ease the traversal and + # manipulation of its relationships: + # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt> + # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt> + # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt> + # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),</tt> + # <tt>Project#milestones.build, Project#milestones.create</tt> + # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt> + # <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt> + # + # === A word of warning + # + # Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveRecord::Core] of + # <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to + # its model, using an association with the same name as one provided by <tt>ActiveRecord::Base</tt> will override the method inherited through <tt>ActiveRecord::Base</tt> and will break things. + # For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of <tt>ActiveRecord::Base</tt> instance methods. + # + # == Auto-generated methods + # See also Instance Public methods below for more details. + # + # === Singular associations (one-to-one) + # | | belongs_to | + # generated methods | belongs_to | :polymorphic | has_one + # ----------------------------------+------------+--------------+--------- + # other(force_reload=false) | X | X | X + # other=(other) | X | X | X + # build_other(attributes={}) | X | | X + # create_other(attributes={}) | X | | X + # create_other!(attributes={}) | X | | X + # + # === Collection associations (one-to-many / many-to-many) + # | | | has_many + # generated methods | habtm | has_many | :through + # ----------------------------------+-------+----------+---------- + # others(force_reload=false) | X | X | X + # others=(other,other,...) | X | X | X + # other_ids | X | X | X + # other_ids=(id,id,...) | X | X | X + # others<< | X | X | X + # others.push | X | X | X + # others.concat | X | X | X + # others.build(attributes={}) | X | X | X + # others.create(attributes={}) | X | X | X + # others.create!(attributes={}) | X | X | X + # others.size | X | X | X + # others.length | X | X | X + # others.count | X | X | X + # others.sum(*args) | X | X | X + # others.empty? | X | X | X + # others.clear | X | X | X + # others.delete(other,other,...) | X | X | X + # others.delete_all | X | X | X + # others.destroy(other,other,...) | X | X | X + # others.destroy_all | X | X | X + # others.find(*args) | X | X | X + # others.exists? | X | X | X + # others.distinct | X | X | X + # others.reset | X | X | X + # + # === Overriding generated methods + # + # Association methods are generated in a module that is included into the model class, + # which allows you to easily override with your own methods and call the original + # generated method with +super+. For example: + # + # class Car < ActiveRecord::Base + # belongs_to :owner + # belongs_to :old_owner + # def owner=(new_owner) + # self.old_owner = self.owner + # super + # end + # end + # + # If your model class is <tt>Project</tt>, then the module is + # named <tt>Project::GeneratedAssociationMethods</tt>. The +GeneratedAssociationMethods+ module is + # included in the model class immediately after the (anonymous) generated attributes methods + # module, meaning an association will override the methods for an attribute with the same name. + # + # == Cardinality and associations + # + # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many + # relationships between models. Each model uses an association to describe its role in + # the relation. The #belongs_to association is always used in the model that has + # the foreign key. + # + # === One-to-one + # + # Use #has_one in the base, and #belongs_to in the associated model. + # + # class Employee < ActiveRecord::Base + # has_one :office + # end + # class Office < ActiveRecord::Base + # belongs_to :employee # foreign key - employee_id + # end + # + # === One-to-many + # + # Use #has_many in the base, and #belongs_to in the associated model. + # + # class Manager < ActiveRecord::Base + # has_many :employees + # end + # class Employee < ActiveRecord::Base + # belongs_to :manager # foreign key - manager_id + # end + # + # === Many-to-many + # + # There are two ways to build a many-to-many relationship. + # + # The first way uses a #has_many association with the <tt>:through</tt> option and a join model, so + # there are two stages of associations. + # + # class Assignment < ActiveRecord::Base + # belongs_to :programmer # foreign key - programmer_id + # belongs_to :project # foreign key - project_id + # end + # class Programmer < ActiveRecord::Base + # has_many :assignments + # has_many :projects, through: :assignments + # end + # class Project < ActiveRecord::Base + # has_many :assignments + # has_many :programmers, through: :assignments + # end + # + # For the second way, use #has_and_belongs_to_many in both models. This requires a join table + # that has no corresponding model or primary key. + # + # class Programmer < ActiveRecord::Base + # has_and_belongs_to_many :projects # foreign keys in the join table + # end + # class Project < ActiveRecord::Base + # has_and_belongs_to_many :programmers # foreign keys in the join table + # end + # + # Choosing which way to build a many-to-many relationship is not always simple. + # If you need to work with the relationship model as its own entity, + # use #has_many <tt>:through</tt>. Use #has_and_belongs_to_many when working with legacy schemas or when + # you never work directly with the relationship itself. + # + # == Is it a #belongs_to or #has_one association? + # + # Both express a 1-1 relationship. The difference is mostly where to place the foreign + # key, which goes on the table for the class declaring the #belongs_to relationship. + # + # class User < ActiveRecord::Base + # # I reference an account. + # belongs_to :account + # end + # + # class Account < ActiveRecord::Base + # # One user references me. + # has_one :user + # end + # + # The tables for these classes could look something like: + # + # CREATE TABLE users ( + # id int NOT NULL auto_increment, + # account_id int default NULL, + # name varchar default NULL, + # PRIMARY KEY (id) + # ) + # + # CREATE TABLE accounts ( + # id int NOT NULL auto_increment, + # name varchar default NULL, + # PRIMARY KEY (id) + # ) + # + # == Unsaved objects and associations + # + # You can manipulate objects and associations before they are saved to the database, but + # there is some special behavior you should be aware of, mostly involving the saving of + # associated objects. + # + # You can set the <tt>:autosave</tt> option on a #has_one, #belongs_to, + # #has_many, or #has_and_belongs_to_many association. Setting it + # to +true+ will _always_ save the members, whereas setting it to +false+ will + # _never_ save the members. More details about <tt>:autosave</tt> option is available at + # AutosaveAssociation. + # + # === One-to-one associations + # + # * Assigning an object to a #has_one association automatically saves that object and + # the object being replaced (if there is one), in order to update their foreign + # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>). + # * If either of these saves fail (due to one of the objects being invalid), an + # ActiveRecord::RecordNotSaved exception is raised and the assignment is + # cancelled. + # * If you wish to assign an object to a #has_one association without saving it, + # use the <tt>#build_association</tt> method (documented below). The object being + # replaced will still be saved to update its foreign key. + # * Assigning an object to a #belongs_to association does not save the object, since + # the foreign key field belongs on the parent. It does not save the parent either. + # + # === Collections + # + # * Adding an object to a collection (#has_many or #has_and_belongs_to_many) automatically + # saves that object, except if the parent object (the owner of the collection) is not yet + # stored in the database. + # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar) + # fails, then <tt>push</tt> returns +false+. + # * If saving fails while replacing the collection (via <tt>association=</tt>), an + # ActiveRecord::RecordNotSaved exception is raised and the assignment is + # cancelled. + # * You can add an object to a collection without automatically saving it by using the + # <tt>collection.build</tt> method (documented below). + # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically + # saved when the parent is saved. + # + # == Customizing the query + # + # \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax + # to customize them. For example, to add a condition: + # + # class Blog < ActiveRecord::Base + # has_many :published_posts, -> { where(published: true) }, class_name: 'Post' + # end + # + # Inside the <tt>-> { ... }</tt> block you can use all of the usual Relation methods. + # + # === Accessing the owner object + # + # Sometimes it is useful to have access to the owner object when building the query. The owner + # is passed as a parameter to the block. For example, the following association would find all + # events that occur on the user's birthday: + # + # class User < ActiveRecord::Base + # has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event' + # end + # + # Note: Joining, eager loading and preloading of these associations is not fully possible. + # These operations happen before instance creation and the scope will be called with a +nil+ argument. + # This can lead to unexpected behavior and is deprecated. + # + # == Association callbacks + # + # Similar to the normal callbacks that hook into the life cycle of an Active Record object, + # you can also define callbacks that get triggered when you add an object to or remove an + # object from an association collection. + # + # class Project + # has_and_belongs_to_many :developers, after_add: :evaluate_velocity + # + # def evaluate_velocity(developer) + # ... + # end + # end + # + # It's possible to stack callbacks by passing them as an array. Example: + # + # class Project + # has_and_belongs_to_many :developers, + # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] + # end + # + # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+. + # + # If any of the +before_add+ callbacks throw an exception, the object will not be + # added to the collection. + # + # Similarly, if any of the +before_remove+ callbacks throw an exception, the object + # will not be removed from the collection. + # + # == Association extensions + # + # The proxy objects that control the access to associations can be extended through anonymous + # modules. This is especially beneficial for adding new finders, creators, and other + # factory-type methods that are only used as part of this association. + # + # class Account < ActiveRecord::Base + # has_many :people do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # end + # + # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson") + # person.first_name # => "David" + # person.last_name # => "Heinemeier Hansson" + # + # If you need to share the same extensions between many associations, you can use a named + # extension module. + # + # module FindOrCreateByNameExtension + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # + # class Account < ActiveRecord::Base + # has_many :people, -> { extending FindOrCreateByNameExtension } + # end + # + # class Company < ActiveRecord::Base + # has_many :people, -> { extending FindOrCreateByNameExtension } + # end + # + # Some extensions can only be made to work with knowledge of the association's internals. + # Extensions can access relevant state using the following methods (where +items+ is the + # name of the association): + # + # * <tt>record.association(:items).owner</tt> - Returns the object the association is part of. + # * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association. + # * <tt>record.association(:items).target</tt> - Returns the associated object for #belongs_to and #has_one, or + # the collection of associated objects for #has_many and #has_and_belongs_to_many. + # + # However, inside the actual extension code, you will not have access to the <tt>record</tt> as + # above. In this case, you can access <tt>proxy_association</tt>. For example, + # <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return + # the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside + # association extensions. + # + # == Association Join Models + # + # Has Many associations can be configured with the <tt>:through</tt> option to use an + # explicit join model to retrieve the data. This operates similarly to a + # #has_and_belongs_to_many association. The advantage is that you're able to add validations, + # callbacks, and extra attributes on the join model. Consider the following schema: + # + # class Author < ActiveRecord::Base + # has_many :authorships + # has_many :books, through: :authorships + # end + # + # class Authorship < ActiveRecord::Base + # belongs_to :author + # belongs_to :book + # end + # + # @author = Author.first + # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to + # @author.books # selects all books by using the Authorship join model + # + # You can also go through a #has_many association on the join model: + # + # class Firm < ActiveRecord::Base + # has_many :clients + # has_many :invoices, through: :clients + # end + # + # class Client < ActiveRecord::Base + # belongs_to :firm + # has_many :invoices + # end + # + # class Invoice < ActiveRecord::Base + # belongs_to :client + # end + # + # @firm = Firm.first + # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm + # @firm.invoices # selects all invoices by going through the Client join model + # + # Similarly you can go through a #has_one association on the join model: + # + # class Group < ActiveRecord::Base + # has_many :users + # has_many :avatars, through: :users + # end + # + # class User < ActiveRecord::Base + # belongs_to :group + # has_one :avatar + # end + # + # class Avatar < ActiveRecord::Base + # belongs_to :user + # end + # + # @group = Group.first + # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group + # @group.avatars # selects all avatars by going through the User join model. + # + # An important caveat with going through #has_one or #has_many associations on the + # join model is that these associations are *read-only*. For example, the following + # would not work following the previous example: + # + # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around + # @group.avatars.delete(@group.avatars.last) # so would this + # + # == Setting Inverses + # + # If you are using a #belongs_to on the join model, it is a good idea to set the + # <tt>:inverse_of</tt> option on the #belongs_to, which will mean that the following example + # works correctly (where <tt>tags</tt> is a #has_many <tt>:through</tt> association): + # + # @post = Post.first + # @tag = @post.tags.build name: "ruby" + # @tag.save + # + # The last line ought to save the through record (a <tt>Tagging</tt>). This will only work if the + # <tt>:inverse_of</tt> is set: + # + # class Tagging < ActiveRecord::Base + # belongs_to :post + # belongs_to :tag, inverse_of: :taggings + # end + # + # If you do not set the <tt>:inverse_of</tt> record, the association will + # do its best to match itself up with the correct inverse. Automatic + # inverse detection only works on #has_many, #has_one, and + # #belongs_to associations. + # + # Extra options on the associations, as defined in the + # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will + # also prevent the association's inverse from being found automatically. + # + # The automatic guessing of the inverse association uses a heuristic based + # on the name of the class, so it may not work for all associations, + # especially the ones with non-standard names. + # + # You can turn off the automatic detection of inverse associations by setting + # the <tt>:inverse_of</tt> option to <tt>false</tt> like so: + # + # class Tagging < ActiveRecord::Base + # belongs_to :tag, inverse_of: false + # end + # + # == Nested \Associations + # + # You can actually specify *any* association with the <tt>:through</tt> option, including an + # association which has a <tt>:through</tt> option itself. For example: + # + # class Author < ActiveRecord::Base + # has_many :posts + # has_many :comments, through: :posts + # has_many :commenters, through: :comments + # end + # + # class Post < ActiveRecord::Base + # has_many :comments + # end + # + # class Comment < ActiveRecord::Base + # belongs_to :commenter + # end + # + # @author = Author.first + # @author.commenters # => People who commented on posts written by the author + # + # An equivalent way of setting up this association this would be: + # + # class Author < ActiveRecord::Base + # has_many :posts + # has_many :commenters, through: :posts + # end + # + # class Post < ActiveRecord::Base + # has_many :comments + # has_many :commenters, through: :comments + # end + # + # class Comment < ActiveRecord::Base + # belongs_to :commenter + # end + # + # When using a nested association, you will not be able to modify the association because there + # is not enough information to know what modification to make. For example, if you tried to + # add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the + # intermediate <tt>Post</tt> and <tt>Comment</tt> objects. + # + # == Polymorphic \Associations + # + # Polymorphic associations on models are not restricted on what types of models they + # can be associated with. Rather, they specify an interface that a #has_many association + # must adhere to. + # + # class Asset < ActiveRecord::Base + # belongs_to :attachable, polymorphic: true + # end + # + # class Post < ActiveRecord::Base + # has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use. + # end + # + # @asset.attachable = @post + # + # This works by using a type column in addition to a foreign key to specify the associated + # record. In the Asset example, you'd need an +attachable_id+ integer column and an + # +attachable_type+ string column. + # + # Using polymorphic associations in combination with single table inheritance (STI) is + # a little tricky. In order for the associations to work as expected, ensure that you + # store the base model for the STI models in the type column of the polymorphic + # association. To continue with the asset example above, suppose there are guest posts + # and member posts that use the posts table for STI. In this case, there must be a +type+ + # column in the posts table. + # + # Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+. + # The +class_name+ of the +attachable+ is passed as a String. + # + # class Asset < ActiveRecord::Base + # belongs_to :attachable, polymorphic: true + # + # def attachable_type=(class_name) + # super(class_name.constantize.base_class.to_s) + # end + # end + # + # class Post < ActiveRecord::Base + # # because we store "Post" in attachable_type now dependent: :destroy will work + # has_many :assets, as: :attachable, dependent: :destroy + # end + # + # class GuestPost < Post + # end + # + # class MemberPost < Post + # end + # + # == Caching + # + # All of the methods are built on a simple caching principle that will keep the result + # of the last query around unless specifically instructed not to. The cache is even + # shared across methods to make it even cheaper to use the macro-added methods without + # worrying too much about performance at the first go. + # + # project.milestones # fetches milestones from the database + # project.milestones.size # uses the milestone cache + # project.milestones.empty? # uses the milestone cache + # project.milestones(true).size # fetches milestones from the database + # project.milestones # uses the milestone cache + # + # == Eager loading of associations + # + # Eager loading is a way to find objects of a certain class and a number of named associations. + # It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100 + # posts that each need to display their author triggers 101 database queries. Through the + # use of eager loading, the number of queries will be reduced from 101 to 2. + # + # class Post < ActiveRecord::Base + # belongs_to :author + # has_many :comments + # end + # + # Consider the following loop using the class above: + # + # Post.all.each do |post| + # puts "Post: " + post.title + # puts "Written by: " + post.author.name + # puts "Last comment on: " + post.comments.first.created_on + # end + # + # To iterate over these one hundred posts, we'll generate 201 database queries. Let's + # first just optimize it for retrieving the author: + # + # Post.includes(:author).each do |post| + # + # This references the name of the #belongs_to association that also used the <tt>:author</tt> + # symbol. After loading the posts, +find+ will collect the +author_id+ from each one and load + # all of the referenced authors with one query. Doing so will cut down the number of queries + # from 201 to 102. + # + # We can improve upon the situation further by referencing both associations in the finder with: + # + # Post.includes(:author, :comments).each do |post| + # + # This will load all comments with a single query. This reduces the total number of queries + # to 3. In general, the number of queries will be 1 plus the number of associations + # named (except if some of the associations are polymorphic #belongs_to - see below). + # + # To include a deep hierarchy of associations, use a hash: + # + # Post.includes(:author, { comments: { author: :gravatar } }).each do |post| + # + # The above code will load all the comments and all of their associated + # authors and gravatars. You can mix and match any combination of symbols, + # arrays, and hashes to retrieve the associations you want to load. + # + # All of this power shouldn't fool you into thinking that you can pull out huge amounts + # of data with no performance penalty just because you've reduced the number of queries. + # The database still needs to send all the data to Active Record and it still needs to + # be processed. So it's no catch-all for performance problems, but it's a great way to + # cut down on the number of queries in a situation as the one described above. + # + # Since only one table is loaded at a time, conditions or orders cannot reference tables + # other than the main one. If this is the case, Active Record falls back to the previously + # used <tt>LEFT OUTER JOIN</tt> based strategy. For example: + # + # Post.includes([:author, :comments]).where(['comments.approved = ?', true]) + # + # This will result in a single SQL query with joins along the lines of: + # <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and + # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions + # like this can have unintended consequences. + # In the above example, posts with no approved comments are not returned at all because + # the conditions apply to the SQL statement as a whole and not just to the association. + # + # You must disambiguate column references for this fallback to happen, for example + # <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not. + # + # If you want to load all posts (including posts with no approved comments), then write + # your own <tt>LEFT OUTER JOIN</tt> query using <tt>ON</tt>: + # + # Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'") + # + # In this case, it is usually more natural to include an association which has conditions defined on it: + # + # class Post < ActiveRecord::Base + # has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment' + # end + # + # Post.includes(:approved_comments) + # + # This will load posts and eager load the +approved_comments+ association, which contains + # only those comments that have been approved. + # + # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored, + # returning all the associated objects: + # + # class Picture < ActiveRecord::Base + # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment' + # end + # + # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments. + # + # Eager loading is supported with polymorphic associations. + # + # class Address < ActiveRecord::Base + # belongs_to :addressable, polymorphic: true + # end + # + # A call that tries to eager load the addressable model + # + # Address.includes(:addressable) + # + # This will execute one query to load the addresses and load the addressables with one + # query per addressable type. + # For example, if all the addressables are either of class Person or Company, then a total + # of 3 queries will be executed. The list of addressable types to load is determined on + # the back of the addresses loaded. This is not supported if Active Record has to fallback + # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. + # The reason is that the parent model's type is a column value so its corresponding table + # name cannot be put in the +FROM+/+JOIN+ clauses of that query. + # + # == Table Aliasing + # + # Active Record uses table aliasing in the case that a table is referenced multiple times + # in a join. If a table is referenced only once, the standard table name is used. The + # second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. + # Indexes are appended for any more successive uses of the table name. + # + # Post.joins(:comments) + # # => SELECT ... FROM posts INNER JOIN comments ON ... + # Post.joins(:special_comments) # STI + # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment' + # Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name + # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts + # + # Acts as tree example: + # + # TreeMixin.joins(:children) + # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... + # TreeMixin.joins(children: :parent) + # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... + # INNER JOIN parents_mixins ... + # TreeMixin.joins(children: {parent: :children}) + # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... + # INNER JOIN parents_mixins ... + # INNER JOIN mixins childrens_mixins_2 + # + # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix: + # + # Post.joins(:categories) + # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... + # Post.joins(categories: :posts) + # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... + # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories + # Post.joins(categories: {posts: :categories}) + # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... + # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories + # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2 + # + # If you wish to specify your own custom joins using ActiveRecord::QueryMethods#joins method, those table + # names will take precedence over the eager associations: + # + # Post.joins(:comments).joins("inner join comments ...") + # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ... + # Post.joins(:comments, :special_comments).joins("inner join comments ...") + # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ... + # INNER JOIN comments special_comments_posts ... + # INNER JOIN comments ... + # + # Table aliases are automatically truncated according to the maximum length of table identifiers + # according to the specific database. + # + # == Modules + # + # By default, associations will look for objects within the current module scope. Consider: + # + # module MyApplication + # module Business + # class Firm < ActiveRecord::Base + # has_many :clients + # end + # + # class Client < ActiveRecord::Base; end + # end + # end + # + # When <tt>Firm#clients</tt> is called, it will in turn call + # <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>. + # If you want to associate with a class in another module scope, this can be done by + # specifying the complete class name. + # + # module MyApplication + # module Business + # class Firm < ActiveRecord::Base; end + # end + # + # module Billing + # class Account < ActiveRecord::Base + # belongs_to :firm, class_name: "MyApplication::Business::Firm" + # end + # end + # end + # + # == Bi-directional associations + # + # When you specify an association, there is usually an association on the associated model + # that specifies the same relationship in reverse. For example, with the following models: + # + # class Dungeon < ActiveRecord::Base + # has_many :traps + # has_one :evil_wizard + # end + # + # class Trap < ActiveRecord::Base + # belongs_to :dungeon + # end + # + # class EvilWizard < ActiveRecord::Base + # belongs_to :dungeon + # end + # + # The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are + # the inverse of each other, and the inverse of the +dungeon+ association on +EvilWizard+ + # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default, + # Active Record can guess the inverse of the association based on the name + # of the class. The result is the following: + # + # d = Dungeon.first + # t = d.traps.first + # d.object_id == t.dungeon.object_id # => true + # + # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to + # the same in-memory instance since the association matches the name of the class. + # The result would be the same if we added +:inverse_of+ to our model definitions: + # + # class Dungeon < ActiveRecord::Base + # has_many :traps, inverse_of: :dungeon + # has_one :evil_wizard, inverse_of: :dungeon + # end + # + # class Trap < ActiveRecord::Base + # belongs_to :dungeon, inverse_of: :traps + # end + # + # class EvilWizard < ActiveRecord::Base + # belongs_to :dungeon, inverse_of: :evil_wizard + # end + # + # There are limitations to <tt>:inverse_of</tt> support: + # + # * does not work with <tt>:through</tt> associations. + # * does not work with <tt>:polymorphic</tt> associations. + # * inverse associations for #belongs_to associations #has_many are ignored. + # + # For more information, see the documentation for the +:inverse_of+ option. + # + # == Deleting from associations + # + # === Dependent associations + # + # #has_many, #has_one, and #belongs_to associations support the <tt>:dependent</tt> option. + # This allows you to specify that associated records should be deleted when the owner is + # deleted. + # + # For example: + # + # class Author + # has_many :posts, dependent: :destroy + # end + # Author.find(1).destroy # => Will destroy all of the author's posts, too + # + # The <tt>:dependent</tt> option can have different values which specify how the deletion + # is done. For more information, see the documentation for this option on the different + # specific association types. When no option is given, the behavior is to do nothing + # with the associated records when destroying a record. + # + # Note that <tt>:dependent</tt> is implemented using Rails' callback + # system, which works by processing callbacks in order. Therefore, other + # callbacks declared either before or after the <tt>:dependent</tt> option + # can affect what it does. + # + # Note that <tt>:dependent</tt> option is ignored for #has_one <tt>:through</tt> associations. + # + # === Delete or destroy? + # + # #has_many and #has_and_belongs_to_many associations have the methods <tt>destroy</tt>, + # <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>. + # + # For #has_and_belongs_to_many, <tt>delete</tt> and <tt>destroy</tt> are the same: they + # cause the records in the join table to be removed. + # + # For #has_many, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the + # record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either + # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or + # if no <tt>:dependent</tt> option is given, then it will follow the default strategy. + # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for + # #has_many <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete + # the join records, without running their callbacks). + # + # There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that + # it returns the association rather than the records which have been deleted. + # + # === What gets deleted? + # + # There is a potential pitfall here: #has_and_belongs_to_many and #has_many <tt>:through</tt> + # associations have records in join tables, as well as the associated records. So when we + # call one of these deletion methods, what exactly should be deleted? + # + # The answer is that it is assumed that deletion on an association is about removing the + # <i>link</i> between the owner and the associated object(s), rather than necessarily the + # associated objects themselves. So with #has_and_belongs_to_many and #has_many + # <tt>:through</tt>, the join records will be deleted, but the associated records won't. + # + # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt> + # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself + # to be removed from the database. + # + # However, there are examples where this strategy doesn't make sense. For example, suppose + # a person has many projects, and each project has many tasks. If we deleted one of a person's + # tasks, we would probably not want the project to be deleted. In this scenario, the delete method + # won't actually work: it can only be used if the association on the join model is a + # #belongs_to. In other situations you are expected to perform operations directly on + # either the associated records or the <tt>:through</tt> association. + # + # With a regular #has_many there is no distinction between the "associated records" + # and the "link", so there is only one choice for what gets deleted. + # + # With #has_and_belongs_to_many and #has_many <tt>:through</tt>, if you want to delete the + # associated records themselves, you can always do something along the lines of + # <tt>person.tasks.each(&:destroy)</tt>. + # + # == Type safety with ActiveRecord::AssociationTypeMismatch + # + # If you attempt to assign an object to an association that doesn't match the inferred + # or specified <tt>:class_name</tt>, you'll get an ActiveRecord::AssociationTypeMismatch. + # + # == Options + # + # All of the association macros can be specialized through options. This makes cases + # more complex than the simple and guessable ones possible. module ClassMethods # Specifies a one-to-many association. The following methods for retrieval and query of # collections of associated objects will be added: diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 08bd532fb0..e5b3af8252 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -259,7 +259,6 @@ module ActiveRecord seen[record.id] = true unless seen.key?(record.id) end end - alias uniq distinct # Replace this collection with +other_array+. This will perform a diff # and delete/add only records that have changed. diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index a81860e40f..9f77f38b35 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -106,7 +106,7 @@ module ActiveRecord private - # Loads all the given data into +records+ for the +association+. + # Loads all the given data into +records+ for the +association+. def preloaders_on(association, records, scope) case association when Hash @@ -132,18 +132,18 @@ module ActiveRecord } end - # Loads all the given data into +records+ for a singular +association+. - # - # Functions by instantiating a preloader class such as Preloader::HasManyThrough and - # call the +run+ method for each passed in class in the +records+ argument. - # - # Not all records have the same class, so group then preload group on the reflection - # itself so that if various subclass share the same association then we do not split - # them unnecessarily - # - # Additionally, polymorphic belongs_to associations can have multiple associated - # classes, depending on the polymorphic_type field. So we group by the classes as - # well. + # Loads all the given data into +records+ for a singular +association+. + # + # Functions by instantiating a preloader class such as Preloader::HasManyThrough and + # call the +run+ method for each passed in class in the +records+ argument. + # + # Not all records have the same class, so group then preload group on the reflection + # itself so that if various subclass share the same association then we do not split + # them unnecessarily + # + # Additionally, polymorphic belongs_to associations can have multiple associated + # classes, depending on the polymorphic_type field. So we group by the classes as + # well. def preloaders_for_one(association, records, scope) grouped_records(association, records).flat_map do |reflection, klasses| klasses.map do |rhs_klass, rs| @@ -187,10 +187,10 @@ module ActiveRecord def self.owners; []; end end - # Returns a class containing the logic needed to load preload the data - # and attach it to a relation. For example +Preloader::Association+ or - # +Preloader::HasManyThrough+. The class returned implements a `run` method - # that accepts a preloader. + # Returns a class containing the logic needed to load preload the data + # and attach it to a relation. For example +Preloader::Association+ or + # +Preloader::HasManyThrough+. The class returned implements a `run` method + # that accepts a preloader. def preloader_for(reflection, owners, rhs_klass) return NullPreloader unless rhs_klass diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index f3ce52fdfe..9843e0ca66 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -29,17 +29,17 @@ module ActiveRecord assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? end - # Assign any deferred nested attributes after the base attributes have been set. + # Assign any deferred nested attributes after the base attributes have been set. def assign_nested_parameter_attributes(pairs) pairs.each { |k, v| _assign_attribute(k, v) } end - # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done - # by calling new on the column type or aggregation type (through composed_of) object with these parameters. - # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate - # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the - # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and - # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+. + # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done + # by calling new on the column type or aggregation type (through composed_of) object with these parameters. + # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate + # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the + # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and + # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+. def assign_multiparameter_attributes(pairs) execute_callstack_for_multiparameter_attributes( extract_callstack_for_multiparameter_attributes(pairs) diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index ba26a11b39..1ed1deec55 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -416,8 +416,8 @@ module ActiveRecord private - # Returns a Hash of the Arel::Attributes and attribute values that have been - # typecasted for use in an Arel insert/update method. + # Returns a Hash of the Arel::Attributes and attribute values that have been + # typecasted for use in an Arel insert/update method. def arel_attributes_with_values(attribute_names) attrs = {} arel_table = self.class.arel_table @@ -428,15 +428,15 @@ module ActiveRecord attrs end - # Filters the primary keys and readonly attributes from the attribute names. + # Filters the primary keys and readonly attributes from the attribute names. def attributes_for_update(attribute_names) attribute_names.reject do |name| readonly_attribute?(name) end end - # Filters out the primary keys, from the attribute names, when the primary - # key is to be generated (e.g. the id attribute has no value). + # Filters out the primary keys, from the attribute names, when the primary + # key is to be generated (e.g. the id attribute has no value). def attributes_for_create(attribute_names) attribute_names.reject do |name| pk_attribute?(name) && id.nil? diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb index 92f124078c..115eb1ef3f 100644 --- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -63,7 +63,7 @@ module ActiveRecord private - # Handle *_before_type_cast for method_missing. + # Handle *_before_type_cast for method_missing. def attribute_before_type_cast(attribute_name) read_attribute_before_type_cast(attribute_name) end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 131ed8740b..30f7750884 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -6,24 +6,24 @@ module ActiveRecord module ClassMethods protected - # We want to generate the methods via module_eval rather than - # define_method, because define_method is slower on dispatch. - # Evaluating many similar methods may use more memory as the instruction - # sequences are duplicated and cached (in MRI). define_method may - # be slower on dispatch, but if you're careful about the closure - # created, then define_method will consume much less memory. - # - # But sometimes the database might return columns with - # characters that are not allowed in normal method names (like - # 'my_column(omg)'. So to work around this we first define with - # the __temp__ identifier, and then use alias method to rename - # it to what we want. - # - # We are also defining a constant to hold the frozen string of - # the attribute name. Using a constant means that we do not have - # to allocate an object on each call to the attribute method. - # Making it frozen means that it doesn't get duped when used to - # key the @attributes in read_attribute. + # We want to generate the methods via module_eval rather than + # define_method, because define_method is slower on dispatch. + # Evaluating many similar methods may use more memory as the instruction + # sequences are duplicated and cached (in MRI). define_method may + # be slower on dispatch, but if you're careful about the closure + # created, then define_method will consume much less memory. + # + # But sometimes the database might return columns with + # characters that are not allowed in normal method names (like + # 'my_column(omg)'. So to work around this we first define with + # the __temp__ identifier, and then use alias method to rename + # it to what we want. + # + # We are also defining a constant to hold the frozen string of + # the attribute name. Using a constant means that we do not have + # to allocate an object on each call to the attribute method. + # Making it frozen means that it doesn't get duped when used to + # key the @attributes in read_attribute. def define_method_attribute(name) safe_name = name.unpack("h*".freeze).first temp_method = "__temp__#{safe_name}" diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index e9d044ef13..f65c297e01 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -37,7 +37,7 @@ module ActiveRecord end private - # Handle *= for method_missing. + # Handle *= for method_missing. def attribute=(attribute_name, value) write_attribute(attribute_name, value) end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index db84876b0a..d3e0dee731 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -329,26 +329,20 @@ module ActiveRecord return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?) validation_context = self.validation_context unless [:create, :update].include?(self.validation_context) + unless valid = record.valid?(validation_context) if reflection.options[:autosave] indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors) record.errors.each do |attribute, message| - if indexed_attribute - attribute = "#{reflection.name}[#{index}].#{attribute}" - else - attribute = "#{reflection.name}.#{attribute}" - end + attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute) errors[attribute] << message errors[attribute].uniq! end record.errors.details.each_key do |attribute| - if indexed_attribute - reflection_attribute = "#{reflection.name}[#{index}].#{attribute}" - else - reflection_attribute = "#{reflection.name}.#{attribute}" - end + reflection_attribute = + normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym record.errors.details[attribute].each do |error| errors.details[reflection_attribute] << error @@ -362,6 +356,14 @@ module ActiveRecord valid end + def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute) + if indexed_attribute + "#{reflection.name}[#{index}].#{attribute}" + else + "#{reflection.name}.#{attribute}" + end + end + # Is used as a before_save callback to check while saving a collection # association whether or not the parent was a new record before saving. def before_save_collection_association diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index d0c5bbe17d..2d62fd8d50 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -158,33 +158,33 @@ module ActiveRecord @lock.synchronize(&block) end - # Test if the queue currently contains any elements. + # Test if the queue currently contains any elements. def any? !@queue.empty? end - # A thread can remove an element from the queue without - # waiting if and only if the number of currently available - # connections is strictly greater than the number of waiting - # threads. + # A thread can remove an element from the queue without + # waiting if and only if the number of currently available + # connections is strictly greater than the number of waiting + # threads. def can_remove_no_wait? @queue.size > @num_waiting end - # Removes and returns the head of the queue if possible, or nil. + # Removes and returns the head of the queue if possible, or nil. def remove @queue.shift end - # Remove and return the head the queue if the number of - # available elements is strictly greater than the number of - # threads currently waiting. Otherwise, return nil. + # Remove and return the head the queue if the number of + # available elements is strictly greater than the number of + # threads currently waiting. Otherwise, return nil. def no_wait_poll remove if can_remove_no_wait? end - # Waits on the queue up to +timeout+ seconds, then removes and - # returns the head of the queue. + # Waits on the queue up to +timeout+ seconds, then removes and + # returns the head of the queue. def wait_poll(timeout) @num_waiting += 1 @@ -582,8 +582,8 @@ module ActiveRecord end private - #-- - # this is unfortunately not concurrent + #-- + # this is unfortunately not concurrent def bulk_make_new_connections(num_new_conns_needed) num_new_conns_needed.times do # try_to_checkout_new_connection will not exceed pool's @size limit @@ -594,19 +594,19 @@ module ActiveRecord end end - #-- - # From the discussion on GitHub: - # https://github.com/rails/rails/pull/14938#commitcomment-6601951 - # This hook-in method allows for easier monkey-patching fixes needed by - # JRuby users that use Fibers. + #-- + # From the discussion on GitHub: + # https://github.com/rails/rails/pull/14938#commitcomment-6601951 + # This hook-in method allows for easier monkey-patching fixes needed by + # JRuby users that use Fibers. def connection_cache_key(thread) thread end - # Take control of all existing connections so a "group" action such as - # reload/disconnect can be performed safely. It is no longer enough to - # wrap it in +synchronize+ because some pool's actions are allowed - # to be performed outside of the main +synchronize+ block. + # Take control of all existing connections so a "group" action such as + # reload/disconnect can be performed safely. It is no longer enough to + # wrap it in +synchronize+ because some pool's actions are allowed + # to be performed outside of the main +synchronize+ block. def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true) with_new_connections_blocked do attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout) @@ -658,8 +658,8 @@ module ActiveRecord end end - #-- - # Must be called in a synchronize block. + #-- + # Must be called in a synchronize block. def checkout_for_exclusive_access(checkout_timeout) checkout(checkout_timeout) rescue ConnectionTimeoutError @@ -690,17 +690,17 @@ module ActiveRecord synchronize { @new_cons_enabled = previous_value } end - # Acquire a connection by one of 1) immediately removing one - # from the queue of available connections, 2) creating a new - # connection if the pool is not at capacity, 3) waiting on the - # queue for a connection to become available. - # - # Raises: - # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired - # - #-- - # Implementation detail: the connection returned by +acquire_connection+ - # will already be "+connection.lease+ -ed" to the current thread. + # Acquire a connection by one of 1) immediately removing one + # from the queue of available connections, 2) creating a new + # connection if the pool is not at capacity, 3) waiting on the + # queue for a connection to become available. + # + # Raises: + # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired + # + #-- + # Implementation detail: the connection returned by +acquire_connection+ + # will already be "+connection.lease+ -ed" to the current thread. def acquire_connection(checkout_timeout) # NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to # +conn.lease+ the returned connection (and to do this in a +synchronized+ @@ -716,8 +716,8 @@ module ActiveRecord end end - #-- - # if owner_thread param is omitted, this must be called in synchronize block + #-- + # if owner_thread param is omitted, this must be called in synchronize block def remove_connection_from_thread_cache(conn, owner_thread = conn.owner) @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn) end @@ -729,11 +729,11 @@ module ActiveRecord end end - # If the pool is not at a +@size+ limit, establish new connection. Connecting - # to the DB is done outside main synchronized section. - #-- - # Implementation constraint: a newly established connection returned by this - # method must be in the +.leased+ state. + # If the pool is not at a +@size+ limit, establish new connection. Connecting + # to the DB is done outside main synchronized section. + #-- + # Implementation constraint: a newly established connection returned by this + # method must be in the +.leased+ state. def try_to_checkout_new_connection # first in synchronized section check if establishing new conns is allowed # and increment @now_connecting, to prevent overstepping this pool's @size 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 def1dadb6a..aa2dfdd573 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -245,7 +245,7 @@ module ActiveRecord end def reset_transaction #:nodoc: - @transaction_manager = TransactionManager.new(self) + @transaction_manager = ConnectionAdapters::TransactionManager.new(self) end # Register a record with the current transaction so that its after_commit and after_rollback callbacks diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 10c60080d5..6ca53c72ce 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -85,8 +85,8 @@ module ActiveRecord result.dup end - # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such - # queries should not be cached. + # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such + # queries should not be cached. def locked?(arel) arel.respond_to?(:locked) && arel.locked end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 0bd5ec4b26..29520ed9c8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -511,7 +511,7 @@ module ActiveRecord # Default is (38,0). # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62]. # Default unknown. - # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38]. + # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38]. # Default (38,0). # # == Examples 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 d0ea1ce0cf..be8511f119 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -818,8 +818,8 @@ module ActiveRecord private - # MySQL is too stupid to create a temporary table for use subquery, so we have - # to give it some prompting in the form of a subsubquery. Ugh! + # MySQL is too stupid to create a temporary table for use subquery, so we have + # to give it some prompting in the form of a subsubquery. Ugh! def subquery_for(key, select) subsubselect = select.clone subsubselect.projections = [key] diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index be6b55e53c..849130ba43 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -63,15 +63,15 @@ module ActiveRecord @uri_parser ||= URI::Parser.new end - # Converts the query parameters of the URI into a hash. - # - # "localhost?pool=5&reaping_frequency=2" - # # => { "pool" => "5", "reaping_frequency" => "2" } - # - # returns empty hash if no query present. - # - # "localhost" - # # => {} + # Converts the query parameters of the URI into a hash. + # + # "localhost?pool=5&reaping_frequency=2" + # # => { "pool" => "5", "reaping_frequency" => "2" } + # + # returns empty hash if no query present. + # + # "localhost" + # # => {} def query_hash Hash[(@query || "").split("&").map { |pair| pair.split("=") }] end @@ -92,7 +92,7 @@ module ActiveRecord end end - # Returns name of the database. + # Returns name of the database. def database_from_path if @adapter == "sqlite3" # 'sqlite3:/foo' is absolute, because that makes sense. The @@ -192,26 +192,26 @@ module ActiveRecord private - # Returns fully resolved connection, accepts hash, string or symbol. - # Always returns a hash. - # - # == Examples - # - # Symbol representing current environment. - # - # Resolver.new("production" => {}).resolve_connection(:production) - # # => {} - # - # One layer deep hash of connection values. - # - # Resolver.new({}).resolve_connection("adapter" => "sqlite3") - # # => { "adapter" => "sqlite3" } - # - # Connection URL. - # - # Resolver.new({}).resolve_connection("postgresql://localhost/foo") - # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } - # + # Returns fully resolved connection, accepts hash, string or symbol. + # Always returns a hash. + # + # == Examples + # + # Symbol representing current environment. + # + # Resolver.new("production" => {}).resolve_connection(:production) + # # => {} + # + # One layer deep hash of connection values. + # + # Resolver.new({}).resolve_connection("adapter" => "sqlite3") + # # => { "adapter" => "sqlite3" } + # + # Connection URL. + # + # Resolver.new({}).resolve_connection("postgresql://localhost/foo") + # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } + # def resolve_connection(spec) case spec when Symbol @@ -223,13 +223,13 @@ module ActiveRecord end end - # Takes the environment such as +:production+ or +:development+. - # This requires that the @configurations was initialized with a key that - # matches. - # - # Resolver.new("production" => {}).resolve_symbol_connection(:production) - # # => {} - # + # Takes the environment such as +:production+ or +:development+. + # This requires that the @configurations was initialized with a key that + # matches. + # + # Resolver.new("production" => {}).resolve_symbol_connection(:production) + # # => {} + # def resolve_symbol_connection(spec) if config = configurations[spec.to_s] resolve_connection(config).merge("name" => spec.to_s) @@ -238,10 +238,10 @@ module ActiveRecord end end - # Accepts a hash. Expands the "url" key that contains a - # URL database connection to a full connection - # hash and merges with the rest of the hash. - # Connection details inside of the "url" key win any merge conflicts + # Accepts a hash. Expands the "url" key that contains a + # URL database connection to a full connection + # hash and merges with the rest of the hash. + # Connection details inside of the "url" key win any merge conflicts def resolve_hash_connection(spec) if spec["url"] && spec["url"] !~ /^jdbc:/ connection_hash = resolve_url_connection(spec.delete("url")) @@ -250,11 +250,11 @@ module ActiveRecord spec end - # Takes a connection URL. - # - # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo") - # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } - # + # Takes a connection URL. + # + # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo") + # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } + # def resolve_url_connection(url) ConnectionUrlResolver.new(url).to_hash end 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 c8238eb266..56800f7590 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -24,11 +24,9 @@ module ActiveRecord # Executes the SQL statement in the context of this connection. def execute(sql, name = nil) - if @connection - # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been - # made since we established the connection - @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone - end + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone super end @@ -71,11 +69,9 @@ module ActiveRecord end def exec_stmt_and_free(sql, name, binds, cache_stmt: false) - if @connection - # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been - # made since we established the connection - @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone - end + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone type_casted_binds = type_casted_binds(binds) diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 0130b4ef62..a3e2c913c5 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -90,7 +90,6 @@ module ActiveRecord #++ def active? - return false unless @connection @connection.ping end @@ -105,10 +104,7 @@ module ActiveRecord # Otherwise, this method does nothing. def disconnect! super - unless @connection.nil? - @connection.close - @connection = nil - end + @connection.close end private diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 03ee0eec5b..8001c0dd53 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -527,7 +527,7 @@ module ActiveRecord case default # Quoted types when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m - # The default 'now'::date is CURRENT_DATE + # The default 'now'::date is CURRENT_DATE if $1 == "now".freeze && $2 == "date".freeze nil else @@ -542,9 +542,9 @@ module ActiveRecord # Object identifier types when /\A-?\d+\z/ $1 - else - # Anything else is blank, some user type, or some function - # and we can't know the value of that, so return nil. + else + # Anything else is blank, some user type, or some function + # and we can't know the value of that, so return nil. nil end end diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb index bbd8ca2377..9a7a8d25bb 100644 --- a/activerecord/lib/active_record/dynamic_matchers.rb +++ b/activerecord/lib/active_record/dynamic_matchers.rb @@ -75,14 +75,14 @@ module ActiveRecord "#{finder}(#{attributes_hash})" end - # The parameters in the signature may have reserved Ruby words, in order - # to prevent errors, we start each param name with `_`. + # The parameters in the signature may have reserved Ruby words, in order + # to prevent errors, we start each param name with `_`. def signature attribute_names.map { |name| "_#{name}" }.join(", ") end - # Given that the parameters starts with `_`, the finder needs to use the - # same parameter name. + # Given that the parameters starts with `_`, the finder needs to use the + # same parameter name. def attributes_hash "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}" end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 40a9aa2783..8b47fbdbe4 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -902,7 +902,7 @@ module ActiveRecord def fixtures(*fixture_set_names) if fixture_set_names.first == :all - fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"] + fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] } else fixture_set_names = fixture_set_names.flatten.map(&:to_s) diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 4adcd7e65c..a1d4f47372 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -132,8 +132,8 @@ module ActiveRecord protected - # Returns the class type of the record using the current module as a prefix. So descendants of - # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass. + # Returns the class type of the record using the current module as a prefix. So descendants of + # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass. def compute_type(type_name) if type_name.match(/^::/) # If the type is prefixed with a scope operator then we assume that @@ -156,9 +156,9 @@ module ActiveRecord private - # Called by +instantiate+ to decide which class to use for a new - # record instance. For single-table inheritance, we check the record - # for a +type+ column and return the corresponding class. + # Called by +instantiate+ to decide which class to use for a new + # record instance. For single-table inheritance, we check the record + # for a +type+ column and return the corresponding class. def discriminate_class_for_record(record) if using_single_table_inheritance?(record) find_sti_class(record[inheritance_column]) @@ -199,8 +199,8 @@ module ActiveRecord sti_column.in(sti_names) end - # Detect the subclass from the inheritance column of attrs. If the inheritance column value - # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound + # Detect the subclass from the inheritance column of attrs. If the inheritance column value + # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound def subclass_from_attributes(attrs) attrs = attrs.to_h if attrs.respond_to?(:permitted?) if attrs.is_a?(Hash) @@ -225,11 +225,11 @@ module ActiveRecord ensure_proper_type end - # Sets the attribute used for single table inheritance to this class name if this is not the - # ActiveRecord::Base descendant. - # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to - # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. - # No such attribute would be set for objects of the Message class in that example. + # Sets the attribute used for single table inheritance to this class name if this is not the + # ActiveRecord::Base descendant. + # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to + # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. + # No such attribute would be set for objects of the Message class in that example. def ensure_proper_type klass = self.class if klass.finder_needs_type_condition? diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 1b6cda3861..8e8a97990a 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -168,10 +168,10 @@ module ActiveRecord private - # We need to apply this decorator here, rather than on module inclusion. The closure - # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the - # sub class being decorated. As such, changes to `lock_optimistically`, or - # `locking_column` would not be picked up. + # We need to apply this decorator here, rather than on module inclusion. The closure + # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the + # sub class being decorated. As such, changes to `lock_optimistically`, or + # `locking_column` would not be picked up. def inherited(subclass) subclass.class_eval do is_lock_column = ->(name, _) { lock_optimistically && name == locking_column } diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 063366bc60..05568039d8 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1163,7 +1163,7 @@ module ActiveRecord private - # Used for running a specific migration. + # Used for running a specific migration. def run_without_lock migration = migrations.detect { |m| m.version == @target_version } raise UnknownMigrationVersionError.new(@target_version) if migration.nil? @@ -1172,7 +1172,7 @@ module ActiveRecord record_environment end - # Used for running multiple migrations up to or down to a certain value. + # Used for running multiple migrations up to or down to a certain value. def migrate_without_lock if invalid_target? raise UnknownMigrationVersionError.new(@target_version) @@ -1185,7 +1185,7 @@ module ActiveRecord record_environment end - # Stores the current environment in the database. + # Stores the current environment in the database. def record_environment return if down? ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment @@ -1195,7 +1195,7 @@ module ActiveRecord migrated.include?(migration.version.to_i) end - # Return true if a valid version is not provided. + # Return true if a valid version is not provided. def invalid_target? !target && @target_version && @target_version > 0 end @@ -1272,7 +1272,7 @@ module ActiveRecord @direction == :down end - # Wrap the migration in a transaction only if supported by the adapter. + # Wrap the migration in a transaction only if supported by the adapter. def ddl_transaction(migration) if use_transaction?(migration) Base.transaction { yield } diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 44ea756028..03103bba98 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -225,7 +225,7 @@ module ActiveRecord [:add_foreign_key, reversed_args] end - # Forwards any missing method call to the \target. + # Forwards any missing method call to the \target. def method_missing(method, *args, &block) if @delegate.respond_to?(method) @delegate.send(method, *args, &block) diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index f49f8da2ed..76b3169411 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -397,13 +397,13 @@ module ActiveRecord end end - # Guesses the table name, but does not decorate it with prefix and suffix information. + # Guesses the table name, but does not decorate it with prefix and suffix information. def undecorated_table_name(class_name = base_class.name) table_name = class_name.to_s.demodulize.underscore pluralize_table_names ? table_name.pluralize : table_name end - # Computes and returns a table name according to default conventions. + # Computes and returns a table name according to default conventions. def compute_table_name base = base_class if self == base diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index f0f88b120a..e983026961 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -341,17 +341,17 @@ module ActiveRecord private - # Generates a writer method for this association. Serves as a point for - # accessing the objects in the association. For example, this method - # could generate the following: - # - # def pirate_attributes=(attributes) - # assign_nested_attributes_for_one_to_one_association(:pirate, attributes) - # end - # - # This redirects the attempts to write objects in an association through - # the helper methods defined below. Makes it seem like the nested - # associations are just regular associations. + # Generates a writer method for this association. Serves as a point for + # accessing the objects in the association. For example, this method + # could generate the following: + # + # def pirate_attributes=(attributes) + # assign_nested_attributes_for_one_to_one_association(:pirate, attributes) + # end + # + # This redirects the attempts to write objects in an association through + # the helper methods defined below. Makes it seem like the nested + # associations are just regular associations. def generate_association_writer(association_name, type) generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1 if method_defined?(:#{association_name}_attributes=) @@ -375,23 +375,23 @@ module ActiveRecord private - # Attribute hash keys that should not be assigned as normal attributes. - # These hash keys are nested attributes implementation details. + # Attribute hash keys that should not be assigned as normal attributes. + # These hash keys are nested attributes implementation details. UNASSIGNABLE_KEYS = %w( id _destroy ) - # Assigns the given attributes to the association. - # - # If an associated record does not yet exist, one will be instantiated. If - # an associated record already exists, the method's behavior depends on - # the value of the update_only option. If update_only is +false+ and the - # given attributes include an <tt>:id</tt> that matches the existing record's - # id, then the existing record will be modified. If no <tt>:id</tt> is provided - # it will be replaced with a new record. If update_only is +true+ the existing - # record will be modified regardless of whether an <tt>:id</tt> is provided. - # - # If the given attributes include a matching <tt>:id</tt> attribute, or - # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value, - # then the existing record will be marked for destruction. + # Assigns the given attributes to the association. + # + # If an associated record does not yet exist, one will be instantiated. If + # an associated record already exists, the method's behavior depends on + # the value of the update_only option. If update_only is +false+ and the + # given attributes include an <tt>:id</tt> that matches the existing record's + # id, then the existing record will be modified. If no <tt>:id</tt> is provided + # it will be replaced with a new record. If update_only is +true+ the existing + # record will be modified regardless of whether an <tt>:id</tt> is provided. + # + # If the given attributes include a matching <tt>:id</tt> attribute, or + # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value, + # then the existing record will be marked for destruction. def assign_nested_attributes_for_one_to_one_association(association_name, attributes) options = self.nested_attributes_options[association_name] if attributes.respond_to?(:permitted?) @@ -424,33 +424,33 @@ module ActiveRecord end end - # Assigns the given attributes to the collection association. - # - # Hashes with an <tt>:id</tt> value matching an existing associated record - # will update that record. Hashes without an <tt>:id</tt> value will build - # a new record for the association. Hashes with a matching <tt>:id</tt> - # value and a <tt>:_destroy</tt> key set to a truthy value will mark the - # matched record for destruction. - # - # For example: - # - # assign_nested_attributes_for_collection_association(:people, { - # '1' => { id: '1', name: 'Peter' }, - # '2' => { name: 'John' }, - # '3' => { id: '2', _destroy: true } - # }) - # - # Will update the name of the Person with ID 1, build a new associated - # person with the name 'John', and mark the associated Person with ID 2 - # for destruction. - # - # Also accepts an Array of attribute hashes: - # - # assign_nested_attributes_for_collection_association(:people, [ - # { id: '1', name: 'Peter' }, - # { name: 'John' }, - # { id: '2', _destroy: true } - # ]) + # Assigns the given attributes to the collection association. + # + # Hashes with an <tt>:id</tt> value matching an existing associated record + # will update that record. Hashes without an <tt>:id</tt> value will build + # a new record for the association. Hashes with a matching <tt>:id</tt> + # value and a <tt>:_destroy</tt> key set to a truthy value will mark the + # matched record for destruction. + # + # For example: + # + # assign_nested_attributes_for_collection_association(:people, { + # '1' => { id: '1', name: 'Peter' }, + # '2' => { name: 'John' }, + # '3' => { id: '2', _destroy: true } + # }) + # + # Will update the name of the Person with ID 1, build a new associated + # person with the name 'John', and mark the associated Person with ID 2 + # for destruction. + # + # Also accepts an Array of attribute hashes: + # + # assign_nested_attributes_for_collection_association(:people, [ + # { id: '1', name: 'Peter' }, + # { name: 'John' }, + # { id: '2', _destroy: true } + # ]) def assign_nested_attributes_for_collection_association(association_name, attributes_collection) options = self.nested_attributes_options[association_name] if attributes_collection.respond_to?(:permitted?) @@ -511,12 +511,12 @@ module ActiveRecord end end - # Takes in a limit and checks if the attributes_collection has too many - # records. It accepts limit in the form of symbol, proc, or - # number-like object (anything that can be compared with an integer). - # - # Raises TooManyRecords error if the attributes_collection is - # larger than the limit. + # Takes in a limit and checks if the attributes_collection has too many + # records. It accepts limit in the form of symbol, proc, or + # number-like object (anything that can be compared with an integer). + # + # Raises TooManyRecords error if the attributes_collection is + # larger than the limit. def check_record_limit!(limit, attributes_collection) if limit limit = \ @@ -535,30 +535,30 @@ module ActiveRecord end end - # Updates a record with the +attributes+ or marks it for destruction if - # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+. + # Updates a record with the +attributes+ or marks it for destruction if + # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+. def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS)) record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy end - # Determines if a hash contains a truthy _destroy key. + # Determines if a hash contains a truthy _destroy key. def has_destroy_flag?(hash) Type::Boolean.new.cast(hash["_destroy"]) end - # Determines if a new record should be rejected by checking - # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this - # association and evaluates to +true+. + # Determines if a new record should be rejected by checking + # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this + # association and evaluates to +true+. def reject_new_record?(association_name, attributes) will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes) end - # Determines if a record with the particular +attributes+ should be - # rejected by calling the reject_if Symbol or Proc (if defined). - # The reject_if option is defined by +accepts_nested_attributes_for+. - # - # Returns false if there is a +destroy_flag+ on the attributes. + # Determines if a record with the particular +attributes+ should be + # rejected by calling the reject_if Symbol or Proc (if defined). + # The reject_if option is defined by +accepts_nested_attributes_for+. + # + # Returns false if there is a +destroy_flag+ on the attributes. def call_reject_if(association_name, attributes) return false if will_be_destroyed?(association_name, attributes) @@ -570,7 +570,7 @@ module ActiveRecord end end - # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true + # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true def will_be_destroyed?(association_name, attributes) allow_destroy?(association_name) && has_destroy_flag?(attributes) end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index a04ef2e263..978fb27cab 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -498,7 +498,6 @@ module ActiveRecord changes[column] = write_attribute(column, time) end - clear_attribute_changes(changes.keys) primary_key = self.class.primary_key scope = self.class.unscoped.where(primary_key => _read_attribute(primary_key)) @@ -508,6 +507,7 @@ module ActiveRecord changes[locking_column] = increment_lock end + clear_attribute_changes(changes.keys) result = scope.update_all(changes) == 1 if !result && locking_enabled? diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 6d571cf026..ef629dcb3b 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -243,7 +243,6 @@ module ActiveRecord # Please see further details in the # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain]. def explain - #TODO: Fix for binds. exec_explain(collecting_queries_for_explain { exec_queries }) end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index a796e35261..a887be8a20 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -252,11 +252,11 @@ module ActiveRecord result = @klass.connection.select_all(query_builder, nil, bound_attributes) row = result.first value = row && row.values.first - column = result.column_types.fetch(column_alias) do + type = result.column_types.fetch(column_alias) do type_for(column_name) end - type_cast_calculated_value(value, column, operation) + type_cast_calculated_value(value, type, operation) end def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: @@ -310,28 +310,26 @@ module ActiveRecord Hash[calculated_data.map do |row| key = group_columns.map { |aliaz, col_name| - column = type_for(col_name) do - calculated_data.column_types.fetch(aliaz) do - Type.default_value - end + type = type_for(col_name) do + calculated_data.column_types.fetch(aliaz, Type.default_value) end - type_cast_calculated_value(row[aliaz], column) + type_cast_calculated_value(row[aliaz], type) } key = key.first if key.size == 1 key = key_records[key] if associated - column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) } - [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)] + type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) } + [key, type_cast_calculated_value(row[aggregate_alias], type, operation)] end] end - # Converts the given keys to the value that the database adapter returns as - # a usable column name: - # - # column_alias_for("users.id") # => "users_id" - # column_alias_for("sum(id)") # => "sum_id" - # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" - # column_alias_for("count(*)") # => "count_all" + # Converts the given keys to the value that the database adapter returns as + # a usable column name: + # + # column_alias_for("users.id") # => "users_id" + # column_alias_for("sum(id)") # => "sum_id" + # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" + # column_alias_for("count(*)") # => "count_all" def column_alias_for(keys) if keys.respond_to? :name keys = "#{keys.relation.name}.#{keys.name}" @@ -356,7 +354,7 @@ module ActiveRecord when "count" then value.to_i when "sum" then type.deserialize(value || 0) when "average" then value.respond_to?(:to_d) ? value.to_d : value - else type.deserialize(value) + else type.deserialize(value) end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 78570140e5..9fbbe32e7f 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1150,22 +1150,22 @@ module ActiveRecord end.flatten! end - # Checks to make sure that the arguments are not blank. Note that if some - # blank-like object were initially passed into the query method, then this - # method will not raise an error. - # - # Example: - # - # Post.references() # raises an error - # Post.references([]) # does not raise an error - # - # This particular method should be called with a method_name and the args - # passed into that method as an input. For example: - # - # def references(*args) - # check_if_method_has_arguments!("references", args) - # ... - # end + # Checks to make sure that the arguments are not blank. Note that if some + # blank-like object were initially passed into the query method, then this + # method will not raise an error. + # + # Example: + # + # Post.references() # raises an error + # Post.references([]) # does not raise an error + # + # This particular method should be called with a method_name and the args + # passed into that method as an input. For example: + # + # def references(*args) + # check_if_method_has_arguments!("references", args) + # ... + # end def check_if_method_has_arguments!(method_name, args) if args.blank? raise ArgumentError, "The method .#{method_name}() must contain arguments." diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index dc00149130..1e7deeffad 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -7,8 +7,6 @@ module ActiveRecord end def build(opts, other) - binds = [] - case opts when String, Array parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] @@ -26,7 +24,7 @@ module ActiveRecord raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})" end - WhereClause.new(parts, binds) + WhereClause.new(parts, binds || []) end protected diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 7f596120eb..e7c0936984 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -7,20 +7,20 @@ module ActiveRecord module ClassMethods protected - # Accepts an array or string of SQL conditions and sanitizes - # them into a valid SQL fragment for a WHERE clause. - # - # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4]) - # # => "name='foo''bar' and group_id=4" - # - # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) - # # => "name='foo''bar' and group_id='4'" - # - # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4]) - # # => "name='foo''bar' and group_id='4'" - # - # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'") - # # => "name='foo''bar' and group_id='4'" + # Accepts an array or string of SQL conditions and sanitizes + # them into a valid SQL fragment for a WHERE clause. + # + # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) + # # => "name='foo''bar' and group_id='4'" + # + # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4]) + # # => "name='foo''bar' and group_id='4'" + # + # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'") + # # => "name='foo''bar' and group_id='4'" def sanitize_sql_for_conditions(condition) return nil if condition.blank? @@ -33,20 +33,20 @@ module ActiveRecord alias :sanitize_conditions :sanitize_sql deprecate sanitize_conditions: :sanitize_sql - # Accepts an array, hash, or string of SQL conditions and sanitizes - # them into a valid SQL fragment for a SET clause. - # - # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4]) - # # => "name=NULL and group_id=4" - # - # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4]) - # # => "name=NULL and group_id=4" - # - # Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 }) - # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4" - # - # sanitize_sql_for_assignment("name=NULL and group_id='4'") - # # => "name=NULL and group_id='4'" + # Accepts an array, hash, or string of SQL conditions and sanitizes + # them into a valid SQL fragment for a SET clause. + # + # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4]) + # # => "name=NULL and group_id=4" + # + # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4]) + # # => "name=NULL and group_id=4" + # + # Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 }) + # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4" + # + # sanitize_sql_for_assignment("name=NULL and group_id='4'") + # # => "name=NULL and group_id='4'" def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name) case assignments when Array; sanitize_sql_array(assignments) @@ -55,14 +55,14 @@ module ActiveRecord end end - # Accepts an array, or string of SQL conditions and sanitizes - # them into a valid SQL fragment for an ORDER clause. - # - # sanitize_sql_for_order(["field(id, ?)", [1,3,2]]) - # # => "field(id, 1,3,2)" - # - # sanitize_sql_for_order("id ASC") - # # => "id ASC" + # Accepts an array, or string of SQL conditions and sanitizes + # them into a valid SQL fragment for an ORDER clause. + # + # sanitize_sql_for_order(["field(id, ?)", [1,3,2]]) + # # => "field(id, 1,3,2)" + # + # sanitize_sql_for_order("id ASC") + # # => "id ASC" def sanitize_sql_for_order(condition) if condition.is_a?(Array) && condition.first.to_s.include?("?") sanitize_sql_array(condition) @@ -71,21 +71,21 @@ module ActiveRecord end end - # Accepts a hash of SQL conditions and replaces those attributes - # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of] - # relationship with their expanded aggregate attribute values. - # - # Given: - # - # class Person < ActiveRecord::Base - # composed_of :address, class_name: "Address", - # mapping: [%w(address_street street), %w(address_city city)] - # end - # - # Then: - # - # { address: Address.new("813 abc st.", "chicago") } - # # => { address_street: "813 abc st.", address_city: "chicago" } + # Accepts a hash of SQL conditions and replaces those attributes + # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of] + # relationship with their expanded aggregate attribute values. + # + # Given: + # + # class Person < ActiveRecord::Base + # composed_of :address, class_name: "Address", + # mapping: [%w(address_street street), %w(address_city city)] + # end + # + # Then: + # + # { address: Address.new("813 abc st.", "chicago") } + # # => { address_street: "813 abc st.", address_city: "chicago" } def expand_hash_conditions_for_aggregates(attrs) expanded_attrs = {} attrs.each do |attr, value| @@ -105,10 +105,10 @@ module ActiveRecord expanded_attrs end - # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. - # - # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts") - # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1" + # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. + # + # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts") + # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1" def sanitize_sql_hash_for_assignment(attrs, table) c = connection attrs.map do |attr, value| @@ -117,36 +117,36 @@ module ActiveRecord end.join(", ") end - # Sanitizes a +string+ so that it is safe to use within an SQL - # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%". - # - # sanitize_sql_like("100%") - # # => "100\\%" - # - # sanitize_sql_like("snake_cased_string") - # # => "snake\\_cased\\_string" - # - # sanitize_sql_like("100%", "!") - # # => "100!%" - # - # sanitize_sql_like("snake_cased_string", "!") - # # => "snake!_cased!_string" + # Sanitizes a +string+ so that it is safe to use within an SQL + # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%". + # + # sanitize_sql_like("100%") + # # => "100\\%" + # + # sanitize_sql_like("snake_cased_string") + # # => "snake\\_cased\\_string" + # + # sanitize_sql_like("100%", "!") + # # => "100!%" + # + # sanitize_sql_like("snake_cased_string", "!") + # # => "snake!_cased!_string" def sanitize_sql_like(string, escape_character = "\\") pattern = Regexp.union(escape_character, "%", "_") string.gsub(pattern) { |x| [escape_character, x].join } end - # Accepts an array of conditions. The array has each value - # sanitized and interpolated into the SQL statement. - # - # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4]) - # # => "name='foo''bar' and group_id=4" - # - # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) - # # => "name='foo''bar' and group_id=4" - # - # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4]) - # # => "name='foo''bar' and group_id='4'" + # Accepts an array of conditions. The array has each value + # sanitized and interpolated into the SQL statement. + # + # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4]) + # # => "name='foo''bar' and group_id='4'" def sanitize_sql_array(ary) statement, *values = ary if values.first.is_a?(Hash) && /:\w+/.match?(statement) diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 7409706851..9d8253faa3 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -46,47 +46,47 @@ module ActiveRecord protected - # Use this macro in your model to set a default scope for all operations on - # the model. - # - # class Article < ActiveRecord::Base - # default_scope { where(published: true) } - # end - # - # Article.all # => SELECT * FROM articles WHERE published = true - # - # The #default_scope is also applied while creating/building a record. - # It is not applied while updating a record. - # - # Article.new.published # => true - # Article.create.published # => true - # - # (You can also pass any object which responds to +call+ to the - # +default_scope+ macro, and it will be called when building the - # default scope.) - # - # If you use multiple #default_scope declarations in your model then - # they will be merged together: - # - # class Article < ActiveRecord::Base - # default_scope { where(published: true) } - # default_scope { where(rating: 'G') } - # end - # - # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' - # - # This is also the case with inheritance and module includes where the - # parent or module defines a #default_scope and the child or including - # class defines a second one. - # - # If you need to do more complex things with a default scope, you can - # alternatively define it as a class method: - # - # class Article < ActiveRecord::Base - # def self.default_scope - # # Should return a scope, you can call 'super' here etc. - # end - # end + # Use this macro in your model to set a default scope for all operations on + # the model. + # + # class Article < ActiveRecord::Base + # default_scope { where(published: true) } + # end + # + # Article.all # => SELECT * FROM articles WHERE published = true + # + # The #default_scope is also applied while creating/building a record. + # It is not applied while updating a record. + # + # Article.new.published # => true + # Article.create.published # => true + # + # (You can also pass any object which responds to +call+ to the + # +default_scope+ macro, and it will be called when building the + # default scope.) + # + # If you use multiple #default_scope declarations in your model then + # they will be merged together: + # + # class Article < ActiveRecord::Base + # default_scope { where(published: true) } + # default_scope { where(rating: 'G') } + # end + # + # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' + # + # This is also the case with inheritance and module includes where the + # parent or module defines a #default_scope and the child or including + # class defines a second one. + # + # If you need to do more complex things with a default scope, you can + # alternatively define it as a class method: + # + # class Article < ActiveRecord::Base + # def self.default_scope + # # Should return a scope, you can call 'super' here etc. + # end + # end def default_scope(scope = nil) scope = Proc.new if block_given? @@ -130,9 +130,9 @@ module ActiveRecord ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore) end - # The ignore_default_scope flag is used to prevent an infinite recursion - # situation where a default scope references a scope which has a default - # scope which references a scope... + # The ignore_default_scope flag is used to prevent an infinite recursion + # situation where a default scope references a scope which has a default + # scope which references a scope... def evaluate_default_scope # :nodoc: return if ignore_default_scope? diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index d19bb96ede..691940ab70 100644 --- a/activerecord/lib/active_record/statement_cache.rb +++ b/activerecord/lib/active_record/statement_cache.rb @@ -7,12 +7,12 @@ module ActiveRecord # end # # The cached statement is executed by using the - # [connection.execute]{rdoc-ref:ConnectionAdapters::DatabaseStatements#execute} method: + # {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method: # # cache.execute([], Book, Book.connection) # # The relation returned by the block is cached, and for each - # [execute]{rdoc-ref:ConnectionAdapters::DatabaseStatements#execute} + # {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] # call the cached relation gets duped. Database is queried when +to_a+ is called on the relation. # # If you want to cache the statement without the values you can use the +bind+ method of the diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index b19ae5c46e..af3fc88282 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -409,7 +409,7 @@ module ActiveRecord protected - # Save the new record state and id of a record so it can be restored later if a transaction fails. + # Save the new record state and id of a record so it can be restored later if a transaction fails. def remember_transaction_record_state #:nodoc: @_start_transaction_state[:id] = id @_start_transaction_state.reverse_merge!( @@ -420,18 +420,18 @@ module ActiveRecord @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1 end - # Clear the new record state and id of a record. + # Clear the new record state and id of a record. def clear_transaction_record_state #:nodoc: @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 force_clear_transaction_record_state if @_start_transaction_state[:level] < 1 end - # Force to clear the transaction record state. + # Force to clear the transaction record state. def force_clear_transaction_record_state #:nodoc: @_start_transaction_state.clear end - # Restore the new record state and id of a record that was previously saved by a call to save_record_state. + # Restore the new record state and id of a record that was previously saved by a call to save_record_state. def restore_transaction_record_state(force = false) #:nodoc: unless @_start_transaction_state.empty? transaction_level = (@_start_transaction_state[:level] || 0) - 1 @@ -449,12 +449,12 @@ module ActiveRecord end end - # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed. + # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed. def transaction_record_state(state) #:nodoc: @_start_transaction_state[state] end - # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks. + # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks. def transaction_include_any_action?(actions) #:nodoc: actions.any? do |action| case action @@ -478,23 +478,23 @@ module ActiveRecord !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty? end - # Updates the attributes on this particular Active Record object so that - # if it's associated with a transaction, then the state of the Active Record - # object will be updated to reflect the current state of the transaction. - # - # The +@transaction_state+ variable stores the states of the associated - # transaction. This relies on the fact that a transaction can only be in - # one rollback or commit (otherwise a list of states would be required). - # Each Active Record object inside of a transaction carries that transaction's - # TransactionState. - # - # This method checks to see if the ActiveRecord object's state reflects - # the TransactionState, and rolls back or commits the Active Record object - # as appropriate. - # - # Since Active Record objects can be inside multiple transactions, this - # method recursively goes through the parent of the TransactionState and - # checks if the Active Record object reflects the state of the object. + # Updates the attributes on this particular Active Record object so that + # if it's associated with a transaction, then the state of the Active Record + # object will be updated to reflect the current state of the transaction. + # + # The +@transaction_state+ variable stores the states of the associated + # transaction. This relies on the fact that a transaction can only be in + # one rollback or commit (otherwise a list of states would be required). + # Each Active Record object inside of a transaction carries that transaction's + # TransactionState. + # + # This method checks to see if the ActiveRecord object's state reflects + # the TransactionState, and rolls back or commits the Active Record object + # as appropriate. + # + # Since Active Record objects can be inside multiple transactions, this + # method recursively goes through the parent of the TransactionState and + # checks if the Active Record object reflects the state of the object. def sync_with_transaction_state update_attributes_from_transaction_state(@transaction_state) end 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 bc9037c476..76ed25ea75 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -17,9 +17,9 @@ module ActiveRecord protected attr_reader :migration_action, :join_tables - # Sets the default migration template that is being used for the generation of the migration. - # Depending on command line arguments, the migration template and the table name instance - # variables are set up. + # Sets the default migration template that is being used for the generation of the migration. + # Depending on command line arguments, the migration template and the table name instance + # variables are set up. def set_local_assigns! @migration_template = "migration.rb" case file_name diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index a72981e4d5..8fa0645b0f 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -33,10 +33,10 @@ module ActiveRecord def test_tables tables = nil ActiveSupport::Deprecation.silence { tables = @connection.tables } - assert tables.include?("accounts") - assert tables.include?("authors") - assert tables.include?("tasks") - assert tables.include?("topics") + assert_includes tables, "accounts" + assert_includes tables, "authors" + assert_includes tables, "tasks" + assert_includes tables, "topics" end def test_table_exists? @@ -53,10 +53,10 @@ module ActiveRecord def test_data_sources data_sources = @connection.data_sources - assert data_sources.include?("accounts") - assert data_sources.include?("authors") - assert data_sources.include?("tasks") - assert data_sources.include?("topics") + assert_includes data_sources, "accounts" + assert_includes data_sources, "authors" + assert_includes data_sources, "tasks" + assert_includes data_sources, "topics" end def test_data_source_exists? diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 9ede57d395..8d8955e5c9 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -63,6 +63,27 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase assert @connection.active? end + def test_execute_after_disconnect + @connection.disconnect! + error = assert_raise(ActiveRecord::StatementInvalid) do + @connection.execute("SELECT 1") + end + assert_match(/closed MySQL connection/, error.message) + end + + def test_quote_after_disconnect + @connection.disconnect! + error = assert_raise(Mysql2::Error) do + @connection.quote("string") + end + assert_match(/closed MySQL connection/, error.message) + end + + def test_active_after_disconnect + @connection.disconnect! + assert_equal false, @connection.active? + end + def test_mysql_connection_collation_is_configured assert_equal "utf8_unicode_ci", @connection.show_variable("collation_connection") assert_equal "utf8_general_ci", ARUnit2Model.connection.show_variable("collation_connection") diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 7736113a19..776549eb7a 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -130,19 +130,19 @@ class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase #the following functions were added to DRY test cases private - # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path + # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path def create_test_fixtures(*fixture_names) ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) end - # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name + # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name def drop_tables_directly(table_names, connection = @connection) table_names.each do |name| connection.drop_table name, if_exists: true end end - # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns + # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns def create_tables_directly (tables, connection = @connection) tables.each do |table_name, column_properties| connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )") diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 3a8fcf388a..9236a67b11 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -38,7 +38,7 @@ if ActiveRecord::Base.connection.supports_extensions? def test_hstore_included_in_extensions assert @connection.respond_to?(:extensions), "connection should have a list of extensions" - assert @connection.extensions.include?("hstore"), "extension list should include hstore" + assert_includes @connection.extensions, "hstore", "extension list should include hstore" end def test_disable_enable_hstore diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index 22f935f24e..7193f23880 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -100,10 +100,10 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase end def test_tables_in_current_schemas - assert !@connection.tables.include?(TABLE_NAME) + assert_not_includes @connection.tables, TABLE_NAME USERS.each do |u| set_session_auth u - assert @connection.tables.include?(TABLE_NAME) + assert_includes @connection.tables, TABLE_NAME set_session_auth end end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index b0be25de0c..51a2306c59 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -130,7 +130,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase ensure @connection.drop_schema "test_schema3" end - assert !@connection.schema_names.include?("test_schema3") + assert_not_includes @connection.schema_names, "test_schema3" end def test_drop_schema_if_exists diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index c3d560cb56..2f62d0367e 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -176,14 +176,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase end def test_dont_add_if_before_callback_raises_exception - assert !@david.unchangeable_posts.include?(@authorless) + assert_not_includes @david.unchangeable_posts, @authorless begin @david.unchangeable_posts << @authorless rescue Exception end assert @david.post_log.empty? - assert !@david.unchangeable_posts.include?(@authorless) + assert_not_includes @david.unchangeable_posts, @authorless @david.reload - assert !@david.unchangeable_posts.include?(@authorless) + assert_not_includes @david.unchangeable_posts, @authorless end end diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 9ae1558dc9..e87431bf32 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -134,8 +134,8 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_sti replies = Reply.all.merge!(includes: :topic, order: "topics.id").to_a - assert replies.include?(topics(:second)) - assert !replies.include?(topics(:first)) + assert_includes replies, topics(:second) + assert_not_includes replies, topics(:first) assert_equal topics(:first), assert_no_queries { replies.first.topic } end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 6011c552b2..d1c4c1cef8 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -42,11 +42,11 @@ class EagerAssociationTest < ActiveRecord::TestCase posts = Post.all.merge!(includes: :comments).to_a post = posts.find { |p| p.id == 1 } assert_equal 2, post.comments.size - assert post.comments.include?(comments(:greetings)) + assert_includes post.comments, comments(:greetings) post = Post.all.merge!(includes: :comments, where: "posts.title = 'Welcome to the weblog'").first assert_equal 2, post.comments.size - assert post.comments.include?(comments(:greetings)) + assert_includes post.comments, comments(:greetings) posts = Post.all.merge!(includes: :last_comment).to_a post = posts.find { |p| p.id == 1 } @@ -103,7 +103,7 @@ class EagerAssociationTest < ActiveRecord::TestCase posts = Post.all.merge!(includes: [ :comments, :author, :categories ], order: "posts.id").to_a assert_equal 2, posts.first.comments.size assert_equal 2, posts.first.categories.size - assert posts.first.comments.include?(comments(:greetings)) + assert_includes posts.first.comments, comments(:greetings) end def test_duplicate_middle_objects @@ -349,8 +349,8 @@ class EagerAssociationTest < ActiveRecord::TestCase comments = Comment.all.merge!(includes: :post).to_a assert_equal 11, comments.length titles = comments.map { |c| c.post.title } - assert titles.include?(posts(:welcome).title) - assert titles.include?(posts(:sti_post_and_comments).title) + assert_includes titles, posts(:welcome).title + assert_includes titles, posts(:sti_post_and_comments).title end def test_eager_association_loading_with_belongs_to_and_limit @@ -630,8 +630,8 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size assert_equal 0, posts[2].categories.size - assert posts[0].categories.include?(categories(:technology)) - assert posts[1].categories.include?(categories(:general)) + assert_includes posts[0].categories, categories(:technology) + assert_includes posts[1].categories, categories(:general) end # Since the preloader for habtm gets raw row hashes from the database and then @@ -695,8 +695,8 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size assert_equal 0, posts[2].categories.size - assert posts[0].categories.include?(categories(:technology)) - assert posts[1].categories.include?(categories(:general)) + assert_includes posts[0].categories, categories(:technology) + assert_includes posts[1].categories, categories(:general) end def test_eager_with_inheritance @@ -895,9 +895,9 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_polymorphic_type_condition post = Post.all.merge!(includes: :taggings).find(posts(:thinking).id) - assert post.taggings.include?(taggings(:thinking_general)) + assert_includes post.taggings, taggings(:thinking_general) post = SpecialPost.all.merge!(includes: :taggings).find(posts(:thinking).id) - assert post.taggings.include?(taggings(:thinking_general)) + assert_includes post.taggings, taggings(:thinking_general) end def test_eager_with_multiple_associations_with_same_table_has_many_and_habtm diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 36f541a506..06fc7a4388 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -169,7 +169,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase active_record = Project.find(1) assert !active_record.developers.empty? assert_equal 3, active_record.developers.size - assert active_record.developers.include?(david) + assert_includes active_record.developers, david end def test_adding_single @@ -544,7 +544,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_no_queries(ignore_none: false) do assert project.developers.loaded? - assert project.developers.include?(developer) + assert_includes project.developers, developer end end @@ -555,7 +555,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase project.reload assert ! project.developers.loaded? assert_queries(1) do - assert project.developers.include?(developer) + assert_includes project.developers, developer end assert ! project.developers.loaded? end @@ -600,8 +600,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase project.save! project.reload - assert project.developers.include?(jamis) - assert project.developers.include?(david) + assert_includes project.developers, jamis + assert_includes project.developers, david end def test_find_in_association_with_options @@ -628,7 +628,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase david.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")] david.save assert_equal 2, david.projects.length - assert !david.projects.include?(projects(:active_record)) + assert_not_includes david.projects, projects(:active_record) end def test_replace_on_new_object @@ -646,9 +646,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase developer.special_projects << special_project developer.reload - assert developer.projects.include?(special_project) - assert developer.special_projects.include?(special_project) - assert !developer.special_projects.include?(other_project) + assert_includes developer.projects, special_project + assert_includes developer.special_projects, special_project + assert_not_includes developer.special_projects, other_project end def test_symbol_join_table @@ -854,7 +854,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_include_method_in_has_and_belongs_to_many_association_should_return_true_for_instance_added_with_build project = Project.new developer = project.developers.build - assert project.developers.include?(developer) + assert_includes project.developers, developer end def test_destruction_does_not_error_without_primary_key diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 1bfb1ea0c8..fed59c2ab3 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -870,7 +870,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase rescue Client::RaisedOnSave end - assert !companies(:first_firm).clients_of_firm.reload.include?(good) + assert_not_includes companies(:first_firm).clients_of_firm.reload, good end def test_transactions_when_adding_to_new_record diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 3508793122..9f716d7820 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -175,7 +175,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase person = Person.new post = Post.new person.posts << post - assert person.posts.include?(post) + assert_includes person.posts, post end def test_associate_existing @@ -187,10 +187,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end assert_queries(1) do - assert post.people.include?(person) + assert_includes post.people, person end - assert post.reload.people.reload.include?(person) + assert_includes post.reload.people.reload, person end def test_delete_all_for_with_dependent_option_destroy @@ -266,7 +266,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase post.people.delete(person) end - assert !post.people.reload.include?(person) + assert_not_includes post.people.reload, person end def test_associating_new @@ -284,10 +284,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end assert_queries(1) do - assert posts(:thinking).people.include?(new_person) + assert_includes posts(:thinking).people, new_person end - assert posts(:thinking).reload.people.reload.include?(new_person) + assert_includes posts(:thinking).reload.people.reload, new_person end def test_associate_new_by_building @@ -300,8 +300,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase # Should only need to load the association once assert_queries(1) do - assert posts(:thinking).people.collect(&:first_name).include?("Bob") - assert posts(:thinking).people.collect(&:first_name).include?("Ted") + assert_includes posts(:thinking).people.collect(&:first_name), "Bob" + assert_includes posts(:thinking).people.collect(&:first_name), "Ted" end # 2 queries for each new record (1 to save the record itself, 1 for the join model) @@ -312,8 +312,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase posts(:thinking).save end - assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Bob") - assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Ted") + assert_includes posts(:thinking).reload.people.reload.collect(&:first_name), "Bob" + assert_includes posts(:thinking).reload.people.reload.collect(&:first_name), "Ted" end def test_build_then_save_with_has_many_inverse @@ -322,7 +322,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase person.save post.reload - assert post.people.include?(person) + assert_includes post.people, person end def test_build_then_save_with_has_one_inverse @@ -331,7 +331,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase person.save post.reload - assert post.single_people.include?(person) + assert_includes post.single_people, person end def test_both_parent_ids_set_when_saving_new @@ -550,12 +550,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end assert_queries(0) { - assert posts(:welcome).people.include?(people(:david)) - assert !posts(:welcome).people.include?(people(:michael)) + assert_includes posts(:welcome).people, people(:david) + assert_not_includes posts(:welcome).people, people(:michael) } - assert posts(:welcome).reload.people.reload.include?(people(:david)) - assert !posts(:welcome).reload.people.reload.include?(people(:michael)) + assert_includes posts(:welcome).reload.people.reload, people(:david) + assert_not_includes posts(:welcome).reload.people.reload, people(:michael) end def test_replace_order_is_preserved @@ -591,10 +591,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase # *Now* we actually need the collection so it's loaded assert_queries(1) do - assert posts(:thinking).people.collect(&:first_name).include?("Jeb") + assert_includes posts(:thinking).people.collect(&:first_name), "Jeb" end - assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Jeb") + assert_includes posts(:thinking).reload.people.reload.collect(&:first_name), "Jeb" end def test_through_record_is_built_when_created_with_where @@ -832,14 +832,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase category = author.named_categories.build(name: "Primary") author.save assert Categorization.exists?(author_id: author.id, named_category_name: category.name) - assert author.named_categories.reload.include?(category) + assert_includes author.named_categories.reload, category end def test_collection_create_with_nonstandard_primary_key_on_belongs_to author = authors(:mary) category = author.named_categories.create(name: "Primary") assert Categorization.exists?(author_id: author.id, named_category_name: category.name) - assert author.named_categories.reload.include?(category) + assert_includes author.named_categories.reload, category end def test_collection_exists @@ -908,14 +908,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase person = Person.new reference = person.references.build job = reference.build_job - assert person.jobs.include?(job) + assert_includes person.jobs, job end def test_include_method_in_association_through_should_return_true_for_instance_added_with_nested_builds author = Author.new post = author.posts.build comment = post.comments.build - assert author.comments.include?(comment) + assert_includes author.comments, comment end def test_through_association_readonly_should_be_false @@ -1022,7 +1022,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase post = posts(:welcome) address = author_addresses(:david_address) - assert post.author_addresses.include?(address) + assert_includes post.author_addresses, address post.author_addresses.delete(address) assert post[:author_count].nil? end @@ -1107,7 +1107,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_through_obeys_order_on_through_association owner = owners(:blackbeard) - assert owner.toys.to_sql.include?("pets.name desc") + assert_includes owner.toys.to_sql, "pets.name desc" assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name } end diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index e95eeb64db..b2f47d2daf 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -182,7 +182,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase @member.organization = @organization end assert_equal @organization, @member.organization - assert @organization.members.include?(@member) + assert_includes @organization.members, @member assert_equal "Extra", @member.member_detail.extra_data end @@ -197,16 +197,16 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end assert_equal @organization, @member.organization assert_equal "Extra", @member.member_detail.extra_data - assert @organization.members.include?(@member) - assert !@new_organization.members.include?(@member) + assert_includes @organization.members, @member + assert_not_includes @new_organization.members, @member assert_no_difference "MemberDetail.count" do @member.organization = @new_organization end assert_equal @new_organization, @member.organization assert_equal "Extra", @member.member_detail.extra_data - assert !@organization.members.include?(@member) - assert @new_organization.members.include?(@member) + assert_not_includes @organization.members, @member + assert_includes @new_organization.members, @member end def test_preloading_has_one_through_on_belongs_to diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 61678ae210..15a7ae941a 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -24,15 +24,15 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase :edges def test_has_many - assert authors(:david).categories.include?(categories(:general)) + assert_includes authors(:david).categories, categories(:general) end def test_has_many_inherited - assert authors(:mary).categories.include?(categories(:sti_test)) + assert_includes authors(:mary).categories, categories(:sti_test) end def test_inherited_has_many - assert categories(:sti_test).authors.include?(authors(:mary)) + assert_includes categories(:sti_test).authors, authors(:mary) end def test_has_many_distinct_through_join_model @@ -467,10 +467,10 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase saved_post.tags << new_tag assert new_tag.persisted? #consistent with habtm! assert saved_post.persisted? - assert saved_post.tags.include?(new_tag) + assert_includes saved_post.tags, new_tag assert new_tag.persisted? - assert saved_post.reload.tags.reload.include?(new_tag) + assert_includes saved_post.reload.tags.reload, new_tag new_post = Post.new(title: "Association replacement works!", body: "You best believe it.") saved_tag = tags(:general) @@ -478,11 +478,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase new_post.tags << saved_tag assert !new_post.persisted? assert saved_tag.persisted? - assert new_post.tags.include?(saved_tag) + assert_includes new_post.tags, saved_tag new_post.save! assert new_post.persisted? - assert new_post.reload.tags.reload.include?(saved_tag) + assert_includes new_post.reload.tags.reload, saved_tag assert !posts(:thinking).tags.build.persisted? assert !posts(:thinking).tags.new.persisted? @@ -642,8 +642,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_polymorphic_has_many expected = taggings(:welcome_general) p = Post.all.merge!(includes: :taggings).find(posts(:welcome).id) - assert_no_queries { assert p.taggings.include?(expected) } - assert posts(:welcome).taggings.include?(taggings(:welcome_general)) + assert_no_queries { assert_includes p.taggings, expected } + assert_includes posts(:welcome).taggings, taggings(:welcome_general) end def test_polymorphic_has_one @@ -675,8 +675,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end taggables = taggings.map(&:taggable) - assert taggables.include?(items(:dvd)) - assert taggables.include?(posts(:welcome)) + assert_includes taggables, items(:dvd) + assert_includes taggables, posts(:welcome) end def test_preload_nil_polymorphic_belongs_to @@ -709,7 +709,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert_no_queries do assert david.categories.loaded? - assert david.categories.include?(category) + assert_includes david.categories, category end end @@ -720,7 +720,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase david.reload assert ! david.categories.loaded? assert_queries(1) do - assert david.categories.include?(category) + assert_includes david.categories, category end assert ! david.categories.loaded? end diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 5222703570..c095b3a91c 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -131,7 +131,7 @@ class AssociationProxyTest < ActiveRecord::TestCase david.posts << (post = Post.new(title: "New on Edge", body: "More cool stuff!")) assert !david.posts.loaded? - assert david.posts.include?(post) + assert_includes david.posts, post end def test_push_has_many_through_does_not_load_target @@ -139,7 +139,7 @@ class AssociationProxyTest < ActiveRecord::TestCase david.categories << categories(:technology) assert !david.categories.loaded? - assert david.categories.include?(categories(:technology)) + assert_includes david.categories, categories(:technology) end def test_push_followed_by_save_does_not_load_target @@ -149,7 +149,7 @@ class AssociationProxyTest < ActiveRecord::TestCase assert !david.posts.loaded? david.save assert !david.posts.loaded? - assert david.posts.include?(post) + assert_includes david.posts, post end def test_push_does_not_lose_additions_to_new_record diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index 2d2ecb4079..a8592bd179 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require "thread" module ActiveRecord module AttributeMethods @@ -40,13 +39,13 @@ module ActiveRecord instance = @klass.new @klass.attribute_names.each do |name| - assert !instance.methods.map(&:to_s).include?(name) + assert_not_includes instance.methods.map(&:to_s), name end @klass.define_attribute_methods @klass.attribute_names.each do |name| - assert instance.methods.map(&:to_s).include?(name), "#{name} is not defined" + assert_includes instance.methods.map(&:to_s), name, "#{name} is not defined" end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 993d9f8b94..4c77ecab7c 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -755,7 +755,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase 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_includes exception.message, "private method" assert_equal "I'm private", topic.send(:title) end @@ -765,7 +765,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new assert !topic.respond_to?(:title=) exception = assert_raise(NoMethodError) { topic.title = "Pants" } - assert exception.message.include?("private method") + assert_includes exception.message, "private method" topic.send(:title=, "Very large pants") end @@ -775,7 +775,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase 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_includes exception.message, "private method" assert topic.send(:title?) end diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb index c18851257e..f4620ae2da 100644 --- a/activerecord/test/cases/attributes_test.rb +++ b/activerecord/test/cases/attributes_test.rb @@ -112,7 +112,7 @@ module ActiveRecord assert_equal 7, klass.attribute_types.length assert_equal 7, klass.column_defaults.length - assert klass.attribute_types.include?("wibble") + assert_includes klass.attribute_types, "wibble" end test "the given default value is cast from user" do @@ -253,7 +253,7 @@ module ActiveRecord test "attributes not backed by database columns appear in inspect" do inspection = OverloadedType.new.inspect - assert inspection.include?("non_existent_decimal") + assert_includes inspection, "non_existent_decimal" end end end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 08df945063..c24d7b8835 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -443,7 +443,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib assert_not invalid_electron.valid? assert valid_electron.valid? assert_not molecule.valid? - assert_equal [{ error: :blank }], molecule.errors.details["electrons.name"] + assert_equal [{ error: :blank }], molecule.errors.details[:"electrons.name"] end def test_errors_details_should_be_indexed_when_passed_as_array @@ -457,8 +457,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib assert_not tuning_peg_invalid.valid? assert tuning_peg_valid.valid? assert_not guitar.valid? - assert_equal [{ error: :not_a_number, value: nil }] , guitar.errors.details["tuning_pegs[1].pitch"] - assert_equal [], guitar.errors.details["tuning_pegs.pitch"] + assert_equal [{ error: :not_a_number, value: nil }], guitar.errors.details[:"tuning_pegs[1].pitch"] + assert_equal [], guitar.errors.details[:"tuning_pegs.pitch"] end def test_errors_details_should_be_indexed_when_global_flag_is_set @@ -474,8 +474,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib assert_not invalid_electron.valid? assert valid_electron.valid? assert_not molecule.valid? - assert_equal [{ error: :blank }], molecule.errors.details["electrons[1].name"] - assert_equal [], molecule.errors.details["electrons.name"] + assert_equal [{ error: :blank }], molecule.errors.details[:"electrons[1].name"] + assert_equal [], molecule.errors.details[:"electrons.name"] ensure ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config end @@ -585,7 +585,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa firm.save firm.reload assert_equal 2, firm.clients.length - assert firm.clients.include?(companies(:second_client)) + assert_includes firm.clients, companies(:second_client) end def test_assign_ids_for_through_a_belongs_to @@ -594,7 +594,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa post.save post.reload assert_equal 2, post.people.length - assert post.people.include?(people(:david)) + assert_includes post.people, people(:david) end def test_build_before_save @@ -647,7 +647,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa assert firm.save firm.reload assert_equal 2, firm.clients.length - assert firm.clients.include?(Client.find_by_name("New Client")) + assert_includes firm.clients, Client.find_by_name("New Client") end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index c03c3f9546..fafa144c6f 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1519,7 +1519,7 @@ class BasicsTest < ActiveRecord::TestCase test "ignored columns are not present in columns_hash" do cache_columns = Developer.connection.schema_cache.columns_hash(Developer.table_name) assert_includes cache_columns.keys, "first_name" - refute_includes Developer.columns_hash.keys, "first_name" + assert_not_includes Developer.columns_hash.keys, "first_name" end test "ignored columns have no attribute methods" do diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index bacf93d275..db2871d383 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -93,20 +93,20 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_group_by_field c = Account.group(:firm_id).sum(:credit_limit) [1,6,2].each do |firm_id| - assert c.keys.include?(firm_id), "Group #{c.inspect} does not contain firm_id #{firm_id}" + assert_includes c.keys, firm_id, "Group #{c.inspect} does not contain firm_id #{firm_id}" end end def test_should_group_by_arel_attribute c = Account.group(Account.arel_table[:firm_id]).sum(:credit_limit) [1,6,2].each do |firm_id| - assert c.keys.include?(firm_id), "Group #{c.inspect} does not contain firm_id #{firm_id}" + assert_includes c.keys, firm_id, "Group #{c.inspect} does not contain firm_id #{firm_id}" end end def test_should_group_by_multiple_fields c = Account.group("firm_id", :credit_limit).count(:all) - [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) } + [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert_includes c.keys, firm_and_limit } end def test_should_group_by_multiple_fields_having_functions @@ -453,7 +453,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_count_field_in_joined_table_with_group_by c = Account.group("accounts.firm_id").joins(:firm).count("companies.id") - [1,6,2,9].each { |firm_id| assert c.keys.include?(firm_id) } + [1,6,2,9].each { |firm_id| assert_includes c.keys, firm_id } end def test_should_count_field_of_root_table_with_conflicting_group_by_column diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 6a7d333766..09bd00291d 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -495,8 +495,8 @@ class DirtyTest < ActiveRecord::TestCase assert_equal 4, pirate.previous_changes.size assert_equal [nil, "arrr"], pirate.previous_changes["catchphrase"] assert_equal [nil, pirate.id], pirate.previous_changes["id"] - assert pirate.previous_changes.include?("updated_on") - assert pirate.previous_changes.include?("created_on") + assert_includes pirate.previous_changes, "updated_on" + assert_includes pirate.previous_changes, "created_on" assert !pirate.previous_changes.key?("parrot_id") pirate.catchphrase = "Yar!!" @@ -631,7 +631,7 @@ class DirtyTest < ActiveRecord::TestCase assert_equal("arrrr", pirate.catchphrase_was) assert pirate.catchphrase_changed?(from: "arrrr") assert_not pirate.catchphrase_changed?(from: "anything else") - assert pirate.changed_attributes.include?(:catchphrase) + assert_includes pirate.changed_attributes, :catchphrase pirate.save! pirate.reload diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index c2009843f0..b7641fcf32 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -18,6 +18,7 @@ class EnumTest < ActiveRecord::TestCase assert @book.author_visibility_visible? assert @book.illustrator_visibility_visible? assert @book.with_medium_font_size? + assert @book.medium_to_read? end test "query state with strings" do @@ -26,6 +27,7 @@ class EnumTest < ActiveRecord::TestCase assert_equal "english", @book.language assert_equal "visible", @book.author_visibility assert_equal "visible", @book.illustrator_visibility + assert_equal "medium", @book.difficulty end test "find via scope" do @@ -34,6 +36,7 @@ class EnumTest < ActiveRecord::TestCase assert_equal @book, Book.in_english.first assert_equal @book, Book.author_visibility_visible.first assert_equal @book, Book.illustrator_visibility_visible.first + assert_equal @book, Book.medium_to_read.first end test "find via where with values" do @@ -422,6 +425,43 @@ class EnumTest < ActiveRecord::TestCase assert_not @book.in_french? end + test "query state by predicate with custom suffix" do + assert @book.medium_to_read? + assert_not @book.easy_to_read? + assert_not @book.hard_to_read? + end + + test "enum methods with custom suffix defined" do + assert @book.class.respond_to?(:easy_to_read) + assert @book.class.respond_to?(:medium_to_read) + assert @book.class.respond_to?(:hard_to_read) + + assert @book.respond_to?(:easy_to_read?) + assert @book.respond_to?(:medium_to_read?) + assert @book.respond_to?(:hard_to_read?) + + assert @book.respond_to?(:easy_to_read!) + assert @book.respond_to?(:medium_to_read!) + assert @book.respond_to?(:hard_to_read!) + end + + test "update enum attributes with custom suffix" do + @book.medium_to_read! + assert_not @book.easy_to_read? + assert @book.medium_to_read? + assert_not @book.hard_to_read? + + @book.easy_to_read! + assert @book.easy_to_read? + assert_not @book.medium_to_read? + assert_not @book.hard_to_read? + + @book.hard_to_read! + assert_not @book.easy_to_read? + assert_not @book.medium_to_read? + assert @book.hard_to_read? + end + test "uses default status when no status is provided in fixtures" do book = books(:tlg) assert book.proposed?, "expected fixture to default to proposed status" diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 31bc4fa1f2..51563b347c 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1008,8 +1008,8 @@ class FinderTest < ActiveRecord::TestCase where("project_id=1").to_a assert_equal 3, developers_on_project_one.length developer_names = developers_on_project_one.map(&:name) - assert developer_names.include?("David") - assert developer_names.include?("Jamis") + assert_includes developer_names, "David" + assert_includes developer_names, "Jamis" end def test_joins_dont_clobber_id @@ -1092,7 +1092,7 @@ class FinderTest < ActiveRecord::TestCase order("client_of DESC"). map(&:client_of) - assert client_of.include?(nil) + assert_includes client_of, nil assert_equal [2, 1].sort, client_of.compact.sort end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 24a9894f19..3f111447ff 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -728,7 +728,7 @@ class LoadAllFixturesTest < ActiveRecord::TestCase self.class.fixtures :all if File.symlink? FIXTURES_ROOT + "/all/admin" - assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort + assert_equal %w(admin/accounts admin/users developers namespaced/accounts people tasks), fixture_table_names.sort end ensure ActiveRecord::FixtureSet.reset_cache @@ -741,7 +741,7 @@ class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase self.class.fixtures :all if File.symlink? FIXTURES_ROOT + "/all/admin" - assert_equal %w(admin/accounts admin/users developers people tasks), fixture_table_names.sort + assert_equal %w(admin/accounts admin/users developers namespaced/accounts people tasks), fixture_table_names.sort end ensure ActiveRecord::FixtureSet.reset_cache @@ -923,7 +923,7 @@ class FoxyFixturesTest < ActiveRecord::TestCase end def test_namespaced_models - assert admin_accounts(:signals37).users.include?(admin_users(:david)) + assert_includes admin_accounts(:signals37).users, admin_users(:david) assert_equal 2, admin_accounts(:signals37).users.size end diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb index e0946544ef..e107ff2362 100644 --- a/activerecord/test/cases/hot_compatibility_test.rb +++ b/activerecord/test/cases/hot_compatibility_test.rb @@ -119,11 +119,11 @@ class HotCompatibilityTest < ActiveRecord::TestCase .instance_variable_get(:@cache)[Process.pid] end - # Rails will automatically clear the prepared statements on the connection - # that runs the migration, so we use two connections to simulate what would - # actually happen on a production system; we'd have one connection running the - # migration from the rake task ("ddl_connection" here), and we'd have another - # connection in a web worker. + # Rails will automatically clear the prepared statements on the connection + # that runs the migration, so we use two connections to simulate what would + # actually happen on a production system; we'd have one connection running the + # migration from the rake task ("ddl_connection" here), and we'd have another + # connection in a web worker. def with_two_connections run_without_connection do |original_connection| ActiveRecord::Base.establish_connection(original_connection.merge(pool_size: 2)) diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb index 814d3b63b2..b06fed4f0d 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -51,7 +51,7 @@ class JsonSerializationTest < ActiveRecord::TestCase assert_match %r{^\{"contact":\{}, json assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -62,7 +62,7 @@ class JsonSerializationTest < ActiveRecord::TestCase assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"awesome":true}, json assert_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -73,7 +73,7 @@ class JsonSerializationTest < ActiveRecord::TestCase assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json assert_no_match %r{"awesome":true}, json - assert !json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_not_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_no_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -83,7 +83,7 @@ class JsonSerializationTest < ActiveRecord::TestCase assert_no_match %r{"name":"Konata Izumi"}, json assert_no_match %r{"age":16}, json assert_match %r{"awesome":true}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_includes json, %("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}) assert_match %r{"preferences":\{"shows":"anime"\}}, json end @@ -275,7 +275,7 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase ['"name":"David"', '"posts":[', '{"id":1}', '{"id":2}', '{"id":4}', '{"id":5}', '{"id":6}', '"name":"Mary"', '"posts":[', '{"id":7}', '{"id":9}'].each do |fragment| - assert json.include?(fragment), json + assert_includes json, fragment, json end end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 5c55584ff7..13b6f6daaf 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -181,6 +181,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase p1.touch assert_equal 1, p1.lock_version + assert_not p1.changed?, "Changes should have been cleared" end def test_touch_stale_object diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb index ab8229dbae..03d781d3d2 100644 --- a/activerecord/test/cases/migration/column_attributes_test.rb +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -63,8 +63,6 @@ module ActiveRecord # Do a manual insertion if current_adapter?(:OracleAdapter) connection.execute "insert into test_models (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)" - elsif current_adapter?(:PostgreSQLAdapter) - connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)" else connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)" end diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb index 870a7f4fa7..55c06da411 100644 --- a/activerecord/test/cases/migration/columns_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -32,7 +32,7 @@ module ActiveRecord rename_column :test_models, :first_name, :nick_name TestModel.reset_column_information - assert TestModel.column_names.include?("nick_name") + assert_includes TestModel.column_names, "nick_name" assert_equal ["foo"], TestModel.all.map(&:nick_name) end @@ -45,7 +45,7 @@ module ActiveRecord rename_column "test_models", "first_name", "nick_name" TestModel.reset_column_information - assert TestModel.column_names.include?("nick_name") + assert_includes TestModel.column_names, "nick_name" assert_equal ["foo"], TestModel.all.map(&:nick_name) end @@ -57,7 +57,7 @@ module ActiveRecord rename_column "test_models", "salary", "annual_salary" - assert TestModel.column_names.include?("annual_salary") + assert_includes TestModel.column_names, "annual_salary" default_after = connection.columns("test_models").find { |c| c.name == "annual_salary" }.default assert_equal "70000", default_after end @@ -88,7 +88,7 @@ module ActiveRecord add_column "test_models", "first_name", :string rename_column "test_models", "first_name", "group" - assert TestModel.column_names.include?("group") + assert_includes TestModel.column_names, "group" end def test_rename_column_with_an_index diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb index 5474c9ce71..fc4f700916 100644 --- a/activerecord/test/cases/migration/rename_table_test.rb +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -57,7 +57,7 @@ module ActiveRecord assert_equal "http://www.foreverflying.com/octopus-black7.jpg", connection.select_value("SELECT url FROM octopi WHERE id=1") index = connection.indexes(:octopi).first - assert index.columns.include?("url") + assert_includes index.columns, "url" assert_equal "index_octopi_on_url", index.name end diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 1c276aa765..296dafacc2 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -133,16 +133,12 @@ module ActiveRecord end def test_quote_string_no_column - assert_equal "'lo\\\\l'", @quoter.quote('lo\l', nil) + assert_equal "'lo\\\\l'", @quoter.quote('lo\l') end def test_quote_as_mb_chars_no_column string = ActiveSupport::Multibyte::Chars.new('lo\l') - assert_equal "'lo\\\\l'", @quoter.quote(string, nil) - end - - def test_string_with_crazy_column - assert_equal "'lo\\\\l'", @quoter.quote('lo\l') + assert_equal "'lo\\\\l'", @quoter.quote(string) end def test_quote_duration diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index cb47237e4b..5dac3d064b 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -127,9 +127,9 @@ class ReflectionTest < ActiveRecord::TestCase :gps_location, nil, {}, Customer ) - assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location) - assert Customer.reflect_on_all_aggregations.include?(reflection_for_balance) - assert Customer.reflect_on_all_aggregations.include?(reflection_for_address) + assert_includes Customer.reflect_on_all_aggregations, reflection_for_gps_location + assert_includes Customer.reflect_on_all_aggregations, reflection_for_balance + assert_includes Customer.reflect_on_all_aggregations, reflection_for_address assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address) diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index bc3b1de697..278dac8171 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -86,9 +86,9 @@ class RelationMergingTest < ActiveRecord::TestCase merged = left.merge(right) assert_equal expected, merged.bound_attributes - assert !merged.to_sql.include?("omg") - assert merged.to_sql.include?("wtf") - assert merged.to_sql.include?("bbq") + assert_not_includes merged.to_sql, "omg" + assert_includes merged.to_sql, "wtf" + assert_includes merged.to_sql, "bbq" end def test_merging_reorders_bind_params diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index e7dc06c106..966ae83a3f 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -71,7 +71,7 @@ module ActiveRecord test "#references!" do assert relation.references!(:foo).equal?(relation) - assert relation.references_values.include?("foo") + assert_includes relation.references_values, "foo" end test "extending!" do diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 3efaace69d..dcaae5b462 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -530,8 +530,8 @@ class RelationTest < ActiveRecord::TestCase assert_equal 3, developers_on_project_one.length developer_names = developers_on_project_one.map(&:name) - assert developer_names.include?("David") - assert developer_names.include?("Jamis") + assert_includes developer_names, "David" + assert_includes developer_names, "Jamis" end def test_find_on_hash_conditions @@ -748,11 +748,11 @@ class RelationTest < ActiveRecord::TestCase posts = Post.preload(:comments) post = posts.find { |p| p.id == 1 } assert_equal 2, post.comments.size - assert post.comments.include?(comments(:greetings)) + assert_includes post.comments, comments(:greetings) post = Post.where("posts.title = 'Welcome to the weblog'").preload(:comments).first assert_equal 2, post.comments.size - assert post.comments.include?(comments(:greetings)) + assert_includes post.comments, comments(:greetings) posts = Post.preload(:last_comment) post = posts.find { |p| p.id == 1 } diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 6cfe4b9581..61062da3e1 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -63,7 +63,7 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_default_scoping_with_threads 2.times do Thread.new { - assert DeveloperOrderedBySalary.all.to_sql.include?("salary DESC") + assert_includes DeveloperOrderedBySalary.all.to_sql, "salary DESC" DeveloperOrderedBySalary.connection.close }.join end @@ -368,7 +368,7 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_unscoped_with_named_scope_should_not_have_default_scope assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor - assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis)) + assert_includes DeveloperCalledJamis.unscoped.poor, developers(:david).becomes(DeveloperCalledJamis) assert_equal 11, DeveloperCalledJamis.unscoped.length assert_equal 1, DeveloperCalledJamis.poor.length diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 2f328e6e70..58e1310ab0 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -33,8 +33,8 @@ class NamedScopingTest < ActiveRecord::TestCase all_posts.to_a new_post = Topic.create! - assert !all_posts.include?(new_post) - assert all_posts.reload.include?(new_post) + assert_not_includes all_posts, new_post + assert_includes all_posts.reload, new_post end def test_delegates_finds_and_calculations_to_the_base_class diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index a46123f451..27b4583457 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -133,8 +133,8 @@ class RelationScopingTest < ActiveRecord::TestCase scoped_developers = Developer.includes(:projects).scoping do Developer.where("projects.id" => 2).to_a end - assert scoped_developers.include?(developers(:david)) - assert !scoped_developers.include?(developers(:jamis)) + assert_includes scoped_developers, developers(:david) + assert_not_includes scoped_developers, developers(:jamis) assert_equal 1, scoped_developers.size end @@ -143,8 +143,8 @@ class RelationScopingTest < ActiveRecord::TestCase Developer.where("developers_projects.project_id = 2").to_a end - assert scoped_developers.include?(developers(:david)) - assert !scoped_developers.include?(developers(:jamis)) + assert_includes scoped_developers, developers(:david) + assert_not_includes scoped_developers, developers(:jamis) assert_equal 1, scoped_developers.size assert_equal developers(:david).attributes, scoped_developers.first.attributes end @@ -155,7 +155,7 @@ class RelationScopingTest < ActiveRecord::TestCase end assert_equal 1, new_comment.post_id - assert Post.find(1).comments.include?(new_comment) + assert_includes Post.find(1).comments, new_comment end def test_scoped_create_with_create_with @@ -164,7 +164,7 @@ class RelationScopingTest < ActiveRecord::TestCase end assert_equal 1, new_comment.post_id - assert Post.find(1).comments.include?(new_comment) + assert_includes Post.find(1).comments, new_comment end def test_scoped_create_with_create_with_has_higher_priority @@ -173,7 +173,7 @@ class RelationScopingTest < ActiveRecord::TestCase end assert_equal 1, new_comment.post_id - assert Post.find(1).comments.include?(new_comment) + assert_includes Post.find(1).comments, new_comment end def test_ensure_that_method_scoping_is_correctly_restored diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 079db3fe8b..d847a02679 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -30,7 +30,7 @@ module ActiveRecord protected_environments = ActiveRecord::Base.protected_environments.dup current_env = ActiveRecord::Migrator.current_environment - assert !protected_environments.include?(current_env) + assert_not_includes protected_environments, current_env # Assert no error ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 8f0b2bd313..834365660f 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -511,9 +511,10 @@ class TransactionTest < ActiveRecord::TestCase topic = Topic.new(title: "test") topic.freeze e = assert_raise(RuntimeError) { topic.save } - assert_match(/frozen/i, e.message) # Not good enough, but we can't do much - # about it since there is no specific error - # for frozen objects. + # Not good enough, but we can't do much + # about it since there is no specific error + # for frozen objects. + assert_match(/frozen/i, e.message) assert !topic.persisted?, "not persisted" assert_nil topic.id assert topic.frozen?, "not frozen" @@ -630,11 +631,11 @@ class TransactionTest < ActiveRecord::TestCase assert_nothing_raised do Topic.reset_column_information Topic.connection.add_column("topics", "stuff", :string) - assert Topic.column_names.include?("stuff") + assert_includes Topic.column_names, "stuff" Topic.reset_column_information Topic.connection.remove_column("topics", "stuff") - assert !Topic.column_names.include?("stuff") + assert_not_includes Topic.column_names, "stuff" end if Topic.connection.supports_ddl_transactions? diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index bdae806f7c..fd88a3ea67 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -36,7 +36,7 @@ class I18nValidationTest < ActiveRecord::TestCase # are used to generate tests to keep things DRY # COMMON_CASES = [ - # [ case, validation_options, generate_message_options] + # [ case, validation_options, generate_message_options] [ "given no options", {}, {}], [ "given custom message", { message: "custom" }, { message: "custom" }], [ "given if condition", { if: lambda { true } }, {}], diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb index 3cbfbc22c6..0e38cee334 100644 --- a/activerecord/test/cases/view_test.rb +++ b/activerecord/test/cases/view_test.rb @@ -149,7 +149,7 @@ if ActiveRecord::Base.connection.supports_views? end end -# sqlite dose not support CREATE, INSERT, and DELETE for VIEW + # sqlite dose not support CREATE, INSERT, and DELETE for VIEW if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) class UpdateableViewTest < ActiveRecord::TestCase self.use_transactional_tests = false diff --git a/activerecord/test/fixtures/all/namespaced/accounts.yml b/activerecord/test/fixtures/all/namespaced/accounts.yml new file mode 100644 index 0000000000..9e341a15af --- /dev/null +++ b/activerecord/test/fixtures/all/namespaced/accounts.yml @@ -0,0 +1,2 @@ +signals37: + name: 37signals diff --git a/activerecord/test/fixtures/books.yml b/activerecord/test/fixtures/books.yml index a304fba399..b3625ee72e 100644 --- a/activerecord/test/fixtures/books.yml +++ b/activerecord/test/fixtures/books.yml @@ -9,6 +9,7 @@ awdr: author_visibility: :visible illustrator_visibility: :visible font_size: :medium + difficulty: :medium rfr: author_id: 1 diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb index 4c275fdebb..17bf3fbcb4 100644 --- a/activerecord/test/models/book.rb +++ b/activerecord/test/models/book.rb @@ -14,6 +14,7 @@ class Book < ActiveRecord::Base enum author_visibility: [:visible, :invisible], _prefix: true enum illustrator_visibility: [:visible, :invisible], _prefix: true enum font_size: [:small, :medium, :large], _prefix: :with, _suffix: true + enum difficulty: [:easy, :medium, :hard], _suffix: :to_read enum cover: { hard: "hard", soft: "soft" } def published! diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 9997ddee77..a4756ec75a 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -98,6 +98,7 @@ ActiveRecord::Schema.define do t.column :author_visibility, :integer, default: 0 t.column :illustrator_visibility, :integer, default: 0 t.column :font_size, :integer, default: 0 + t.column :difficulty, :integer, default: 0 t.column :cover, :string, default: "hard" end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 8ef91fa3f0..e65e472d08 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -200,15 +200,15 @@ module ActiveSupport # You may also specify additional options via the +options+ argument. # Setting <tt>force: true</tt> forces a cache "miss," meaning we treat # the cache value as missing even if it's present. Passing a block is - # required when `force` is true so this always results in a cache write. + # required when +force+ is true so this always results in a cache write. # # cache.write('today', 'Monday') # cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday' # cache.fetch('today', force: true) # => ArgumentError # - # The `:force` option is useful when you're calling some other method to + # The +:force+ option is useful when you're calling some other method to # ask whether you should force a cache write. Otherwise, it's clearer to - # just call `Cache#write`. + # just call <tt>Cache#write</tt>. # # Setting <tt>:compress</tt> will store a large cache entry set by the call # in a compressed format. @@ -361,6 +361,9 @@ module ActiveSupport # the cache with the given keys, then that data is returned. Otherwise, # the supplied block is called for each key for which there was no data, # and the result will be written to the cache and returned. + # Therefore, you need to pass a block that returns the data to be written + # to the cache. If you do not want to write the cache when the cache is + # not found, use #read_multi. # # Options are passed to the underlying cache implementation. # @@ -374,6 +377,8 @@ module ActiveSupport # # "unknown_key" => "Fallback value for key: unknown_key" } # def fetch_multi(*names) + raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given? + options = names.extract_options! options = merged_options(options) results = read_multi(*names, options) diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index ecf0c0eec7..3a2c7b0e74 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -102,9 +102,9 @@ module ActiveSupport end end - # A hook invoked every time a before callback is halted. - # This can be overridden in ActiveSupport::Callbacks implementors in order - # to provide better debugging/logging. + # A hook invoked every time a before callback is halted. + # This can be overridden in ActiveSupport::Callbacks implementors in order + # to provide better debugging/logging. def halted_callback_hook(filter) end @@ -367,15 +367,15 @@ module ActiveSupport lambda { |*args, &blk| !l.call(*args, &blk) } end - # Filters support: - # - # Symbols:: A method to call. - # Strings:: Some content to evaluate. - # Procs:: A proc to call with the object. - # Objects:: An object with a <tt>before_foo</tt> method on it to call. - # - # All of these objects are converted into a lambda and handled - # the same after this point. + # Filters support: + # + # Symbols:: A method to call. + # Strings:: Some content to evaluate. + # Procs:: A proc to call with the object. + # Objects:: An object with a <tt>before_foo</tt> method on it to call. + # + # All of these objects are converted into a lambda and handled + # the same after this point. def make_lambda(filter) case filter when Symbol @@ -422,9 +422,9 @@ module ActiveSupport end end - # Execute before and after filters in a sequence instead of - # chaining them with nested lambda calls, see: - # https://github.com/rails/rails/issues/18011 + # Execute before and after filters in a sequence instead of + # chaining them with nested lambda calls, see: + # https://github.com/rails/rails/issues/18011 class CallbackSequence def initialize(&call) @call = call @@ -458,7 +458,7 @@ module ActiveSupport end end - # An Array with a compile method. + # An Array with a compile method. class CallbackChain #:nodoc:# include Enumerable diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb index 8969ebb080..4318523b07 100644 --- a/activesupport/lib/active_support/concurrency/share_lock.rb +++ b/activesupport/lib/active_support/concurrency/share_lock.rb @@ -199,7 +199,7 @@ module ActiveSupport private - # Must be called within synchronize + # Must be called within synchronize def busy_for_exclusive?(purpose) busy_for_sharing?(purpose) || @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0) diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb index df56f13b8d..e96cb8e883 100644 --- a/activesupport/lib/active_support/duration/iso8601_parser.rb +++ b/activesupport/lib/active_support/duration/iso8601_parser.rb @@ -84,7 +84,7 @@ module ActiveSupport scanner.eos? end - # Parses number which can be a float with either comma or period. + # Parses number which can be a float with either comma or period. def number PERIOD_OR_COMMA.match?(scanner[1]) ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i end @@ -97,7 +97,7 @@ module ActiveSupport raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip end - # Checks for various semantic errors as stated in ISO 8601 standard. + # Checks for various semantic errors as stated in ISO 8601 standard. def validate! raise_parsing_error("is empty duration") if parts.empty? diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 0b7d67e37a..98aceabe21 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -105,13 +105,13 @@ module ActiveSupport @updated_at || max_mtime(paths) || Time.at(0) end - # This method returns the maximum mtime of the files in +paths+, or +nil+ - # if the array is empty. - # - # Files with a mtime in the future are ignored. Such abnormal situation - # can happen for example if the user changes the clock by hand. It is - # healthy to consider this edge case because with mtimes in the future - # reloading is not triggered. + # This method returns the maximum mtime of the files in +paths+, or +nil+ + # if the array is empty. + # + # Files with a mtime in the future are ignored. Such abnormal situation + # can happen for example if the user changes the clock by hand. It is + # healthy to consider this edge case because with mtimes in the future + # reloading is not triggered. def max_mtime(paths) time_now = Time.now max_mtime = nil diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 79cede2a0c..c80243c40a 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -356,11 +356,11 @@ module ActiveSupport private - # Mounts a regular expression, returned as a string to ease interpolation, - # that will match part by part the given constant. - # - # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?" - # const_regexp("::") # => "::" + # Mounts a regular expression, returned as a string to ease interpolation, + # that will match part by part the given constant. + # + # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?" + # const_regexp("::") # => "::" def const_regexp(camel_cased_word) #:nodoc: parts = camel_cased_word.split("::".freeze) @@ -373,10 +373,10 @@ module ActiveSupport end end - # Applies inflection rules for +singularize+ and +pluralize+. - # - # apply_inflections('post', inflections.plurals) # => "posts" - # apply_inflections('posts', inflections.singulars) # => "post" + # Applies inflection rules for +singularize+ and +pluralize+. + # + # apply_inflections('post', inflections.plurals) # => "posts" + # apply_inflections('posts', inflections.singulars) # => "post" def apply_inflections(word, rules) result = word.to_s.dup diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb index 156cfc32f8..1064398d8c 100644 --- a/activesupport/lib/active_support/key_generator.rb +++ b/activesupport/lib/active_support/key_generator.rb @@ -51,8 +51,8 @@ module ActiveSupport private - # To prevent users from using something insecure like "Password" we make sure that the - # secret they've provided is at least 30 characters in length. + # To prevent users from using something insecure like "Password" we make sure that the + # secret they've provided is at least 30 characters in length. def ensure_secret_secure(secret) if secret.blank? raise ArgumentError, "A secret is required to generate an integrity hash " \ diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index be2fceb123..f7586efe6a 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -133,7 +133,7 @@ module ActiveSupport period.zone_identifier.to_s end - # Returns a string of the object's date, time, zone and offset from UTC. + # Returns a string of the object's date, time, zone, and offset from UTC. # # Time.zone.now.inspect # => "Thu, 04 Dec 2014 11:00:25 EST -05:00" def inspect diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index 70864038e8..46b91806f6 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -157,7 +157,7 @@ module ActiveSupport "#{left}#{middle.tr('_ ', '--')}#{right}" end - # TODO: Add support for other encodings + # TODO: Add support for other encodings def _parse_binary(bin, entity) #:nodoc: case entity["encoding"] when "base64" diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb index c698780da8..10498c9be0 100644 --- a/activesupport/lib/active_support/xml_mini/jdom.rb +++ b/activesupport/lib/active_support/xml_mini/jdom.rb @@ -52,12 +52,12 @@ module ActiveSupport private - # Convert an XML element and merge into the hash - # - # hash:: - # Hash to merge the converted element into. - # element:: - # XML element to merge into hash + # Convert an XML element and merge into the hash + # + # hash:: + # Hash to merge the converted element into. + # element:: + # XML element to merge into hash def merge_element!(hash, element, depth) raise "Document too deep!" if depth == 0 delete_empty(hash) @@ -68,10 +68,10 @@ module ActiveSupport hash.delete(CONTENT_KEY) if hash[CONTENT_KEY] == "" end - # Actually converts an XML document element into a data structure. - # - # element:: - # The document element to be collapsed. + # Actually converts an XML document element into a data structure. + # + # element:: + # The document element to be collapsed. def collapse(element, depth) hash = get_attributes(element) @@ -88,12 +88,12 @@ module ActiveSupport end end - # Merge all the texts of an element into the hash - # - # hash:: - # Hash to add the converted element to. - # element:: - # XML element whose texts are to me merged into the hash + # Merge all the texts of an element into the hash + # + # hash:: + # Hash to add the converted element to. + # element:: + # XML element whose texts are to me merged into the hash def merge_texts!(hash, element) delete_empty(hash) text_children = texts(element) @@ -105,17 +105,17 @@ module ActiveSupport end end - # Adds a new key/value pair to an existing Hash. If the key to be added - # already exists and the existing value associated with key is not - # an Array, it will be wrapped in an Array. Then the new value is - # appended to that Array. - # - # hash:: - # Hash to add key/value pair to. - # key:: - # Key to be added. - # value:: - # Value to be associated with key. + # Adds a new key/value pair to an existing Hash. If the key to be added + # already exists and the existing value associated with key is not + # an Array, it will be wrapped in an Array. Then the new value is + # appended to that Array. + # + # hash:: + # Hash to add key/value pair to. + # key:: + # Key to be added. + # value:: + # Value to be associated with key. def merge!(hash, key, value) if hash.has_key?(key) if hash[key].instance_of?(Array) @@ -131,11 +131,11 @@ module ActiveSupport hash end - # Converts the attributes array of an XML element into a hash. - # Returns an empty Hash if node has no attributes. - # - # element:: - # XML element to extract attributes from. + # Converts the attributes array of an XML element into a hash. + # Returns an empty Hash if node has no attributes. + # + # element:: + # XML element to extract attributes from. def get_attributes(element) attribute_hash = {} attributes = element.attributes @@ -146,10 +146,10 @@ module ActiveSupport attribute_hash end - # Determines if a document element has text content - # - # element:: - # XML element to be checked. + # Determines if a document element has text content + # + # element:: + # XML element to be checked. def texts(element) texts = [] child_nodes = element.child_nodes @@ -162,10 +162,10 @@ module ActiveSupport texts end - # Determines if a document element has text content - # - # element:: - # XML element to be checked. + # Determines if a document element has text content + # + # element:: + # XML element to be checked. def empty_content?(element) text = "" child_nodes = element.child_nodes diff --git a/activesupport/test/autoload_test.rb b/activesupport/test/autoload_test.rb index c4d6a69212..6c8aa3e055 100644 --- a/activesupport/test/autoload_test.rb +++ b/activesupport/test/autoload_test.rb @@ -31,7 +31,7 @@ class TestAutoloadModule < ActiveSupport::TestCase end end - assert !$LOADED_FEATURES.include?(@some_class_path) + assert_not_includes $LOADED_FEATURES, @some_class_path assert_nothing_raised { ::Fixtures::Autoload::SomeClass } end @@ -40,7 +40,7 @@ class TestAutoloadModule < ActiveSupport::TestCase autoload :SomeClass end - assert !$LOADED_FEATURES.include?(@some_class_path) + assert_not_includes $LOADED_FEATURES, @some_class_path assert_nothing_raised { ::Fixtures::Autoload::SomeClass } end @@ -51,9 +51,9 @@ class TestAutoloadModule < ActiveSupport::TestCase end end - assert !$LOADED_FEATURES.include?(@some_class_path) + assert_not_includes $LOADED_FEATURES, @some_class_path ::Fixtures::Autoload.eager_load! - assert $LOADED_FEATURES.include?(@some_class_path) + assert_includes $LOADED_FEATURES, @some_class_path assert_nothing_raised { ::Fixtures::Autoload::SomeClass } end @@ -64,7 +64,7 @@ class TestAutoloadModule < ActiveSupport::TestCase end end - assert !$LOADED_FEATURES.include?(@another_class_path) + assert_not_includes $LOADED_FEATURES, @another_class_path assert_nothing_raised { ::Fixtures::AnotherClass } end @@ -75,7 +75,7 @@ class TestAutoloadModule < ActiveSupport::TestCase end end - assert !$LOADED_FEATURES.include?(@another_class_path) + assert_not_includes $LOADED_FEATURES, @another_class_path assert_nothing_raised { ::Fixtures::AnotherClass } end end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index a669d666be..0df4173a1a 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -363,6 +363,12 @@ module CacheStoreBehavior assert_equal({ foo => "FOO!", bar => "BAM!" }, values) end + def test_fetch_multi_without_block + assert_raises(ArgumentError) do + @cache.fetch_multi("foo") + end + end + def test_read_and_write_compressed_small_data @cache.write("foo", "bar", compress: true) assert_equal "bar", @cache.read("foo") diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb index 4af9ce8163..95507c815d 100644 --- a/activesupport/test/concern_test.rb +++ b/activesupport/test/concern_test.rb @@ -66,7 +66,7 @@ class ConcernTest < ActiveSupport::TestCase def test_module_is_included_normally @klass.include(Baz) assert_equal "baz", @klass.new.baz - assert @klass.included_modules.include?(ConcernTest::Baz) + assert_includes @klass.included_modules, ConcernTest::Baz end def test_class_methods_are_extended @@ -105,7 +105,7 @@ class ConcernTest < ActiveSupport::TestCase assert_equal "bar", @klass.new.bar assert_equal "bar+baz", @klass.new.baz assert_equal "bar's baz + baz", @klass.baz - assert @klass.included_modules.include?(ConcernTest::Bar) + assert_includes @klass.included_modules, ConcernTest::Bar end def test_dependencies_with_multiple_modules diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb index c67ea8b222..3cd6d2d4d0 100644 --- a/activesupport/test/configurable_test.rb +++ b/activesupport/test/configurable_test.rb @@ -121,11 +121,11 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase def assert_method_defined(object, method) methods = object.public_methods.map(&:to_s) - assert methods.include?(method.to_s), "Expected #{methods.inspect} to include #{method.to_s.inspect}" + assert_includes methods, method.to_s, "Expected #{methods.inspect} to include #{method.to_s.inspect}" end def assert_method_not_defined(object, method) methods = object.public_methods.map(&:to_s) - assert !methods.include?(method.to_s), "Expected #{methods.inspect} to not include #{method.to_s.inspect}" + assert_not_includes methods, method.to_s, "Expected #{methods.inspect} to not include #{method.to_s.inspect}" end end diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb index ccbdab19f0..469efcd934 100644 --- a/activesupport/test/core_ext/array/conversions_test.rb +++ b/activesupport/test/core_ext/array/conversions_test.rb @@ -97,28 +97,28 @@ class ToXmlTest < ActiveSupport::TestCase ].to_xml(skip_instruct: true, indent: 0) assert_equal '<objects type="array"><object>', xml.first(30) - assert xml.include?(%(<age type="integer">26</age>)), xml - assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>)), xml - assert xml.include?(%(<name>David</name>)), xml - assert xml.include?(%(<age type="integer">31</age>)), xml - assert xml.include?(%(<age-in-millis type="decimal">1.0</age-in-millis>)), xml - assert xml.include?(%(<name>Jason</name>)), xml + assert_includes xml, %(<age type="integer">26</age>), xml + assert_includes xml, %(<age-in-millis type="integer">820497600000</age-in-millis>), xml + assert_includes xml, %(<name>David</name>), xml + assert_includes xml, %(<age type="integer">31</age>), xml + assert_includes xml, %(<age-in-millis type="decimal">1.0</age-in-millis>), xml + assert_includes xml, %(<name>Jason</name>), xml end def test_to_xml_with_non_hash_elements xml = %w[1 2 3].to_xml(skip_instruct: true, indent: 0) assert_equal '<strings type="array"><string', xml.first(29) - assert xml.include?(%(<string>2</string>)), xml + assert_includes xml, %(<string>2</string>), xml end def test_to_xml_with_non_hash_different_type_elements xml = [1, 2.0, "3"].to_xml(skip_instruct: true, indent: 0) assert_equal '<objects type="array"><object', xml.first(29) - assert xml.include?(%(<object type="integer">1</object>)), xml - assert xml.include?(%(<object type="float">2.0</object>)), xml - assert xml.include?(%(object>3</object>)), xml + assert_includes xml, %(<object type="integer">1</object>), xml + assert_includes xml, %(<object type="float">2.0</object>), xml + assert_includes xml, %(object>3</object>), xml end def test_to_xml_with_dedicated_name @@ -135,10 +135,10 @@ class ToXmlTest < ActiveSupport::TestCase ].to_xml(skip_instruct: true, skip_types: true, indent: 0) assert_equal "<objects><object>", xml.first(17) - assert xml.include?(%(<street-address>Paulina</street-address>)) - assert xml.include?(%(<name>David</name>)) - assert xml.include?(%(<street-address>Evergreen</street-address>)) - assert xml.include?(%(<name>Jason</name>)) + assert_includes xml, %(<street-address>Paulina</street-address>) + assert_includes xml, %(<name>David</name>) + assert_includes xml, %(<street-address>Evergreen</street-address>) + assert_includes xml, %(<name>Jason</name>) end def test_to_xml_with_indent_set @@ -147,10 +147,10 @@ class ToXmlTest < ActiveSupport::TestCase ].to_xml(skip_instruct: true, skip_types: true, indent: 4) assert_equal "<objects>\n <object>", xml.first(22) - assert xml.include?(%(\n <street-address>Paulina</street-address>)) - assert xml.include?(%(\n <name>David</name>)) - assert xml.include?(%(\n <street-address>Evergreen</street-address>)) - assert xml.include?(%(\n <name>Jason</name>)) + assert_includes xml, %(\n <street-address>Paulina</street-address>) + assert_includes xml, %(\n <name>David</name>) + assert_includes xml, %(\n <street-address>Evergreen</street-address>) + assert_includes xml, %(\n <name>Jason</name>) end def test_to_xml_with_dasherize_false @@ -159,8 +159,8 @@ class ToXmlTest < ActiveSupport::TestCase ].to_xml(skip_instruct: true, skip_types: true, indent: 0, dasherize: false) assert_equal "<objects><object>", xml.first(17) - assert xml.include?(%(<street_address>Paulina</street_address>)) - assert xml.include?(%(<street_address>Evergreen</street_address>)) + assert_includes xml, %(<street_address>Paulina</street_address>) + assert_includes xml, %(<street_address>Evergreen</street_address>) end def test_to_xml_with_dasherize_true @@ -169,8 +169,8 @@ class ToXmlTest < ActiveSupport::TestCase ].to_xml(skip_instruct: true, skip_types: true, indent: 0, dasherize: true) assert_equal "<objects><object>", xml.first(17) - assert xml.include?(%(<street-address>Paulina</street-address>)) - assert xml.include?(%(<street-address>Evergreen</street-address>)) + assert_includes xml, %(<street-address>Paulina</street-address>) + assert_includes xml, %(<street-address>Evergreen</street-address>) end def test_to_xml_with_instruct @@ -191,7 +191,7 @@ class ToXmlTest < ActiveSupport::TestCase builder.count 2 end - assert xml.include?(%(<count>2</count>)), xml + assert_includes xml, %(<count>2</count>), xml end def test_to_xml_with_empty diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index e457cb866d..ae35adb19b 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -1127,63 +1127,63 @@ class HashToXmlTest < ActiveSupport::TestCase def test_one_level xml = { name: "David", street: "Paulina" }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street>Paulina</street>)) - assert xml.include?(%(<name>David</name>)) + assert_includes xml, %(<street>Paulina</street>) + assert_includes xml, %(<name>David</name>) end def test_one_level_dasherize_false xml = { name: "David", street_name: "Paulina" }.to_xml(@xml_options.merge(dasherize: false)) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street_name>Paulina</street_name>)) - assert xml.include?(%(<name>David</name>)) + assert_includes xml, %(<street_name>Paulina</street_name>) + assert_includes xml, %(<name>David</name>) end def test_one_level_dasherize_true xml = { name: "David", street_name: "Paulina" }.to_xml(@xml_options.merge(dasherize: true)) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street-name>Paulina</street-name>)) - assert xml.include?(%(<name>David</name>)) + assert_includes xml, %(<street-name>Paulina</street-name>) + assert_includes xml, %(<name>David</name>) end def test_one_level_camelize_true xml = { name: "David", street_name: "Paulina" }.to_xml(@xml_options.merge(camelize: true)) assert_equal "<Person>", xml.first(8) - assert xml.include?(%(<StreetName>Paulina</StreetName>)) - assert xml.include?(%(<Name>David</Name>)) + assert_includes xml, %(<StreetName>Paulina</StreetName>) + assert_includes xml, %(<Name>David</Name>) end def test_one_level_camelize_lower xml = { name: "David", street_name: "Paulina" }.to_xml(@xml_options.merge(camelize: :lower)) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<streetName>Paulina</streetName>)) - assert xml.include?(%(<name>David</name>)) + assert_includes xml, %(<streetName>Paulina</streetName>) + assert_includes xml, %(<name>David</name>) end def test_one_level_with_types xml = { name: "David", street: "Paulina", age: 26, age_in_millis: 820497600000, moved_on: Date.new(2005, 11, 15), resident: :yes }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street>Paulina</street>)) - assert xml.include?(%(<name>David</name>)) - assert xml.include?(%(<age type="integer">26</age>)) - assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>)) - assert xml.include?(%(<moved-on type="date">2005-11-15</moved-on>)) - assert xml.include?(%(<resident type="symbol">yes</resident>)) + assert_includes xml, %(<street>Paulina</street>) + assert_includes xml, %(<name>David</name>) + assert_includes xml, %(<age type="integer">26</age>) + assert_includes xml, %(<age-in-millis type="integer">820497600000</age-in-millis>) + assert_includes xml, %(<moved-on type="date">2005-11-15</moved-on>) + assert_includes xml, %(<resident type="symbol">yes</resident>) end def test_one_level_with_nils xml = { name: "David", street: "Paulina", age: nil }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street>Paulina</street>)) - assert xml.include?(%(<name>David</name>)) - assert xml.include?(%(<age nil="true"/>)) + assert_includes xml, %(<street>Paulina</street>) + assert_includes xml, %(<name>David</name>) + assert_includes xml, %(<age nil="true"/>) end def test_one_level_with_skipping_types xml = { name: "David", street: "Paulina", age: nil }.to_xml(@xml_options.merge(skip_types: true)) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street>Paulina</street>)) - assert xml.include?(%(<name>David</name>)) - assert xml.include?(%(<age nil="true"/>)) + assert_includes xml, %(<street>Paulina</street>) + assert_includes xml, %(<name>David</name>) + assert_includes xml, %(<age nil="true"/>) end def test_one_level_with_yielding @@ -1192,37 +1192,37 @@ class HashToXmlTest < ActiveSupport::TestCase end assert_equal "<person>", xml.first(8) - assert xml.include?(%(<street>Paulina</street>)) - assert xml.include?(%(<name>David</name>)) - assert xml.include?(%(<creator>Rails</creator>)) + assert_includes xml, %(<street>Paulina</street>) + assert_includes xml, %(<name>David</name>) + assert_includes xml, %(<creator>Rails</creator>) end def test_two_levels xml = { name: "David", address: { street: "Paulina" } }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<address><street>Paulina</street></address>)) - assert xml.include?(%(<name>David</name>)) + assert_includes xml, %(<address><street>Paulina</street></address>) + assert_includes xml, %(<name>David</name>) end def test_two_levels_with_second_level_overriding_to_xml xml = { name: "David", address: { street: "Paulina" }, child: IWriteMyOwnXML.new }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<address><street>Paulina</street></address>)) - assert xml.include?(%(<level_one><second_level>content</second_level></level_one>)) + assert_includes xml, %(<address><street>Paulina</street></address>) + assert_includes xml, %(<level_one><second_level>content</second_level></level_one>) end def test_two_levels_with_array xml = { name: "David", addresses: [{ street: "Paulina" }, { street: "Evergreen" }] }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) - assert xml.include?(%(<addresses type="array"><address>)) - assert xml.include?(%(<address><street>Paulina</street></address>)) - assert xml.include?(%(<address><street>Evergreen</street></address>)) - assert xml.include?(%(<name>David</name>)) + assert_includes xml, %(<addresses type="array"><address>) + assert_includes xml, %(<address><street>Paulina</street></address>) + assert_includes xml, %(<address><street>Evergreen</street></address>) + assert_includes xml, %(<name>David</name>) end def test_three_levels_with_array xml = { name: "David", addresses: [{ streets: [ { name: "Paulina" }, { name: "Paulina" } ] } ] }.to_xml(@xml_options) - assert xml.include?(%(<addresses type="array"><address><streets type="array"><street><name>)) + assert_includes xml, %(<addresses type="array"><address><streets type="array"><street><name>) end def test_timezoned_attributes diff --git a/activesupport/test/core_ext/module/concerning_test.rb b/activesupport/test/core_ext/module/concerning_test.rb index 038cbf1f2f..098036828a 100644 --- a/activesupport/test/core_ext/module/concerning_test.rb +++ b/activesupport/test/core_ext/module/concerning_test.rb @@ -4,7 +4,7 @@ require "active_support/core_ext/module/concerning" class ModuleConcerningTest < ActiveSupport::TestCase def test_concerning_declares_a_concern_and_includes_it_immediately klass = Class.new { concerning(:Foo) {} } - assert klass.ancestors.include?(klass::Foo), klass.ancestors.inspect + assert_includes klass.ancestors, klass::Foo, klass.ancestors.inspect end end @@ -21,10 +21,10 @@ class ModuleConcernTest < ActiveSupport::TestCase assert klass.const_defined?(:Baz, false) assert !ModuleConcernTest.const_defined?(:Baz) assert_kind_of ActiveSupport::Concern, klass::Baz - assert !klass.ancestors.include?(klass::Baz), klass.ancestors.inspect + assert_not_includes klass.ancestors, klass::Baz, klass.ancestors.inspect # Public method visibility by default - assert klass::Baz.public_instance_methods.map(&:to_s).include?("should_be_public") + assert_includes klass::Baz.public_instance_methods.map(&:to_s), "should_be_public" # Calls included hook assert_equal 1, Class.new { include klass::Baz }.instance_variable_get("@foo") diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index 25e6693e4d..fc0e72e09a 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -66,15 +66,15 @@ class RangeTest < ActiveSupport::TestCase end def test_exclusive_end_should_not_include_identical_with_inclusive_end - assert !(1...10).include?(1..10) + assert_not_includes (1...10), 1..10 end def test_should_not_include_overlapping_first - assert !(2..8).include?(1..3) + assert_not_includes (2..8), 1..3 end def test_should_not_include_overlapping_last - assert !(2..8).include?(5..9) + assert_not_includes (2..8), 5..9 end def test_should_include_identical_exclusive_with_floats diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index e0edb4a086..54068f5a08 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -114,25 +114,25 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal 1, $check_warnings_load_count assert_equal true, $checked_verbose, "On first load warnings should be enabled." - assert ActiveSupport::Dependencies.loaded.include?(expanded) + assert_includes ActiveSupport::Dependencies.loaded, expanded ActiveSupport::Dependencies.clear assert_not ActiveSupport::Dependencies.loaded.include?(expanded) - assert ActiveSupport::Dependencies.history.include?(expanded) + assert_includes ActiveSupport::Dependencies.history, expanded silence_warnings { require_dependency filename } assert_equal 2, $check_warnings_load_count assert_equal nil, $checked_verbose, "After first load warnings should be left alone." - assert ActiveSupport::Dependencies.loaded.include?(expanded) + assert_includes ActiveSupport::Dependencies.loaded, expanded ActiveSupport::Dependencies.clear assert_not ActiveSupport::Dependencies.loaded.include?(expanded) - assert ActiveSupport::Dependencies.history.include?(expanded) + assert_includes ActiveSupport::Dependencies.history, expanded enable_warnings { require_dependency filename } assert_equal 3, $check_warnings_load_count assert_equal true, $checked_verbose, "After first load warnings should be left alone." - assert ActiveSupport::Dependencies.loaded.include?(expanded) + assert_includes ActiveSupport::Dependencies.loaded, expanded ActiveSupport::Dependencies.warnings_on_first_load = old_warnings end end @@ -1059,13 +1059,13 @@ class DependenciesTest < ActiveSupport::TestCase end def test_load_and_require_stay_private - assert Object.private_methods.include?(:load) - assert Object.private_methods.include?(:require) + assert_includes Object.private_methods, :load + assert_includes Object.private_methods, :require ActiveSupport::Dependencies.unhook! - assert Object.private_methods.include?(:load) - assert Object.private_methods.include?(:require) + assert_includes Object.private_methods, :load + assert_includes Object.private_methods, :require ensure ActiveSupport::Dependencies.hook! end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 39e8c611e5..1c5a4f378c 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -245,12 +245,12 @@ class InflectorTest < ActiveSupport::TestCase end end -# FIXME: get following tests to pass on jruby, currently skipped -# -# Currently this fails because ActiveSupport::Multibyte::Unicode#tidy_bytes -# required a specific Encoding::Converter(UTF-8 to UTF8-MAC) which unavailable on JRuby -# causing our tests to error out. -# related bug http://jira.codehaus.org/browse/JRUBY-7194 + # FIXME: get following tests to pass on jruby, currently skipped + # + # Currently this fails because ActiveSupport::Multibyte::Unicode#tidy_bytes + # required a specific Encoding::Converter(UTF-8 to UTF8-MAC) which unavailable on JRuby + # causing our tests to error out. + # related bug http://jira.codehaus.org/browse/JRUBY-7194 def test_parameterize jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterized.each do |some_string, parameterized_string| diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb index ece4167de9..3f04783401 100644 --- a/activesupport/test/logger_test.rb +++ b/activesupport/test/logger_test.rb @@ -67,7 +67,7 @@ class LoggerTest < ActiveSupport::TestCase def test_should_log_debugging_message_when_debugging @logger.level = Logger::DEBUG @logger.add(Logger::DEBUG, @message) - assert @output.string.include?(@message) + assert_includes @output.string, @message end def test_should_not_log_debug_messages_when_log_level_is_info @@ -79,25 +79,25 @@ class LoggerTest < ActiveSupport::TestCase def test_should_add_message_passed_as_block_when_using_add @logger.level = Logger::INFO @logger.add(Logger::INFO) { @message } - assert @output.string.include?(@message) + assert_includes @output.string, @message end def test_should_add_message_passed_as_block_when_using_shortcut @logger.level = Logger::INFO @logger.info { @message } - assert @output.string.include?(@message) + assert_includes @output.string, @message end def test_should_convert_message_to_string @logger.level = Logger::INFO @logger.info @integer_message - assert @output.string.include?(@integer_message.to_s) + assert_includes @output.string, @integer_message.to_s end def test_should_convert_message_to_string_when_passed_in_block @logger.level = Logger::INFO @logger.info { @integer_message } - assert @output.string.include?(@integer_message.to_s) + assert_includes @output.string, @integer_message.to_s end def test_should_not_evaluate_block_if_message_wont_be_logged @@ -125,10 +125,10 @@ class LoggerTest < ActiveSupport::TestCase @logger.level = Logger::INFO @logger.info(UNICODE_STRING) @logger.info(BYTE_STRING) - assert @output.string.include?(UNICODE_STRING) + assert_includes @output.string, UNICODE_STRING byte_string = @output.string.dup byte_string.force_encoding("ASCII-8BIT") - assert byte_string.include?(BYTE_STRING) + assert_includes byte_string, BYTE_STRING end def test_silencing_everything_but_errors @@ -138,7 +138,7 @@ class LoggerTest < ActiveSupport::TestCase end assert_not @output.string.include?("NOT THERE") - assert @output.string.include?("THIS IS HERE") + assert_includes @output.string, "THIS IS HERE" end def test_logger_silencing_works_for_broadcast @@ -154,12 +154,12 @@ class LoggerTest < ActiveSupport::TestCase @logger.error "CORRECT ERROR" end - assert @output.string.include?("CORRECT DEBUG") - assert @output.string.include?("CORRECT ERROR") + assert_includes @output.string, "CORRECT DEBUG" + assert_includes @output.string, "CORRECT ERROR" assert_not @output.string.include?("FAILURE") - assert another_output.string.include?("CORRECT DEBUG") - assert another_output.string.include?("CORRECT ERROR") + assert_includes another_output.string, "CORRECT DEBUG" + assert_includes another_output.string, "CORRECT ERROR" assert_not another_output.string.include?("FAILURE") end @@ -176,13 +176,13 @@ class LoggerTest < ActiveSupport::TestCase @logger.error "CORRECT ERROR" end - assert @output.string.include?("CORRECT DEBUG") - assert @output.string.include?("CORRECT ERROR") + assert_includes @output.string, "CORRECT DEBUG" + assert_includes @output.string, "CORRECT ERROR" assert_not @output.string.include?("FAILURE") - assert another_output.string.include?("CORRECT DEBUG") - assert another_output.string.include?("CORRECT ERROR") - assert another_output.string.include?("FAILURE") + assert_includes another_output.string, "CORRECT DEBUG" + assert_includes another_output.string, "CORRECT ERROR" + assert_includes another_output.string, "FAILURE" # We can't silence plain ruby Logger cause with thread safety # but at least we don't break it end diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 54429f4aab..5ff1543328 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -213,11 +213,11 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_should_know_if_one_includes_the_other - assert @chars.include?("") - assert @chars.include?("ち") - assert @chars.include?("わ") - assert !@chars.include?("こちわ") - assert !@chars.include?("a") + assert_includes @chars, "" + assert_includes @chars, "ち" + assert_includes @chars, "わ" + assert_not_includes @chars, "こちわ" + assert_not_includes @chars, "a" end def test_include_raises_when_nil_is_passed diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb index d2dfc34040..86da9f193a 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -129,22 +129,22 @@ class OrderedHashTest < ActiveSupport::TestCase copy = @ordered_hash.dup copy.delete("pink") assert_equal copy, @ordered_hash.delete_if { |k, _| k == "pink" } - assert !@ordered_hash.keys.include?("pink") + assert_not_includes @ordered_hash.keys, "pink" end def test_reject! (copy = @ordered_hash.dup).delete("pink") @ordered_hash.reject! { |k, _| k == "pink" } assert_equal copy, @ordered_hash - assert !@ordered_hash.keys.include?("pink") + assert_not_includes @ordered_hash.keys, "pink" end def test_reject copy = @ordered_hash.dup new_ordered_hash = @ordered_hash.reject { |k, _| k == "pink" } assert_equal copy, @ordered_hash - assert !new_ordered_hash.keys.include?("pink") - assert @ordered_hash.keys.include?("pink") + assert_not_includes new_ordered_hash.keys, "pink" + assert_includes @ordered_hash.keys, "pink" assert_instance_of ActiveSupport::OrderedHash, new_ordered_hash end @@ -191,7 +191,7 @@ class OrderedHashTest < ActiveSupport::TestCase def test_shift pair = @ordered_hash.shift assert_equal [@keys.first, @values.first], pair - assert !@ordered_hash.keys.include?(pair.first) + assert_not_includes @ordered_hash.keys, pair.first end def test_keys @@ -201,7 +201,7 @@ class OrderedHashTest < ActiveSupport::TestCase end def test_inspect - assert @ordered_hash.inspect.include?(@hash.inspect) + assert_includes @ordered_hash.inspect, @hash.inspect end def test_json diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index f89b608270..76fee1fdd4 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -505,13 +505,13 @@ class TimeZoneTest < ActiveSupport::TestCase end def test_us_zones - assert ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Hawaii"]) - assert !ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Kuala Lumpur"]) + assert_includes ActiveSupport::TimeZone.us_zones, ActiveSupport::TimeZone["Hawaii"] + assert_not_includes ActiveSupport::TimeZone.us_zones, ActiveSupport::TimeZone["Kuala Lumpur"] end def test_country_zones - assert ActiveSupport::TimeZone.country_zones("ru").include?(ActiveSupport::TimeZone["Moscow"]) - assert !ActiveSupport::TimeZone.country_zones(:ru).include?(ActiveSupport::TimeZone["Kuala Lumpur"]) + assert_includes ActiveSupport::TimeZone.country_zones("ru"), ActiveSupport::TimeZone["Moscow"] + assert_not_includes ActiveSupport::TimeZone.country_zones(:ru), ActiveSupport::TimeZone["Kuala Lumpur"] end def test_to_yaml diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb new file mode 100644 index 0000000000..f568a111f6 --- /dev/null +++ b/guides/bug_report_templates/active_record_migrations_gem.rb @@ -0,0 +1,65 @@ +begin + require "bundler/inline" +rescue LoadError => e + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" + raise e +end + +gemfile(true) do + source "https://rubygems.org" + # Activate the gem you are reporting the issue against. + gem "activerecord", "5.0.0.1" + gem "sqlite3" +end + +require "active_record" +require "minitest/autorun" +require "logger" + +# Ensure backward compatibility with Minitest 4 +Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) + +# This connection will do for database-independent bug reports. +ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") +ActiveRecord::Base.logger = Logger.new(STDOUT) + +ActiveRecord::Schema.define do + create_table :payments, force: true do |t| + t.decimal :amount, precision: 10, scale: 0, default: 0, null: false + end +end + +class Payment < ActiveRecord::Base +end + +class ChangeAmountToAddScale < ActiveRecord::Migration[5.0] + def change + reversible do |dir| + dir.up do + change_column :payments, :amount, :decimal, precision: 10, scale: 2, default: 0, null: false + end + + dir.down do + change_column :payments, :amount, :decimal, precision: 10, scale: 0, default: 0, null: false + end + end + end +end + +class BugTest < Minitest::Test + def test_migration_up + migrator = ActiveRecord::Migrator.new(:up, [ChangeAmountToAddScale]) + migrator.run + Payment.reset_column_information + + assert_equal "decimal(10,2)", Payment.columns.last.sql_type + end + + def test_migration_down + migrator = ActiveRecord::Migrator.new(:down, [ChangeAmountToAddScale]) + migrator.run + Payment.reset_column_information + + assert_equal "decimal(10,0)", Payment.columns.last.sql_type + end +end
\ No newline at end of file diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb new file mode 100644 index 0000000000..ef7b42e0a6 --- /dev/null +++ b/guides/bug_report_templates/active_record_migrations_master.rb @@ -0,0 +1,64 @@ +begin + require "bundler/inline" +rescue LoadError => e + $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" + raise e +end + +gemfile(true) do + source "https://rubygems.org" + gem "rails", github: "rails/rails" + gem "sqlite3" +end + +require "active_record" +require "minitest/autorun" +require "logger" + +# Ensure backward compatibility with Minitest 4 +Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) + +# This connection will do for database-independent bug reports. +ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") +ActiveRecord::Base.logger = Logger.new(STDOUT) + +ActiveRecord::Schema.define do + create_table :payments, force: true do |t| + t.decimal :amount, precision: 10, scale: 0, default: 0, null: false + end +end + +class Payment < ActiveRecord::Base +end + +class ChangeAmountToAddScale < ActiveRecord::Migration[5.0] + def change + reversible do |dir| + dir.up do + change_column :payments, :amount, :decimal, precision: 10, scale: 2, default: 0, null: false + end + + dir.down do + change_column :payments, :amount, :decimal, precision: 10, scale: 0, default: 0, null: false + end + end + end +end + +class BugTest < Minitest::Test + def test_migration_up + migrator = ActiveRecord::Migrator.new(:up, [ChangeAmountToAddScale]) + migrator.run + Payment.reset_column_information + + assert_equal "decimal(10,2)", Payment.columns.last.sql_type + end + + def test_migration_down + migrator = ActiveRecord::Migrator.new(:down, [ChangeAmountToAddScale]) + migrator.run + Payment.reset_column_information + + assert_equal "decimal(10,0)", Payment.columns.last.sql_type + end +end
\ No newline at end of file diff --git a/guides/rails_guides/levenshtein.rb b/guides/rails_guides/levenshtein.rb index a3e7620444..e947150364 100644 --- a/guides/rails_guides/levenshtein.rb +++ b/guides/rails_guides/levenshtein.rb @@ -1,6 +1,8 @@ module RailsGuides module Levenshtein - # This code is based directly on the Text gem implementation + # This code is based directly on the Text gem implementation. + # Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher. + # # Returns a value representing the "cost" of transforming str1 into str2 def self.distance(str1, str2) s = str1 diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 6538629972..42971598ba 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -802,7 +802,8 @@ Please refer to the [Changelog][active-record] for detailed changes. were getting rescued and printed in the logs, unless you used the (newly deprecated) `raise_in_transactional_callbacks = true` option. - Now these errors are not rescued anymore and just bubble up, as the other callbacks. + Now these errors are not rescued anymore and just bubble up, matching the + behavior of other callbacks. ([commit](https://github.com/rails/rails/commit/07d3d402341e81ada0214f2cb2be1da69eadfe72)) Active Model diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index e11466e79f..ff0127522b 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -254,12 +254,6 @@ as if we had written: <%= render partial: "product", locals: { product: @product } %> ``` -With the `as` option we can specify a different name for the local variable. For example, if we wanted it to be `item` instead of `product` we would do: - -```erb -<%= render partial: "product", as: "item" %> -``` - The `object` option can be used to directly specify which object is rendered into the partial; useful when the template's object is elsewhere (e.g. in a different instance variable or in a local variable). For example, instead of: @@ -274,12 +268,18 @@ we would do: <%= render partial: "product", object: @item %> ``` -The `object` and `as` options can also be used together: +With the `as` option we can specify a different name for the said local variable. For example, if we wanted it to be `item` instead of `product` we would do: ```erb <%= render partial: "product", object: @item, as: "item" %> ``` +This is equivalent to + +```erb +<%= render partial: "product", locals: { item: @item } %> +``` + #### Rendering Collections It is very common that a template will need to iterate over a collection and render a sub-template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders a partial for each one of the elements in the array. diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 644756906a..bbe1b0decc 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -50,7 +50,7 @@ class Role < ApplicationRecord end ``` -Active Record will perform queries on the database for you and is compatible with most database systems, including MySQL, MariaDB, PostgreSQL and SQLite. Regardless of which database system you're using, the Active Record method format will always be the same. +Active Record will perform queries on the database for you and is compatible with most database systems, including MySQL, MariaDB, PostgreSQL, and SQLite. Regardless of which database system you're using, the Active Record method format will always be the same. Retrieving Objects from the Database ------------------------------------ diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 5df16e68c9..4f938f5deb 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -40,6 +40,7 @@ Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, th Having a way to reproduce your issue will be very helpful for others to help confirm, investigate and ultimately fix your issue. You can do this by providing an executable test case. To make this process easier, we have prepared several bug report templates for you to use as a starting point: * Template for Active Record (models, database) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb) +* Template for testing Active Record (migration) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_migrations_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_migrations_master.rb) * Template for Action Pack (controllers, routing) issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_master.rb) * Template for Active Job issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_job_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_job_master.rb) * Generic template for other issues: [gem](https://github.com/rails/rails/blob/master/guides/bug_report_templates/generic_gem.rb) / [master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/generic_master.rb) diff --git a/guides/source/engines.md b/guides/source/engines.md index 83c0a7f337..0020112a1c 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -184,7 +184,7 @@ end By inheriting from the `Rails::Engine` class, this gem notifies Rails that there's an engine at the specified path, and will correctly mount the engine inside the application, performing tasks such as adding the `app` directory of -the engine to the load path for models, mailers, controllers and views. +the engine to the load path for models, mailers, controllers, and views. The `isolate_namespace` method here deserves special notice. This call is responsible for isolating the controllers, models, routes and other things into diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 2722789c49..7e4ec5ba7e 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -630,6 +630,8 @@ to use in this case. redirect_back(fallback_location: root_path) ``` +NOTE: `redirect_to` and `redirect_back` do not halt and return immediately from method execution, but simply set HTTP responses. Statements occurring after them in a method will be executed. You can halt by an explicit `return` or some other halting mechanism, if needed. + #### Getting a Different Redirect Status Code Rails uses HTTP status code 302, a temporary redirect, when you call `redirect_to`. If you'd like to use a different status code, perhaps 301, a permanent redirect, you can use the `:status` option: @@ -749,7 +751,7 @@ When Rails renders a view as a response, it does so by combining the view with t ### Asset Tag Helpers -Asset tag helpers provide methods for generating HTML that link views to feeds, JavaScript, stylesheets, images, videos and audios. There are six asset tag helpers available in Rails: +Asset tag helpers provide methods for generating HTML that link views to feeds, JavaScript, stylesheets, images, videos, and audios. There are six asset tag helpers available in Rails: * `auto_discovery_link_tag` * `javascript_include_tag` diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 86d66afddb..90e7c04d7c 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -337,7 +337,7 @@ module Rails # == Loading priority # # In order to change engine's priority you can use +config.railties_order+ in the main application. - # It will affect the priority of loading views, helpers, assets and all the other files + # It will affect the priority of loading views, helpers, assets, and all the other files # related to engine or application. # # # load Blog::Engine with highest priority, followed by application and other railties diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 6a112fc710..fd35dfd1a2 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -277,7 +277,9 @@ module Rails protected - # This code is based directly on the Text gem implementation + # This code is based directly on the Text gem implementation. + # Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher. + # # Returns a value representing the "cost" of transforming str1 into str2 def self.levenshtein_distance(str1, str2) s = str1 diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index d76acbdafc..91342c592c 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -243,7 +243,7 @@ module Rails ] + dev_edge_common elsif options.edge? [ - GemfileEntry.github("rails", "rails/rails", "5-0-stable") + GemfileEntry.github("rails", "rails/rails") ] + dev_edge_common else [GemfileEntry.version("rails", diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb b/railties/lib/rails/generators/rails/app/templates/config/puma.rb index 121ad7080e..7ee948002e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb @@ -32,6 +32,14 @@ environment ENV.fetch("RAILS_ENV") { "development" } # # preload_app! +# If you are preloading your application and using Active Record, it's +# recommended that you close any connections to the database before workers +# are forked to prevent connection leakage. +# +# before_fork do +# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) +# end + # The code in the `on_worker_boot` will be called if you are using # clustered mode by specifying a number of `workers`. After each worker # process is booted this block will be run, if you are using `preload_app!` @@ -42,6 +50,7 @@ environment ENV.fetch("RAILS_ENV") { "development" } # on_worker_boot do # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) # end +# # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 88ec2ba85b..1c1810dde6 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -30,7 +30,7 @@ module Rails # root["config/routes"].inspect # => ["config/routes.rb"] # # The +add+ method accepts the following options as arguments: - # eager_load, autoload, autoload_once and glob. + # eager_load, autoload, autoload_once, and glob. # # Finally, the +Path+ object also provides a few helpers: # diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb index 2f076ea2f6..0bbd25db2b 100644 --- a/railties/test/application/bin_setup_test.rb +++ b/railties/test/application/bin_setup_test.rb @@ -6,10 +6,17 @@ module ApplicationTests def setup build_app + + create_gemfile + update_boot_file_to_use_bundler + @old_gemfile_env = ENV["BUNDLE_GEMFILE"] + ENV["BUNDLE_GEMFILE"] = app_path + "/Gemfile" end def teardown teardown_app + + ENV["BUNDLE_GEMFILE"] = @old_gemfile_env end def test_bin_setup @@ -52,5 +59,16 @@ Created database 'db/test.sqlite3' OUTPUT end end + + private + def create_gemfile + app_file("Gemfile", "source 'https://rubygems.org'") + app_file("Gemfile", "gem 'rails', path: '#{RAILS_FRAMEWORK_ROOT}'", "a") + app_file("Gemfile", "gem 'sqlite3'", "a") + end + + def update_boot_file_to_use_bundler + app_file("config/boot.rb", "require 'bundler/setup'") + end end end diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index 3b8062f74b..72f340df34 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -126,7 +126,7 @@ class FullStackConsoleTest < ActiveSupport::TestCase end end - assert output.include?(expected), "#{expected.inspect} expected, but got:\n\n#{output}" + assert_includes output, expected, "#{expected.inspect} expected, but got:\n\n#{output}" end def write_prompt(command, expected_output = nil) diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index d4008fe677..0153f94504 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -134,10 +134,10 @@ module ApplicationTests require "#{app_path}/config/environment" Rails.application.load_generators - assert Rails::Generators.hidden_namespaces.include?("assets") - assert Rails::Generators.hidden_namespaces.include?("helper") - assert Rails::Generators.hidden_namespaces.include?("js") - assert Rails::Generators.hidden_namespaces.include?("css") + assert_includes Rails::Generators.hidden_namespaces, "assets" + assert_includes Rails::Generators.hidden_namespaces, "helper" + assert_includes Rails::Generators.hidden_namespaces, "js" + assert_includes Rails::Generators.hidden_namespaces, "css" assert Rails::Generators.options[:rails][:api] assert_equal false, Rails::Generators.options[:rails][:assets] assert_equal false, Rails::Generators.options[:rails][:helper] diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb index 0ddaf346e0..206e42703b 100644 --- a/railties/test/application/initializers/i18n_test.rb +++ b/railties/test/application/initializers/i18n_test.rb @@ -30,7 +30,7 @@ module ApplicationTests end def assert_no_fallbacks - assert !I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + assert_not_includes I18n.backend.class.included_modules, I18n::Backend::Fallbacks end # Locales @@ -62,8 +62,8 @@ module ApplicationTests "#{app_path}/config/locales/en.yml", "#{app_path}/config/another_locale.yml" ], Rails.application.config.i18n.load_path - assert I18n.load_path.include?("#{app_path}/config/locales/en.yml") - assert I18n.load_path.include?("#{app_path}/config/another_locale.yml") + assert_includes I18n.load_path, "#{app_path}/config/locales/en.yml" + assert_includes I18n.load_path, "#{app_path}/config/another_locale.yml" end test "load_path is populated before eager loaded models" do @@ -214,7 +214,7 @@ fr: test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings" do I18n::Railtie.config.i18n.fallbacks = true load_app - assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + assert_includes I18n.backend.class.included_modules, I18n::Backend::Fallbacks assert_fallbacks de: [:de, :en] end @@ -222,7 +222,7 @@ fr: I18n::Railtie.config.i18n.fallbacks = true I18n::Railtie.config.i18n.backend = Class.new(I18n::Backend::Simple).new load_app - assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + assert_includes I18n.backend.class.included_modules, I18n::Backend::Fallbacks assert_fallbacks de: [:de, :en] end diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb index caab7c0c8a..dbefb22837 100644 --- a/railties/test/application/initializers/load_path_test.rb +++ b/railties/test/application/initializers/load_path_test.rb @@ -19,7 +19,7 @@ module ApplicationTests RUBY require "#{app_path}/config/environment" - assert $:.include?("#{app_path}/app/models") + assert_includes $:, "#{app_path}/app/models" end test "initializing an application allows to load code on lib path inside application class definition" do @@ -36,7 +36,7 @@ module ApplicationTests require "#{app_path}/config/environment" end - assert $:.include?("#{app_path}/lib") + assert_includes $:, "#{app_path}/lib" end test "initializing an application eager load any path under app" do diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb index 2873425703..a6019a9db4 100644 --- a/railties/test/application/middleware/session_test.rb +++ b/railties/test/application/middleware/session_test.rb @@ -370,7 +370,7 @@ module ApplicationTests assert_equal 200, last_response.status assert_equal "It worked!", last_response.body - refute Rails.application.middleware.include?(ActionDispatch::Flash) + assert_not_includes Rails.application.middleware, ActionDispatch::Flash end test "cookie_only is set to true even if user tries to overwrite it" do diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 1cc931b29e..45481dc1b6 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -73,7 +73,7 @@ module ApplicationTests test "Rack::Cache is not included by default" do boot! - assert !middleware.include?("Rack::Cache"), "Rack::Cache is not included in the default stack unless you set config.action_dispatch.rack_cache" + assert_not_includes middleware, "Rack::Cache", "Rack::Cache is not included in the default stack unless you set config.action_dispatch.rack_cache" end test "Rack::Cache is present when action_dispatch.rack_cache is set" do @@ -81,7 +81,7 @@ module ApplicationTests boot! - assert middleware.include?("Rack::Cache") + assert_includes middleware, "Rack::Cache" end test "ActiveRecord::Migration::CheckPending is present when active_record.migration_error is set to :page_load" do @@ -89,13 +89,13 @@ module ApplicationTests boot! - assert middleware.include?("ActiveRecord::Migration::CheckPending") + assert_includes middleware, "ActiveRecord::Migration::CheckPending" end test "ActionDispatch::SSL is present when force_ssl is set" do add_to_config "config.force_ssl = true" boot! - assert middleware.include?("ActionDispatch::SSL") + assert_includes middleware, "ActionDispatch::SSL" end test "ActionDispatch::SSL is configured with options when given" do @@ -109,7 +109,7 @@ module ApplicationTests test "removing Active Record omits its middleware" do use_frameworks [] boot! - assert !middleware.include?("ActiveRecord::Migration::CheckPending") + assert_not_includes middleware, "ActiveRecord::Migration::CheckPending" end test "includes executor" do @@ -139,20 +139,20 @@ module ApplicationTests test "removes static asset server if public_file_server.enabled is disabled" do add_to_config "config.public_file_server.enabled = false" boot! - assert !middleware.include?("ActionDispatch::Static") + assert_not_includes middleware, "ActionDispatch::Static" end test "can delete a middleware from the stack" do add_to_config "config.middleware.delete ActionDispatch::Static" boot! - assert !middleware.include?("ActionDispatch::Static") + assert_not_includes middleware, "ActionDispatch::Static" end test "can delete a middleware from the stack even if insert_before is added after delete" do add_to_config "config.middleware.delete Rack::Runtime" add_to_config "config.middleware.insert_before(Rack::Runtime, Rack::Config)" boot! - assert middleware.include?("Rack::Config") + assert_includes middleware, "Rack::Config" assert_not middleware.include?("Rack::Runtime") end @@ -160,21 +160,21 @@ module ApplicationTests add_to_config "config.middleware.delete Rack::Runtime" add_to_config "config.middleware.insert_after(Rack::Runtime, Rack::Config)" boot! - assert middleware.include?("Rack::Config") + assert_includes middleware, "Rack::Config" assert_not middleware.include?("Rack::Runtime") end test "includes exceptions middlewares even if action_dispatch.show_exceptions is disabled" do add_to_config "config.action_dispatch.show_exceptions = false" boot! - assert middleware.include?("ActionDispatch::ShowExceptions") - assert middleware.include?("ActionDispatch::DebugExceptions") + assert_includes middleware, "ActionDispatch::ShowExceptions" + assert_includes middleware, "ActionDispatch::DebugExceptions" end test "removes ActionDispatch::Reloader if cache_classes is true" do add_to_config "config.cache_classes = true" boot! - assert !middleware.include?("ActionDispatch::Reloader") + assert_not_includes middleware, "ActionDispatch::Reloader" end test "use middleware" do diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index 8eb1d42b33..515205296c 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -55,9 +55,9 @@ module ApplicationTests test "booting up Rails yields a list of paths that are eager" do eager_load = @paths.eager_load - assert eager_load.include?(root("app/controllers")) - assert eager_load.include?(root("app/helpers")) - assert eager_load.include?(root("app/models")) + assert_includes eager_load, root("app/controllers") + assert_includes eager_load, root("app/helpers") + assert_includes eager_load, root("app/models") end test "environments has a glob equal to the current environment" do diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb index 28da8164a7..6ebd2d5461 100644 --- a/railties/test/application/rake/restart_test.rb +++ b/railties/test/application/rake/restart_test.rb @@ -13,32 +13,32 @@ module ApplicationTests teardown_app end - test "rake restart touches tmp/restart.txt" do + test "rails restart touches tmp/restart.txt" do Dir.chdir(app_path) do - `rake restart` + `bin/rails restart` assert File.exist?("tmp/restart.txt") prev_mtime = File.mtime("tmp/restart.txt") sleep(1) - `rake restart` + `bin/rails restart` curr_mtime = File.mtime("tmp/restart.txt") assert_not_equal prev_mtime, curr_mtime end end - test "rake restart should work even if tmp folder does not exist" do + test "rails restart should work even if tmp folder does not exist" do Dir.chdir(app_path) do FileUtils.remove_dir("tmp") - `rake restart` + `bin/rails restart` assert File.exist?("tmp/restart.txt") end end - test "rake restart removes server.pid also" do + test "rails restart removes server.pid also" do Dir.chdir(app_path) do FileUtils.mkdir_p("tmp/pids") FileUtils.touch("tmp/pids/server.pid") - `rake restart` + `bin/rails restart` assert_not File.exist?("tmp/pids/server.pid") end end diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb index ef65c7a893..838adbbda9 100644 --- a/railties/test/application/test_test.rb +++ b/railties/test/application/test_test.rb @@ -101,7 +101,7 @@ module ApplicationTests File.delete "#{app_path}/config/initializers/disable_maintain_test_schema.rb" result = assert_successful_test_run("models/user_test.rb") - assert !result.include?("create_table(:users)") + assert_not_includes result, "create_table(:users)" end test "sql structure migrations" do @@ -289,7 +289,7 @@ Expected: ["id", "name"] def assert_unsuccessful_run(name, message) result = run_test_file(name) assert_not_equal 0, $?.to_i - assert result.include?(message) + assert_includes result, message result end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index acab83799d..8f7fa1155f 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -606,7 +606,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_edge_option assert_generates_with_bundler edge: true - assert_file "Gemfile", %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["'],\s+branch:\s+["']#{Regexp.escape("5-0-stable")}["']$} + assert_file "Gemfile", %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$} end def test_spring diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb index c58468dfc1..0258b3b9d7 100644 --- a/railties/test/generators/named_base_test.rb +++ b/railties/test/generators/named_base_test.rb @@ -106,9 +106,9 @@ class NamedBaseTest < Rails::Generators::TestCase def test_hide_namespace g = generator ["Hidden"] g.class.stub(:namespace, "hidden") do - assert !Rails::Generators.hidden_namespaces.include?("hidden") + assert_not_includes Rails::Generators.hidden_namespaces, "hidden" g.class.hide! - assert Rails::Generators.hidden_namespaces.include?("hidden") + assert_includes Rails::Generators.hidden_namespaces, "hidden" end end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 0d8e6f8907..15079f2735 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -205,7 +205,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_edge_option assert_generates_without_bundler(edge: true) - assert_file "Gemfile", %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["'],\s+branch:\s+["']#{Regexp.escape("5-0-stable")}["']$} + assert_file "Gemfile", %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$} end def test_generation_does_not_run_bundle_install_with_full_and_mountable diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 3c22881080..68ba435393 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -229,7 +229,7 @@ class GeneratorsTest < Rails::Generators::TestCase def test_source_paths_for_not_namespaced_generators mspec = Rails::Generators.find_by_namespace :fixjour - assert mspec.source_paths.include?(File.join(Rails.root, "lib", "templates", "fixjour")) + assert_includes mspec.source_paths, File.join(Rails.root, "lib", "templates", "fixjour") end def test_usage_with_embedded_ruby @@ -239,8 +239,8 @@ class GeneratorsTest < Rails::Generators::TestCase end def test_hide_namespace - assert !Rails::Generators.hidden_namespaces.include?("special:namespace") + assert_not_includes Rails::Generators.hidden_namespaces, "special:namespace" Rails::Generators.hide_namespace("special:namespace") - assert Rails::Generators.hidden_namespaces.include?("special:namespace") + assert_includes Rails::Generators.hidden_namespaces, "special:namespace" end end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index b9b11504dc..8c1fe43a10 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -117,11 +117,6 @@ module TestHelpers end end - gemfile_path = "#{app_path}/Gemfile" - if options[:gemfile].blank? && File.exist?(gemfile_path) - File.delete gemfile_path - end - routes = File.read("#{app_path}/config/routes.rb") if routes =~ /(\n\s*end\s*)\Z/ File.open("#{app_path}/config/routes.rb", "w") do |f| diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index a4f6922cb4..7b2551062a 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -103,7 +103,7 @@ class PathsTest < ActiveSupport::TestCase @root.add "app", with: "/app" @root["app"].autoload_once! assert @root["app"].autoload_once? - assert @root.autoload_once.include?(@root["app"].expanded.first) + assert_includes @root.autoload_once, @root["app"].expanded.first end end @@ -114,14 +114,14 @@ class PathsTest < ActiveSupport::TestCase @root["app"].skip_autoload_once! assert !@root["app"].autoload_once? - assert !@root.autoload_once.include?(@root["app"].expanded.first) + assert_not_includes @root.autoload_once, @root["app"].expanded.first end test "it is possible to add a path without assignment and specify it should be loaded only once" do File.stub(:exist?, true) do @root.add "app", with: "/app", autoload_once: true assert @root["app"].autoload_once? - assert @root.autoload_once.include?("/app") + assert_includes @root.autoload_once, "/app" end end @@ -129,8 +129,8 @@ class PathsTest < ActiveSupport::TestCase File.stub(:exist?, true) do @root.add "app", with: ["/app", "/app2"], autoload_once: true assert @root["app"].autoload_once? - assert @root.autoload_once.include?("/app") - assert @root.autoload_once.include?("/app2") + assert_includes @root.autoload_once, "/app" + assert_includes @root.autoload_once, "/app2" end end @@ -157,7 +157,7 @@ class PathsTest < ActiveSupport::TestCase @root["app"] = "/app" @root["app"].eager_load! assert @root["app"].eager_load? - assert @root.eager_load.include?(@root["app"].to_a.first) + assert_includes @root.eager_load, @root["app"].to_a.first end end @@ -168,14 +168,14 @@ class PathsTest < ActiveSupport::TestCase @root["app"].skip_eager_load! assert !@root["app"].eager_load? - assert !@root.eager_load.include?(@root["app"].to_a.first) + assert_not_includes @root.eager_load, @root["app"].to_a.first end test "it is possible to add a path without assignment and mark it as eager" do File.stub(:exist?, true) do @root.add "app", with: "/app", eager_load: true assert @root["app"].eager_load? - assert @root.eager_load.include?("/app") + assert_includes @root.eager_load, "/app" end end @@ -183,8 +183,8 @@ class PathsTest < ActiveSupport::TestCase File.stub(:exist?, true) do @root.add "app", with: ["/app", "/app2"], eager_load: true assert @root["app"].eager_load? - assert @root.eager_load.include?("/app") - assert @root.eager_load.include?("/app2") + assert_includes @root.eager_load, "/app" + assert_includes @root.eager_load, "/app2" end end @@ -193,8 +193,8 @@ class PathsTest < ActiveSupport::TestCase @root.add "app", with: "/app", eager_load: true, autoload_once: true assert @root["app"].eager_load? assert @root["app"].autoload_once? - assert @root.eager_load.include?("/app") - assert @root.autoload_once.include?("/app") + assert_includes @root.eager_load, "/app" + assert_includes @root.autoload_once, "/app" end end diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb index fc4175719e..59e79de41a 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -48,9 +48,9 @@ class InfoTest < ActiveSupport::TestCase end html = Rails::Info.to_html - assert html.include?('<tr><td class="name">Middleware</td>') + assert_includes html, '<tr><td class="name">Middleware</td>' properties.value_for("Middleware").each do |value| - assert html.include?("<li>#{CGI.escapeHTML(value)}</li>") + assert_includes html, "<li>#{CGI.escapeHTML(value)}</li>" end end diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index 755ac23cb1..30cd525266 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -135,7 +135,7 @@ module RailtiesTest require "rdoc/task" Rails.application.load_tasks - assert $ran_block.include?("my_tie") + assert_includes $ran_block, "my_tie" end test "generators block is executed when MyApp.load_generators is called" do |