diff options
37 files changed, 215 insertions, 128 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml index 50e0f7a657..7114a98266 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -25,6 +25,6 @@ checks: plugins: rubocop: enabled: true - channel: rubocop-0-66 + channel: rubocop-0-67 exclude_patterns: [] diff --git a/.rubocop.yml b/.rubocop.yml index 3dbd4a27a6..0cfe5d5d84 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,5 @@ +require: rubocop-performance + AllCops: TargetRubyVersion: 2.5 # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop @@ -29,6 +29,7 @@ gem "uglifier", ">= 1.3.0", require: false gem "json", ">= 2.0.0" gem "rubocop", ">= 0.47", require: false +gem "rubocop-performance", require: false group :doc do gem "sdoc", "~> 1.0" diff --git a/Gemfile.lock b/Gemfile.lock index b92603d799..62ad1134d5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -411,7 +411,7 @@ GEM resque (~> 1.26) rufus-scheduler (~> 3.2) retriable (3.1.2) - rubocop (0.66.0) + rubocop (0.67.2) jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.5, != 2.5.1.1) @@ -419,6 +419,8 @@ GEM rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.6) + rubocop-performance (1.1.0) + rubocop (>= 0.67.0) ruby-progressbar (1.10.0) ruby-vips (2.0.13) ffi (~> 1.9) @@ -581,6 +583,7 @@ DEPENDENCIES resque resque-scheduler rubocop (>= 0.47) + rubocop-performance sass-rails sdoc (~> 1.0) selenium-webdriver (>= 3.5.0, < 3.13.0) diff --git a/actionview/test/template/resolver_shared_tests.rb b/actionview/test/template/resolver_shared_tests.rb index 525b452075..8b47c5bc89 100644 --- a/actionview/test/template/resolver_shared_tests.rb +++ b/actionview/test/template/resolver_shared_tests.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ResolverSharedTests attr_reader :tmpdir @@ -7,7 +9,7 @@ module ResolverSharedTests end end - def with_file(filename, source="File at #{filename}") + def with_file(filename, source = "File at #{filename}") path = File.join(tmpdir, filename) FileUtils.mkdir_p(File.dirname(path)) File.write(path, source) @@ -103,7 +105,7 @@ module ResolverSharedTests c = context.find("hello_world", "test", false, [], {}) # disable_cache should give us a new object - refute_same a, b + assert_not_same a, b # but it should not clear the cache assert_same a, c diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 83bd91fd10..485547f036 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -4,6 +4,17 @@ *Ryuta Kamizono* +* Add `ActiveRecord::Relation#cache_version` to support recyclable cache keys via + the versioned entries in `ActiveSupport::Cache`. This also means that + `ActiveRecord::Relation#cache_key` will now return a stable key that does not + include the max timestamp or count any more. + + NOTE: This feature is turned off by default, and `cache_key` will still return + cache keys with timestamps until you set `ActiveRecord::Base.collection_cache_versioning = true`. + That's the setting for all new apps on Rails 6.0+ + + *Lachlan Sylvester* + * Fix dirty tracking for `touch` to track saved changes. Fixes #33429. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 200184c2f9..bf0bb84c93 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -170,8 +170,11 @@ module ActiveRecord class Version include Comparable - def initialize(version_string) + attr_reader :full_version_string + + def initialize(version_string, full_version_string = nil) @version = version_string.split(".").map(&:to_i) + @full_version_string = full_version_string end def <=>(version_string) 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 8b907759c6..282b2b1838 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -56,7 +56,9 @@ module ActiveRecord end def get_database_version #:nodoc: - Version.new(version_string) + full_version_string = get_full_version + version_string = version_string(full_version_string) + Version.new(version_string, full_version_string) end def mariadb? # :nodoc: @@ -788,8 +790,8 @@ module ActiveRecord MismatchedForeignKey.new(options) end - def version_string - full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1] + def version_string(full_version_string) + full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1] end class MysqlString < Type::String # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 0dc880c731..5b0335c22b 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -126,7 +126,11 @@ module ActiveRecord end def full_version - @full_version ||= @connection.server_info[:version] + schema_cache.database_version.full_version_string + end + + def get_full_version + @connection.server_info[:version] end end end diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 6782833c5a..040ebdb960 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -109,8 +109,8 @@ module ActiveRecord # a role. If you would like to use a different role you can pass a hash to database: # # ActiveRecord::Base.connected_to(database: { readonly_slow: :animals_slow_replica }) do - # Dog.run_a_long_query # runs a long query while connected to the +animals_slow_replica+ - # using the readonly_slow role. + # # runs a long query while connected to the +animals_slow_replica+ using the readonly_slow role. + # Dog.run_a_long_query # end # # When using the database key a new connection will be established every time. diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb index b769541e95..c745bc1330 100644 --- a/activerecord/lib/active_record/integration.rb +++ b/activerecord/lib/active_record/integration.rb @@ -22,6 +22,14 @@ module ActiveRecord # # This is +true+, by default on Rails 5.2 and above. class_attribute :cache_versioning, instance_writer: false, default: false + + ## + # :singleton-method: + # Indicates whether to use a stable #cache_key method that is accompanied + # by a changing version in the #cache_version method on collections. + # + # This is +false+, by default until Rails 6.1. + class_attribute :collection_cache_versioning, instance_writer: false, default: false end # Returns a +String+, which Action Pack uses for constructing a URL to this diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index 6b84431343..6248c2f578 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -110,7 +110,7 @@ module ActiveRecord end def extract_query_source_location(locations) - backtrace_cleaner.clean(locations).first + backtrace_cleaner.clean(locations.lazy).first end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index cd62b0b881..8eb71e6454 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -291,27 +291,23 @@ module ActiveRecord limit_value ? records.many? : size > 1 end - # Returns a cache key that can be used to identify the records fetched by - # this query. The cache key is built with a fingerprint of the sql query, - # the number of records matched by the query and a timestamp of the last - # updated record. When a new record comes to match the query, or any of - # the existing records is updated or deleted, the cache key changes. + # Returns a stable cache key that can be used to identify this query. + # The cache key is built with a fingerprint of the SQL query. # - # Product.where("name like ?", "%Cosmic Encounter%").cache_key - # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000" + # Product.where("name like ?", "%Cosmic Encounter%").cache_key + # # => "products/query-1850ab3d302391b85b8693e941286659" # - # If the collection is loaded, the method will iterate through the records - # to generate the timestamp, otherwise it will trigger one SQL query like: + # If ActiveRecord::Base.collection_cache_versioning is turned off, as it was + # in Rails 6.0 and earlier, the cache key will also include a version. # - # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%') + # ActiveRecord::Base.collection_cache_versioning = false + # Product.where("name like ?", "%Cosmic Encounter%").cache_key + # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000" # # You can also pass a custom timestamp column to fetch the timestamp of the # last updated record. # # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at) - # - # You can customize the strategy to generate the key on a per model basis - # overriding ActiveRecord::Base#collection_cache_key. def cache_key(timestamp_column = :updated_at) @cache_keys ||= {} @cache_keys[timestamp_column] ||= @klass.collection_cache_key(self, timestamp_column) @@ -321,6 +317,31 @@ module ActiveRecord query_signature = ActiveSupport::Digest.hexdigest(to_sql) key = "#{klass.model_name.cache_key}/query-#{query_signature}" + if cache_version(timestamp_column) + key + else + "#{key}-#{compute_cache_version(timestamp_column)}" + end + end + + # Returns a cache version that can be used together with the cache key to form + # a recyclable caching scheme. The cache version is built with the number of records + # matching the query, and the timestamp of the last updated record. When a new record + # comes to match the query, or any of the existing records is updated or deleted, + # the cache version changes. + # + # If the collection is loaded, the method will iterate through the records + # to generate the timestamp, otherwise it will trigger one SQL query like: + # + # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%') + def cache_version(timestamp_column = :updated_at) + if collection_cache_versioning + @cache_versions ||= {} + @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column) + end + end + + def compute_cache_version(timestamp_column) # :nodoc: if loaded? || distinct_value size = records.size if size > 0 @@ -356,9 +377,9 @@ module ActiveRecord end if timestamp - "#{key}-#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}" + "#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}" else - "#{key}-#{size}" + "#{size}" end end diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb index 483383257b..f07f3c42e6 100644 --- a/activerecord/test/cases/collection_cache_key_test.rb +++ b/activerecord/test/cases/collection_cache_key_test.rb @@ -171,5 +171,39 @@ module ActiveRecord assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key) end + + test "cache_key should be stable when using collection_cache_versioning" do + with_collection_cache_versioning do + developers = Developer.where(salary: 100000) + + assert_match(/\Adevelopers\/query-(\h+)\z/, developers.cache_key) + + /\Adevelopers\/query-(\h+)\z/ =~ developers.cache_key + + assert_equal ActiveSupport::Digest.hexdigest(developers.to_sql), $1 + end + end + + test "cache_version for relation" do + with_collection_cache_versioning do + developers = Developer.where(salary: 100000).order(updated_at: :desc) + last_developer_timestamp = developers.first.updated_at + + assert_match(/(\d+)-(\d+)\z/, developers.cache_version) + + /(\d+)-(\d+)\z/ =~ developers.cache_version + + assert_equal developers.count.to_s, $1 + assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $2 + end + end + + def with_collection_cache_versioning(value = true) + @old_collection_cache_versioning = ActiveRecord::Base.collection_cache_versioning + ActiveRecord::Base.collection_cache_versioning = value + yield + ensure + ActiveRecord::Base.collection_cache_versioning = @old_collection_cache_versioning + end end end diff --git a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb index a2d289bf2f..d3184f39f5 100644 --- a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb @@ -209,7 +209,7 @@ module ActiveRecord config = { "default_env" => { "animals" => { adapter: "sqlite3", database: "db/animals.sqlite3" }, - "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" } + "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" } } } @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config @@ -236,7 +236,7 @@ module ActiveRecord config = { "default_env" => { "animals" => { adapter: "sqlite3", database: "db/animals.sqlite3" }, - "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" } + "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" } } } @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb index 89a9c30f9b..28e232b88f 100644 --- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb +++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb @@ -95,6 +95,10 @@ module ActiveRecord assert_no_queries do assert_equal @database_version.to_s, @cache.database_version.to_s + + if current_adapter?(:Mysql2Adapter) + assert_not_nil @cache.database_version.full_version_string + end end end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 57e03b5e12..597e7b8929 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,8 @@ +* Calling test methods with `with_info_handler` method to allow minitest-hooks + plugin to work. + + *Mauri Mustonen* + * The Zeitwerk compatibility interface for `ActiveSupport::Dependencies` no longer implements `autoloaded_constants` or `autoloaded?` (undocumented, anyway). Experience shows introspection does not have many use cases, and diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 62973eca58..02cbfbaee6 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -122,7 +122,11 @@ module ActiveSupport end def noise(backtrace) - backtrace - silence(backtrace) + backtrace.select do |line| + @silencers.any? do |s| + s.call(line) + end + end end end end diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index c506b35b1e..8812b67f63 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -180,13 +180,13 @@ module ActiveSupport def start(name, id, payload) timestack = Thread.current[:_timestack] ||= [] - timestack.push Time.now + timestack.push Concurrent.monotonic_time end def finish(name, id, payload) timestack = Thread.current[:_timestack] started = timestack.pop - @delegate.call(name, started, Time.now, id, payload) + @delegate.call(name, started, Concurrent.monotonic_time, id, payload) end end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index a03e7e483e..12546511a8 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -68,9 +68,8 @@ module ActiveSupport @transaction_id = transaction_id @end = ending @children = [] - @duration = nil - @cpu_time_start = nil - @cpu_time_finish = nil + @cpu_time_start = 0 + @cpu_time_finish = 0 @allocation_count_start = 0 @allocation_count_finish = 0 end @@ -125,7 +124,7 @@ module ActiveSupport # # @event.duration # => 1000.138 def duration - @duration ||= 1000.0 * (self.end - time) + 1000.0 * (self.end - time) end def <<(event) diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb index 63440069b1..08285b2f52 100644 --- a/activesupport/lib/active_support/testing/parallelization.rb +++ b/activesupport/lib/active_support/testing/parallelization.rb @@ -79,7 +79,9 @@ module ActiveSupport klass = job[0] method = job[1] reporter = job[2] - result = Minitest.run_one_method(klass, method) + result = klass.with_info_handler reporter do + Minitest.run_one_method(klass, method) + end begin queue.record(reporter, result) diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index bb20d26a25..0af59764b5 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -302,7 +302,7 @@ module Notifications class EventTest < TestCase def test_events_are_initialized_with_details - time = Time.now + time = Concurrent.monotonic_time event = event(:foo, time, time + 0.01, random_id, {}) assert_equal :foo, event.name @@ -310,15 +310,24 @@ module Notifications assert_in_delta 10.0, event.duration, 0.00001 end + def test_event_cpu_time_and_idle_time_when_start_and_finish_is_not_called + time = Concurrent.monotonic_time + event = event(:foo, time, time + 0.01, random_id, {}) + + assert_equal 0, event.cpu_time + assert_in_delta 10.0, event.idle_time, 0.00001 + end + + def test_events_consumes_information_given_as_payload - event = event(:foo, Time.now, Time.now + 1, random_id, payload: :bar) + event = event(:foo, Concurrent.monotonic_time, Concurrent.monotonic_time + 1, random_id, payload: :bar) assert_equal Hash[payload: :bar], event.payload end def test_event_is_parent_based_on_children - time = Time.utc(2009, 01, 01, 0, 0, 1) + time = Concurrent.monotonic_time - parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, random_id, {}) + parent = event(:foo, Concurrent.monotonic_time, Concurrent.monotonic_time + 100, random_id, {}) child = event(:foo, time, time + 10, random_id, {}) not_child = event(:foo, time, time + 100, random_id, {}) diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 454613e733..d853559440 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -33,13 +33,11 @@ passing the `--skip-sprockets` option. rails new appname --skip-sprockets ``` -Rails automatically adds the `sass-rails`, `coffee-rails` and `uglifier` -gems to your `Gemfile`, which are used by Sprockets for asset compression: +Rails automatically adds the `sass-rails` gem to your `Gemfile`, which is used +by Sprockets for asset compression: ```ruby gem 'sass-rails' -gem 'uglifier' -gem 'coffee-rails' ``` Using the `--skip-sprockets` option will prevent Rails from adding @@ -176,8 +174,7 @@ in `app/assets` are never served directly in production. ### Controller Specific Assets -When you generate a scaffold or a controller, Rails also generates a JavaScript -file (or CoffeeScript file if the `coffee-rails` gem is in the `Gemfile`) and a +When you generate a scaffold or a controller, Rails also generates a Cascading Style Sheet file (or SCSS file if `sass-rails` is in the `Gemfile`) for that controller. Additionally, when generating a scaffold, Rails generates the file `scaffolds.css` (or `scaffolds.scss` if `sass-rails` is in the @@ -434,9 +431,8 @@ one file rather than many, the load time of pages can be greatly reduced because the browser makes fewer requests. Compression also reduces file size, enabling the browser to download them faster. - -For example, a new Rails application includes a default -`app/assets/javascripts/application.js` file containing the following lines: +For example, with a `app/assets/javascripts/application.js` file containing the +following lines: ```js // ... @@ -476,8 +472,7 @@ which contains these lines: */ ``` -Rails creates both `app/assets/javascripts/application.js` and -`app/assets/stylesheets/application.css` regardless of whether the +Rails create `app/assets/stylesheets/application.css` regardless of whether the --skip-sprockets option is used when creating a new Rails application. This is so you can easily add asset pipelining later if you like. @@ -517,8 +512,7 @@ The file extensions used on an asset determine what preprocessing is applied. When a controller or a scaffold is generated with the default Rails gemset, a CoffeeScript file and a SCSS file are generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which -generated an `app/assets/javascripts/projects.coffee` and an -`app/assets/stylesheets/projects.scss` file. +generated an `app/assets/stylesheets/projects.scss` file. In development mode, or if the asset pipeline is disabled, when these files are requested they are processed by the processors provided by the `coffee-script` @@ -1083,7 +1077,7 @@ Possible options for JavaScript compression are `:closure`, `:uglifier` and `:yui`. These require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems, respectively. -The default `Gemfile` includes [uglifier](https://github.com/lautis/uglifier). +Take the `uglifier` gem, for example. This gem wraps [UglifyJS](https://github.com/mishoo/UglifyJS) (written for NodeJS) in Ruby. It compresses your code by removing white space and comments, shortening local variable names, and performing other micro-optimizations such @@ -1230,4 +1224,3 @@ it as a preprocessor for your mime type. ```ruby Sprockets.register_preprocessor 'text/css', AddComment ``` - diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 0091271363..4ad143d105 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -257,7 +257,7 @@ We will set up a simple resource called "HighScore" that will keep track of our ```bash $ rails generate scaffold HighScore game:string score:integer invoke active_record - create db/migrate/20130717151933_create_high_scores.rb + create db/migrate/20190416145729_create_high_scores.rb create app/models/high_score.rb invoke test_unit create test/models/high_score_test.rb @@ -275,20 +275,19 @@ $ rails generate scaffold HighScore game:string score:integer create app/views/high_scores/_form.html.erb invoke test_unit create test/controllers/high_scores_controller_test.rb + create test/system/high_scores_test.rb invoke helper create app/helpers/high_scores_helper.rb + invoke test_unit invoke jbuilder create app/views/high_scores/index.json.jbuilder create app/views/high_scores/show.json.jbuilder - invoke test_unit - create test/system/high_scores_test.rb + create app/views/high_scores/_high_score.json.jbuilder invoke assets - invoke coffee - create app/assets/javascripts/high_scores.coffee invoke scss create app/assets/stylesheets/high_scores.scss invoke scss - identical app/assets/stylesheets/scaffolds.scss + create app/assets/stylesheets/scaffolds.scss ``` The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the **resource**, and new tests for everything. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index c33d523c0e..f86589bdf1 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -247,7 +247,7 @@ Rails follows a simple set of coding style conventions: * Two spaces, no tabs (for indentation). * No trailing whitespace. Blank lines should not have any spaces. -* Indent after private/protected. +* Indent and no blank line after private/protected. * Use Ruby >= 1.9 syntax for hashes. Prefer `{ a: :b }` over `{ :a => :b }`. * Prefer `&&`/`||` over `and`/`or`. * Prefer class << self over self.method for class methods. diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index b79dbdbc6f..d743c1c0d9 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -142,6 +142,10 @@ module Rails active_storage.queues.analysis = :active_storage_analysis active_storage.queues.purge = :active_storage_purge end + + if respond_to?(:active_record) + active_record.collection_cache_versioning = true + end else raise "Unknown version #{target_version.to_s.inspect}" end diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt index 3f73bae3da..5928deb6aa 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt +++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt @@ -8,7 +8,8 @@ def system!(*args) end FileUtils.chdir APP_ROOT do - # This script is a starting point to setup your application. + # This script is a way to setup or update your development environment automatically. + # This script is idempotent, so that you can run it at anytime and get an expectable outcome. # Add necessary setup steps to this file. puts '== Installing dependencies ==' @@ -27,7 +28,7 @@ FileUtils.chdir APP_ROOT do # end puts "\n== Preparing database ==" - system! 'bin/rails db:setup' + system! 'bin/rails db:prepare' <% end -%> puts "\n== Removing old logs and tempfiles ==" diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update.tt b/railties/lib/rails/generators/rails/app/templates/bin/update.tt deleted file mode 100644 index 03b77d0d46..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/bin/update.tt +++ /dev/null @@ -1,33 +0,0 @@ -require 'fileutils' - -# path to your application root. -APP_ROOT = File.expand_path('..', __dir__) - -def system!(*args) - system(*args) || abort("\n== Command #{args} failed ==") -end - -FileUtils.chdir APP_ROOT do - # This script is a way to update your development environment automatically. - # Add necessary update steps to this file. - - puts '== Installing dependencies ==' - system! 'gem install bundler --conservative' - system('bundle check') || system!('bundle install') -<% unless options.skip_javascript? -%> - - # Install JavaScript dependencies - # system('bin/yarn') -<% end -%> -<% unless options.skip_active_record? -%> - - puts "\n== Updating database ==" - system! 'rails db:migrate' -<% end -%> - - puts "\n== Removing old logs and tempfiles ==" - system! 'rails log:clear tmp:clear' - - puts "\n== Restarting application server ==" - system! 'rails restart' -end diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb index 5cffa52860..4a1942790b 100644 --- a/railties/lib/rails/mailers_controller.rb +++ b/railties/lib/rails/mailers_controller.rb @@ -5,8 +5,9 @@ require "rails/application_controller" class Rails::MailersController < Rails::ApplicationController # :nodoc: prepend_view_path ActionDispatch::DebugView::RESCUES_TEMPLATE_PATH + around_action :set_locale, only: :preview + before_action :find_preview, only: :preview before_action :require_local!, unless: :show_previews? - before_action :find_preview, :set_locale, only: :preview helper_method :part_query, :locale_query @@ -92,6 +93,8 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: end def set_locale - I18n.locale = params[:locale] || I18n.default_locale + I18n.with_locale(params[:locale] || I18n.default_locale) do + yield + end end end diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb index a952d2466b..aa0da0931d 100644 --- a/railties/test/application/bin_setup_test.rb +++ b/railties/test/application/bin_setup_test.rb @@ -6,21 +6,12 @@ module ApplicationTests class BinSetupTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation - def setup - build_app - end - - def teardown - teardown_app - end + setup :build_app + teardown :teardown_app def test_bin_setup Dir.chdir(app_path) do - app_file "db/schema.rb", <<-RUBY - ActiveRecord::Schema.define(version: 20140423102712) do - create_table(:articles) {} - end - RUBY + rails "generate", "model", "article" list_tables = lambda { rails("runner", "p ActiveRecord::Base.connection.tables").strip } File.write("log/test.log", "zomg!") @@ -28,15 +19,20 @@ module ApplicationTests assert_equal "[]", list_tables.call assert_equal 5, File.size("log/test.log") assert_not File.exist?("tmp/restart.txt") + `bin/setup 2>&1` assert_equal 0, File.size("log/test.log") - assert_equal '["articles", "schema_migrations", "ar_internal_metadata"]', list_tables.call + assert_equal '["schema_migrations", "ar_internal_metadata", "articles"]', list_tables.call assert File.exist?("tmp/restart.txt") end end def test_bin_setup_output Dir.chdir(app_path) do + # SQLite3 seems to auto-create the database on first checkout. + rails "db:system:change", "--to=postgresql" + rails "db:drop" + app_file "db/schema.rb", "" output = `bin/setup 2>&1` @@ -53,8 +49,8 @@ module ApplicationTests The Gemfile's dependencies are satisfied == Preparing database == - Created database 'db/development.sqlite3' - Created database 'db/test.sqlite3' + Created database 'app_development' + Created database 'app_test' == Removing old logs and tempfiles == diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index a2e3e781c0..62d9b1c813 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -1721,7 +1721,7 @@ module ApplicationTests end EOS - app_file "config/initializers/autoload.rb", "D" + app_file "config/initializers/autoload.rb", "D.class" app "development" @@ -1749,7 +1749,7 @@ module ApplicationTests end EOS - app_file "config/initializers/autoload.rb", "D" + app_file "config/initializers/autoload.rb", "D.class" app "production" diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb index fb84276b8a..fa9ed868c4 100644 --- a/railties/test/application/mailer_previews_test.rb +++ b/railties/test/application/mailer_previews_test.rb @@ -515,6 +515,13 @@ module ApplicationTests assert_match '<option selected value="locale=ja">ja', last_response.body end + test "preview does not leak I18n global setting changes" do + I18n.with_locale(:en) do + get "/rails/mailers/notifier/foo.txt?locale=ja" + assert_equal :en, I18n.locale + end + end + test "mailer previews create correct links when loaded on a subdirectory" do mailer "notifier", <<-RUBY class Notifier < ActionMailer::Base diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb index ec512b6b64..6de23acebe 100644 --- a/railties/test/backtrace_cleaner_test.rb +++ b/railties/test/backtrace_cleaner_test.rb @@ -17,6 +17,16 @@ class BacktraceCleanerTest < ActiveSupport::TestCase assert_equal 1, result.length end + test "can filter for noise" do + backtrace = [ "(irb):1", + "/Path/to/rails/railties/lib/rails/commands/console.rb:77:in `start'", + "bin/rails:4:in `<main>'" ] + result = @cleaner.clean(backtrace, :noise) + assert_equal "/Path/to/rails/railties/lib/rails/commands/console.rb:77:in `start'", result[0] + assert_equal "bin/rails:4:in `<main>'", result[1] + assert_equal 2, result.length + end + test "should omit ActionView template methods names" do method_name = ActionView::Template.new(nil, "app/views/application/index.html.erb", nil, locals: []).send :method_name backtrace = [ "app/views/application/index.html.erb:4:in `block in #{method_name}'"] diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 4b9878187b..503564beec 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -120,7 +120,6 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase bin/rails bin/rake bin/setup - bin/update config/application.rb config/boot.rb config/cable.yml diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 8771b53071..5b439fdcba 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -41,7 +41,6 @@ DEFAULT_APP_FILES = %w( bin/rails bin/rake bin/setup - bin/update bin/yarn config/application.rb config/boot.rb @@ -321,10 +320,6 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "#{app_root}/bin/setup" do |content| assert_no_match(/system\('bin\/yarn'\)/, content) end - - assert_file "#{app_root}/bin/update" do |content| - assert_no_match(/system\('bin\/yarn'\)/, content) - end end end diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index 26ce487c5f..3e8ce1c018 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -191,10 +191,7 @@ module SharedGeneratorTests assert_no_match(/fixtures :all/, helper_content) end assert_file "#{application_path}/bin/setup" do |setup_content| - assert_no_match(/db:setup/, setup_content) - end - assert_file "#{application_path}/bin/update" do |update_content| - assert_no_match(/db:migrate/, update_content) + assert_no_match(/db:prepare/, setup_content) end assert_file ".gitignore" do |content| assert_no_match(/sqlite/i, content) diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 3fcfaa9623..fab704944b 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -18,7 +18,9 @@ require "active_support/testing/method_call_assertions" require "active_support/test_case" require "minitest/retry" -Minitest::Retry.use!(verbose: false, retry_count: 1) +if ENV["BUILDKITE"] + Minitest::Retry.use!(verbose: false, retry_count: 1) +end RAILS_FRAMEWORK_ROOT = File.expand_path("../../..", __dir__) |