diff options
183 files changed, 1062 insertions, 459 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index 2889acfdd1..87b5c30496 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -82,16 +82,8 @@ Style/MethodDefParentheses: Style/FrozenStringLiteralComment: Enabled: true EnforcedStyle: always - Include: - - 'activesupport/**/*' - - 'activemodel/**/*' - - 'actioncable/**/*' - - 'activejob/**/*' - - 'activerecord/**/*' - - 'actionmailer/**/*' - - 'actionview/**/*' - - 'actionpack/**/*' Exclude: + - 'railties/**/*' - 'actionview/test/**/*.builder' - 'actionview/test/**/*.ruby' - 'actionpack/test/**/*.builder' diff --git a/.travis.yml b/.travis.yml index d1b384720a..46b9c45133 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,7 +45,7 @@ env: matrix: - "GEM=railties" - "GEM=ap,ac" - - "GEM=am,amo,as,av,aj" + - "GEM=am,amo,as,av,aj,ast" - "GEM=as PRESERVE_TIMEZONES=1" - "GEM=ar:mysql2" - "GEM=ar:sqlite3" @@ -1,9 +1,8 @@ +# frozen_string_literal: true + 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 +git_source(:github) { |repo| "https://github.com/#{repo}.git" } gemspec @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "net/http" $:.unshift __dir__ diff --git a/actioncable/README.md b/actioncable/README.md index 6946dbefb0..9667403673 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -53,7 +53,7 @@ module ApplicationCable private def find_verified_user - if verified_user = User.find_by(id: cookies.signed[:user_id]) + if verified_user = User.find_by(id: cookies.encrypted[:user_id]) verified_user else reject_unauthorized_connection diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index 8dbafe5105..84053db9fd 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -26,7 +26,7 @@ module ActionCable # # private # def find_verified_user - # User.find_by_identity(cookies.signed[:identity_id]) || + # User.find_by_identity(cookies.encrypted[:identity_id]) || # reject_unauthorized_connection # end # end diff --git a/actioncable/test/connection/multiple_identifiers_test.rb b/actioncable/test/connection/multiple_identifiers_test.rb index 230433a110..7f90cb3876 100644 --- a/actioncable/test/connection/multiple_identifiers_test.rb +++ b/actioncable/test/connection/multiple_identifiers_test.rb @@ -36,8 +36,4 @@ class ActionCable::Connection::MultipleIdentifiersTest < ActionCable::TestCase @connection.process @connection.send :handle_open end - - def close_connection - @connection.send :handle_close - end end diff --git a/actioncable/test/connection/string_identifier_test.rb b/actioncable/test/connection/string_identifier_test.rb index 6387fb792c..4cb58e7fd0 100644 --- a/actioncable/test/connection/string_identifier_test.rb +++ b/actioncable/test/connection/string_identifier_test.rb @@ -38,8 +38,4 @@ class ActionCable::Connection::StringIdentifierTest < ActionCable::TestCase @connection.process @connection.send :on_open end - - def close_connection - @connection.send :on_close - end end diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index 36c2e5866d..69578471b0 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -58,6 +58,12 @@ module ActionMailer end end + initializer "action_mailer.eager_load_actions" do + ActiveSupport.on_load(:after_initialize) do + ActionMailer::Base.descendants.each(&:action_methods) if config.eager_load + end + end + config.after_initialize do |app| options = app.config.action_mailer diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb index 89a3c7475e..03e8d4fb66 100644 --- a/actionmailer/test/message_delivery_test.rb +++ b/actionmailer/test/message_delivery_test.rb @@ -14,7 +14,6 @@ class MessageDeliveryTest < ActiveSupport::TestCase ActionMailer::Base.deliver_later_queue_name = :test_queue ActionMailer::Base.delivery_method = :test ActiveJob::Base.logger = Logger.new(nil) - ActionMailer::Base.deliveries.clear ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true @@ -25,6 +24,8 @@ class MessageDeliveryTest < ActiveSupport::TestCase end teardown do + ActionMailer::Base.deliveries.clear + ActiveJob::Base.logger = @previous_logger ActionMailer::Base.delivery_method = @previous_delivery_method ActionMailer::Base.deliver_later_queue_name = @previous_deliver_later_queue_name @@ -60,8 +61,6 @@ class MessageDeliveryTest < ActiveSupport::TestCase def test_should_enqueue_and_run_correctly_in_activejob @mail.deliver_later! assert_equal 1, ActionMailer::Base.deliveries.size - ensure - ActionMailer::Base.deliveries.clear end test "should enqueue the email with :deliver_now delivery method" do diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 1bc26cd5e1..938b24a6b9 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,11 @@ +* Deprecate `ActionDispatch::TestResponse` response aliases + + `#success?`, `#missing?` & `#error?` are not supported by the actual + `ActionDispatch::Response` object and can produce false-positives. Instead, + use the response helpers provided by `Rack::Response`. + + *Trevor Wistaff* + * Protect from forgery by default Rather than protecting from forgery in the generated `ApplicationController`, diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 7dea22931c..cc02c9a53a 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -79,5 +79,11 @@ module ActionController end end end + + initializer "action_controller.eager_load_actions" do + ActiveSupport.on_load(:after_initialize) do + ActionController::Metal.descendants.each(&:action_methods) if config.eager_load + end + end end end diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index 0b7e9ca945..b23ea7479c 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -20,13 +20,31 @@ module ActionDispatch end # Was the response successful? - alias_method :success?, :successful? + def success? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The success? predicate is deprecated and will be removed in Rails 6.0. + Please use successful? as provided by Rack::Response::Helpers. + MSG + successful? + end # Was the URL not found? - alias_method :missing?, :not_found? + def missing? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The missing? predicate is deprecated and will be removed in Rails 6.0. + Please use not_found? as provided by Rack::Response::Helpers. + MSG + not_found? + end # Was there a server-side error? - alias_method :error?, :server_error? + def error? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The error? predicate is deprecated and will be removed in Rails 6.0. + Please use server_error? as provided by Rack::Response::Helpers. + MSG + server_error? + end def parsed_body @parsed_body ||= @response_parser.call(body) diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 67d6dea86f..a14083c6a2 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -296,13 +296,8 @@ class ResponseTest < ActiveSupport::TestCase end test "read content type with default charset utf-8" do - original = ActionDispatch::Response.default_charset - begin - resp = ActionDispatch::Response.new(200, "Content-Type" => "text/xml") - assert_equal("utf-8", resp.charset) - ensure - ActionDispatch::Response.default_charset = original - end + resp = ActionDispatch::Response.new(200, "Content-Type" => "text/xml") + assert_equal("utf-8", resp.charset) end test "read content type with charset utf-16" do diff --git a/actionpack/test/dispatch/test_response_test.rb b/actionpack/test/dispatch/test_response_test.rb index 2629a61057..f0b8f7785d 100644 --- a/actionpack/test/dispatch/test_response_test.rb +++ b/actionpack/test/dispatch/test_response_test.rb @@ -27,4 +27,11 @@ class TestResponseTest < ActiveSupport::TestCase response = ActionDispatch::TestResponse.create(200, { "Content-Type" => "application/json" }, '{ "foo": "fighters" }') assert_equal({ "foo" => "fighters" }, response.parsed_body) end + + test "response status aliases deprecated" do + response = ActionDispatch::TestResponse.create + assert_deprecated { response.success? } + assert_deprecated { response.missing? } + assert_deprecated { response.error? } + end end diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 8b6322f8ad..e5a2f7e520 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -1569,7 +1569,7 @@ module ActionView # In the above block, a +FormBuilder+ object is yielded as the # +person_form+ variable. This allows you to generate the +text_field+ # and +check_box+ fields by specifying their eponymous methods, which - # modify the underlying template and associates the +@person+ model object + # modify the underlying template and associates the <tt>@person</tt> model object # with the form. # # The +FormBuilder+ object can be thought of as serving as a proxy for the @@ -2165,11 +2165,11 @@ module ActionView # <%= f.submit %> # <% end %> # - # In the example above, if @post is a new record, it will use "Create Post" as - # submit button label, otherwise, it uses "Update Post". + # In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as + # submit button label; otherwise, it uses "Update Post". # - # Those labels can be customized using I18n, under the helpers.submit key and accept - # the %{model} as translation interpolation: + # Those labels can be customized using I18n under the +helpers.submit+ key and using + # <tt>%{model}</tt> for translation interpolation: # # en: # helpers: @@ -2177,7 +2177,7 @@ module ActionView # create: "Create a %{model}" # update: "Confirm changes to %{model}" # - # It also searches for a key specific for the given object: + # It also searches for a key specific to the given object: # # en: # helpers: @@ -2198,11 +2198,11 @@ module ActionView # <%= f.button %> # <% end %> # - # In the example above, if @post is a new record, it will use "Create Post" as - # button label, otherwise, it uses "Update Post". + # In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as + # button label; otherwise, it uses "Update Post". # - # Those labels can be customized using I18n, under the helpers.submit key - # (the same as submit helper) and accept the %{model} as translation interpolation: + # Those labels can be customized using I18n under the +helpers.submit+ key + # (the same as submit helper) and using <tt>%{model}</tt> for translation interpolation: # # en: # helpers: @@ -2210,7 +2210,7 @@ module ActionView # create: "Create a %{model}" # update: "Confirm changes to %{model}" # - # It also searches for a key specific for the given object: + # It also searches for a key specific to the given object: # # en: # helpers: diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index f548fc24ed..f2edcb750c 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -355,7 +355,7 @@ module ActionView # 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 + # If +options[:partial]+ is a string, then the <tt>@path</tt> 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) diff --git a/activejob/test/support/integration/adapters/delayed_job.rb b/activejob/test/support/integration/adapters/delayed_job.rb index ae5de78b60..fc9bae47fa 100644 --- a/activejob/test/support/integration/adapters/delayed_job.rb +++ b/activejob/test/support/integration/adapters/delayed_job.rb @@ -18,5 +18,6 @@ module DelayedJobJobsManager def stop_workers @worker.stop + @thread.join end end diff --git a/activemodel/lib/active_model/type/integer.rb b/activemodel/lib/active_model/type/integer.rb index d1473bd792..fe396998a3 100644 --- a/activemodel/lib/active_model/type/integer.rb +++ b/activemodel/lib/active_model/type/integer.rb @@ -6,7 +6,7 @@ module ActiveModel include Helpers::Numeric # Column storage size in bytes. - # 4 bytes means a MySQL int or Postgres integer as opposed to smallint etc. + # 4 bytes means an integer as opposed to smallint etc. DEFAULT_LIMIT = 4 def initialize(*) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 840f71bef2..a61c0336db 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -349,6 +349,7 @@ module ActiveRecord # build_other(attributes={}) | X | | X # create_other(attributes={}) | X | | X # create_other!(attributes={}) | X | | X + # reload_other | X | X | X # # === Collection associations (one-to-many / many-to-many) # | | | has_many @@ -378,6 +379,7 @@ module ActiveRecord # others.exists? | X | X | X # others.distinct | X | X | X # others.reset | X | X | X + # others.reload | X | X | X # # === Overriding generated methods # @@ -1190,8 +1192,8 @@ module ActiveRecord # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>. # # [collection] - # Returns an array of all the associated objects. - # An empty array is returned if none are found. + # Returns a Relation of all the associated objects. + # An empty Relation is returned if none are found. # [collection<<(object, ...)] # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. # Note that this operation instantly fires update SQL without waiting for the save or update call on the @@ -1248,6 +1250,9 @@ module ActiveRecord # [collection.create!(attributes = {})] # Does the same as <tt>collection.create</tt>, but raises ActiveRecord::RecordInvalid # if the record is invalid. + # [collection.reload] + # Returns a Relation of all of the associated objects, forcing a database read. + # An empty Relation is returned if none are found. # # === Example # @@ -1267,6 +1272,7 @@ module ActiveRecord # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>) # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>) # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>) + # * <tt>Firm#clients.reload</tt> # The declaration can also include an +options+ hash to specialize the behavior of the association. # # === Scopes @@ -1426,6 +1432,8 @@ module ActiveRecord # [create_association!(attributes = {})] # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid # if the record is invalid. + # [reload_association] + # Returns the associated object, forcing a database read. # # === Example # @@ -1435,6 +1443,7 @@ module ActiveRecord # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>) + # * <tt>Account#reload_beneficiary</tt> # # === Scopes # @@ -1555,6 +1564,8 @@ module ActiveRecord # [create_association!(attributes = {})] # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid # if the record is invalid. + # [reload_association] + # Returns the associated object, forcing a database read. # # === Example # @@ -1564,6 +1575,7 @@ module ActiveRecord # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>) # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>) # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>) + # * <tt>Post#reload_author</tt> # The declaration can also include an +options+ hash to specialize the behavior of the association. # # === Scopes @@ -1704,8 +1716,8 @@ module ActiveRecord # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>. # # [collection] - # Returns an array of all the associated objects. - # An empty array is returned if none are found. + # Returns a Relation of all the associated objects. + # An empty Relation is returned if none are found. # [collection<<(object, ...)] # Adds one or more objects to the collection by creating associations in the join table # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method). @@ -1743,6 +1755,9 @@ module ActiveRecord # Returns a new object of the collection type that has been instantiated # with +attributes+, linked to this object through the join table, and that has already been # saved (if it passed the validation). + # [collection.reload] + # Returns a Relation of all of the associated objects, forcing a database read. + # An empty Relation is returned if none are found. # # === Example # @@ -1761,6 +1776,7 @@ module ActiveRecord # * <tt>Developer#projects.exists?(...)</tt> # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>) # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>) + # * <tt>Developer#projects.reload</tt> # The declaration may include an +options+ hash to specialize the behavior of the association. # # === Scopes diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 9b0b50977d..3d79e540b8 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -68,11 +68,11 @@ module ActiveRecord foreign_key = join_keys.foreign_key value = transform_value(owner[foreign_key]) - scope = scope.where(table.name => { key => value }) + scope = apply_scope(scope, table, key, value) if reflection.type polymorphic_type = transform_value(owner.class.base_class.name) - scope = scope.where(table.name => { reflection.type => polymorphic_type }) + scope = apply_scope(scope, table, reflection.type, polymorphic_type) end scope @@ -91,10 +91,10 @@ module ActiveRecord if reflection.type value = transform_value(next_reflection.klass.base_class.name) - scope = scope.where(table.name => { reflection.type => value }) + scope = apply_scope(scope, table, reflection.type, value) end - scope = scope.joins(join(foreign_table, constraint)) + scope.joins!(join(foreign_table, constraint)) end class ReflectionProxy < SimpleDelegator # :nodoc: @@ -165,6 +165,14 @@ module ActiveRecord scope end + def apply_scope(scope, table, key, value) + if scope.table == table + scope.where!(key => value) + else + scope.where!(table.name => { key => value }) + end + end + def eval_scope(reflection, table, scope, owner) reflection.build_scope(table).instance_exec(owner, &scope) end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 0001b804a8..38e7ba39d3 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -59,7 +59,9 @@ module ActiveRecord r.send(reflection.association_primary_key) end.values_at(*ids).compact if records.size != ids.size - klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, reflection.association_primary_key) + found_ids = records.map { |record| record.send(reflection.association_primary_key) } + not_found_ids = ids - found_ids + klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, reflection.association_primary_key, not_found_ids) else replace(records) end @@ -72,23 +74,19 @@ module ActiveRecord end def find(*args) - if block_given? - load_target.find(*args) { |*block_args| yield(*block_args) } - else - if options[:inverse_of] && loaded? - args_flatten = args.flatten - raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank? - result = find_by_scan(*args) - - result_size = Array(result).size - if !result || result_size != args_flatten.size - scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size) - else - result - end + if options[:inverse_of] && loaded? + args_flatten = args.flatten + raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank? + result = find_by_scan(*args) + + result_size = Array(result).size + if !result || result_size != args_flatten.size + scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size) else - scope.find(*args) + result end + else + scope.find(*args) end end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 0678b07699..412e89255d 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -135,8 +135,9 @@ module ActiveRecord # # #<Pet id: 2, name: "Spook", person_id: 1>, # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> # # ] - def find(*args, &block) - @association.find(*args, &block) + def find(*args) + return super if block_given? + @association.find(*args) end ## diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 5efe051125..48d33e6744 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -5,7 +5,7 @@ require_relative "../attribute_mutation_tracker" module ActiveRecord module AttributeMethods - module Dirty # :nodoc: + module Dirty extend ActiveSupport::Concern include ActiveModel::Dirty @@ -47,7 +47,7 @@ module ActiveRecord clear_mutation_trackers end - def changes_applied + def changes_applied # :nodoc: @mutations_before_last_save = mutation_tracker @mutations_from_database = AttributeMutationTracker.new(@attributes) @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new @@ -55,27 +55,27 @@ module ActiveRecord clear_mutation_trackers end - def clear_changes_information + def clear_changes_information # :nodoc: @mutations_before_last_save = nil @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new forget_attribute_assignments clear_mutation_trackers end - def write_attribute_without_type_cast(attr_name, *) + def write_attribute_without_type_cast(attr_name, *) # :nodoc: result = super clear_attribute_change(attr_name) result end - def clear_attribute_changes(attr_names) + def clear_attribute_changes(attr_names) # :nodoc: super attr_names.each do |attr_name| clear_attribute_change(attr_name) end end - def changed_attributes + def changed_attributes # :nodoc: # This should only be set by methods which will call changed_attributes # multiple times when it is known that the computed value cannot change. if defined?(@cached_changed_attributes) @@ -85,17 +85,17 @@ module ActiveRecord end end - def changes + def changes # :nodoc: cache_changed_attributes do super end end - def previous_changes + def previous_changes # :nodoc: mutations_before_last_save.changes end - def attribute_changed_in_place?(attr_name) + def attribute_changed_in_place?(attr_name) # :nodoc: mutation_tracker.changed_in_place?(attr_name) end 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 c11b7b012f..0759f4d2b3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -270,7 +270,7 @@ module ActiveRecord # Connections must be leased while holding the main pool mutex. This is # an internal subclass that also +.leases+ returned connections while # still in queue's critical section (queue synchronizes with the same - # +@lock+ as the main pool) so that a returned connection is already + # <tt>@lock</tt> as the main pool) so that a returned connection is already # leased and there is no need to re-enter synchronized block. class ConnectionLeasingQueue < Queue # :nodoc: include BiasableQueue @@ -326,8 +326,6 @@ module ActiveRecord @spec = spec @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5 - @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f)) - @reaper.run # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 @@ -340,7 +338,7 @@ module ActiveRecord # then that +thread+ does indeed own that +conn+. However, an absence of a such # mapping does not mean that the +thread+ doesn't own the said connection. In # that case +conn.owner+ attr should be consulted. - # Access and modification of +@thread_cached_conns+ does not require + # Access and modification of <tt>@thread_cached_conns</tt> does not require # synchronization. @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size) @@ -357,6 +355,9 @@ module ActiveRecord @available = ConnectionLeasingQueue.new self @lock_thread = false + + @reaper = Reaper.new(self, spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f) + @reaper.run end def lock_thread=(lock_thread) @@ -736,10 +737,10 @@ module ActiveRecord # 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 + # NOTE: we rely on <tt>@available.poll</tt> and +try_to_checkout_new_connection+ to # +conn.lease+ the returned connection (and to do this in a +synchronized+ # section). This is not the cleanest implementation, as ideally we would - # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+ + # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to <tt>@available.poll</tt> # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections # of the said methods and avoid an additional +synchronize+ overhead. if conn = @available.poll || try_to_checkout_new_connection @@ -763,7 +764,7 @@ module ActiveRecord end end - # If the pool is not at a +@size+ limit, establish new connection. Connecting + # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting # to the DB is done outside main synchronized section. #-- # Implementation constraint: a newly established connection returned by this 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 ecf5201d12..41d93c4322 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "concurrent/map" + module ActiveRecord module ConnectionAdapters # :nodoc: module QueryCache 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 e21f93856e..3c9b25e411 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -780,7 +780,7 @@ module ActiveRecord def rename_index(table_name, old_name, new_name) validate_index_length!(table_name, new_name) - # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance) + # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance) old_index_def = indexes(table_name).detect { |i| i.name == old_name } return unless old_index_def add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index fbb4c671a5..a84e62ab11 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -143,7 +143,7 @@ module ActiveRecord self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new end - module ClassMethods + module ClassMethods # :nodoc: def allocate define_attribute_methods super @@ -251,7 +251,7 @@ module ActiveRecord end end - # Overwrite the default class equality method to provide support for association proxies. + # Overwrite the default class equality method to provide support for decorated models. def ===(object) object.is_a?(self) end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 933589d4b1..e790760292 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -169,7 +169,7 @@ module ActiveRecord class NoDatabaseError < StatementInvalid end - # Raised when Postgres returns 'cached plan must not change result type' and + # Raised when PostgreSQL returns 'cached plan must not change result type' and # we cannot retry gracefully (e.g. inside a transaction) class PreparedStatementCacheExpired < StatementInvalid end diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index d6595c9355..784292f3f9 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -39,7 +39,7 @@ module ActiveRecord end end - # Since 5.1 Postgres adapter uses bigserial type for primary + # Since 5.1 PostgreSQL adapter uses bigserial type for primary # keys by default and MySQL uses bigint. This compat layer makes old migrations utilize # serial/int type instead -- the way it used to work before 5.1. unless options.key?(:id) diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 97f1a83033..d2b85e168b 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -305,13 +305,17 @@ module ActiveRecord end def get_join_keys(association_klass) - JoinKeys.new(join_pk(association_klass), join_fk) + JoinKeys.new(join_pk(association_klass), join_foreign_key) end def build_scope(table, predicate_builder = predicate_builder(table)) Relation.create(klass, table, predicate_builder) end + def join_foreign_key + active_record_primary_key + end + protected def actual_source_reflection # FIXME: this is a horrible name self @@ -325,10 +329,6 @@ module ActiveRecord def join_pk(_) foreign_key end - - def join_fk - active_record_primary_key - end end # Base class for AggregateReflection and AssociationReflection. Objects of @@ -754,16 +754,16 @@ module ActiveRecord owner[foreign_key] end + def join_foreign_key + foreign_key + end + private def calculate_constructable(macro, options) !polymorphic? end - def join_fk - foreign_key - end - def join_pk(klass) polymorphic? ? association_primary_key(klass) : association_primary_key end @@ -1073,10 +1073,6 @@ module ActiveRecord @reflection.scope end - def table_name - @reflection.table_name - end - def plural_name @reflection.plural_name end @@ -1112,10 +1108,6 @@ module ActiveRecord @association.klass end - def table_name - klass.table_name - end - def constraints @reflection.constraints end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 626c50470e..5da9573052 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -330,7 +330,7 @@ module ActiveRecord # of results obtained should be provided in the +result_size+ argument and # the expected number of results should be provided in the +expected_size+ # argument. - def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key) # :nodoc: + def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc: conditions = arel.where_sql(@klass) conditions = " [#{conditions}]" if conditions name = @klass.name @@ -344,8 +344,8 @@ module ActiveRecord raise RecordNotFound.new(error, name, key, ids) else error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup - error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})" - + error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})." + error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids raise RecordNotFound.new(error, name, primary_key, ids) end end diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb index e64d9fdf2a..0255a65bfe 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb @@ -9,7 +9,7 @@ module ActiveRecord end def queries - [associated_table.association_foreign_key.to_s => ids] + [associated_table.association_join_foreign_key.to_s => ids] end # TODO Change this to private once we've dropped Ruby 2.2 support. @@ -30,7 +30,7 @@ module ActiveRecord end def primary_key - associated_table.association_primary_key + associated_table.association_join_keys.key end def convert_to_id(value) diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index 71351449e1..a5187efc84 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -2,7 +2,7 @@ module ActiveRecord class TableMetadata # :nodoc: - delegate :foreign_type, :foreign_key, to: :association, prefix: true + delegate :foreign_type, :foreign_key, :join_keys, :join_foreign_key, to: :association, prefix: true delegate :association_primary_key, to: :association def initialize(klass, arel_table, association = nil) diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index a2e74efc2b..5681ccdd23 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -22,7 +22,7 @@ module ActiveRecord configuration.merge("encoding" => encoding) establish_connection configuration rescue ActiveRecord::StatementInvalid => error - if /database .* already exists/.match?(error.message) + if error.cause.is_a?(PG::DuplicateDatabase) raise DatabaseAlreadyExists else raise @@ -136,7 +136,7 @@ module ActiveRecord ensure tempfile.close end - FileUtils.mv(tempfile.path, filename) + FileUtils.cp(tempfile.path, filename) end end end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index f91f0cdf12..b2f5e39e09 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -472,7 +472,7 @@ module ActiveRecord # 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 + # The <tt>@transaction_state</tt> 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 diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 79dcfe110c..ee08841eb3 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -21,8 +21,8 @@ module PostgresqlJSONSharedTestCases @connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] } klass.reset_column_information - assert_equal({ "users" => "read", "posts" => ["read", "write"] }, JsonDataType.column_defaults["permissions"]) - assert_equal({ "users" => "read", "posts" => ["read", "write"] }, JsonDataType.new.permissions) + assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.column_defaults["permissions"]) + assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.new.permissions) end def test_deserialize_with_array @@ -33,15 +33,6 @@ module PostgresqlJSONSharedTestCases x.reload assert_equal ["foo" => "bar"], x.objects end - - def test_not_compatible_with_serialize_macro - new_klass = Class.new(klass) do - serialize :payload, JSON - end - assert_raises(ActiveRecord::AttributeMethods::Serialization::ColumnNotSerializableError) do - new_klass.new - end - end end class PostgresqlJSONTest < ActiveRecord::PostgreSQLTestCase 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 5797ffef38..4c2723addc 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -817,7 +817,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase sarah = Person.create!(first_name: "Sarah", primary_contact_id: people(:susan).id, gender: "F", number1_fan_id: 1) john = Person.create!(first_name: "John", primary_contact_id: sarah.id, gender: "M", number1_fan_id: 1) assert_equal sarah.agents, [john] - assert_equal people(:susan).agents.flat_map(&:agents), people(:susan).agents_of_agents + assert_equal people(:susan).agents.flat_map(&:agents).sort, people(:susan).agents_of_agents.sort end def test_associate_existing_with_nonstandard_primary_key_on_belongs_to @@ -891,7 +891,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase company = companies(:rails_core) ids = [Developer.first.id, -9999] e = assert_raises(ActiveRecord::RecordNotFound) { company.developer_ids = ids } - assert_match(/Couldn't find all Developers with 'id'/, e.message) + msg = "Couldn't find all Developers with 'id': (1, -9999) (found 1 results, but was looking for 2). Couldn't find Developer with id -9999." + assert_equal(msg, e.message) end def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set_with_changed_primary_key @@ -905,7 +906,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase author = authors(:david) ids = [categories(:general).name, "Unknown"] e = assert_raises(ActiveRecord::RecordNotFound) { author.essay_category_ids = ids } - assert_equal "Couldn't find all Categories with 'name': (General, Unknown) (found 1 results, but was looking for 2)", e.message + msg = "Couldn't find all Categories with 'name': (General, Unknown) (found 1 results, but was looking for 2). Couldn't find Category with name Unknown." + assert_equal msg, e.message end def test_build_a_model_from_hm_through_association_with_where_clause diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 2eb6cef1d9..fdb98d84e0 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -15,7 +15,7 @@ require "models/post" class HasOneAssociationsTest < ActiveRecord::TestCase self.use_transactional_tests = false unless supports_savepoints? - fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates + fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates, :authors def setup Account.destroyed_account_ids.clear diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 2f68bc5141..9d3d5353ff 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -404,7 +404,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_polymorphic_has_one - assert_equal Tagging.find(1, 2).sort_by(&:id), authors(:david).taggings_2 + assert_equal Tagging.find(1, 2).sort_by(&:id), authors(:david).taggings_2.sort_by(&:id) end def test_has_many_through_polymorphic_has_many diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 1d33564989..1a1d4ce039 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -885,10 +885,17 @@ class BasicsTest < ActiveRecord::TestCase def test_bignum company = Company.find(1) - company.rating = 2147483647 + company.rating = 2147483648 company.save company = Company.find(1) - assert_equal 2147483647, company.rating + assert_equal 2147483648, company.rating + end + + unless current_adapter?(:SQLite3Adapter) + def test_bignum_pk + company = Company.create!(id: 2147483648, name: "foo") + assert_equal company, Company.find(company.id) + end end # TODO: extend defaults tests to other databases! diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index fb3d691d51..a602f83d8c 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -466,11 +466,10 @@ class DirtyTest < ActiveRecord::TestCase def test_save_should_not_save_serialized_attribute_with_partial_writes_if_not_present with_partial_writes(Topic) do - Topic.create!(author_name: "Bill", content: { a: "a" }) - topic = Topic.select("id, author_name").first + topic = Topic.create!(author_name: "Bill", content: { a: "a" }) + topic = Topic.select("id, author_name").find(topic.id) topic.update_columns author_name: "John" - topic = Topic.first - assert_not_nil topic.content + assert_not_nil topic.reload.content end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index a0d87dfb90..55496147c1 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1153,7 +1153,7 @@ class FinderTest < ActiveRecord::TestCase e = assert_raises(ActiveRecord::RecordNotFound) do model.find "Hello", "World!" end - assert_equal "Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2)", e.message + assert_equal "Couldn't find all MercedesCars with 'name': (Hello, World!) (found 0 results, but was looking for 2).", e.message end end diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb index f708acf0aa..952194c6dc 100644 --- a/activerecord/test/cases/json_shared_test_cases.rb +++ b/activerecord/test/cases/json_shared_test_cases.rb @@ -216,6 +216,15 @@ module JSONSharedTestCases assert_equal true, json.payload end + def test_not_compatible_with_serialize_macro + new_klass = Class.new(klass) do + serialize :payload, JSON + end + assert_raises(ActiveRecord::AttributeMethods::Serialization::ColumnNotSerializableError) do + new_klass.new + end + end + private def klass JsonDataType diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index c45fd38bc9..2fe2a67b1c 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -295,6 +295,20 @@ module ActiveRecord assert_equal essays(:david_modest_proposal), essay end + def test_where_with_relation_on_has_many_association + essay = essays(:david_modest_proposal) + author = Author.where(essays: Essay.where(id: essay.id)).first + + assert_equal authors(:david), author + end + + def test_where_with_relation_on_has_one_association + author = authors(:david) + author_address = AuthorAddress.where(author: Author.where(id: author.id)).first + assert_equal author_addresses(:david_address), author_address + end + + def test_where_on_association_with_select_relation essay = Essay.where(author: Author.where(name: "David").select(:name)).take assert_equal essays(:david_modest_proposal), essay diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 1db7432e34..b0431a4e34 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -117,7 +117,8 @@ class NamedScopingTest < ActiveRecord::TestCase assert_not_equal Post.containing_the_letter_a, authors(:david).posts assert !Post.containing_the_letter_a.empty? - assert_equal authors(:david).posts & Post.containing_the_letter_a, authors(:david).posts.containing_the_letter_a + expected = authors(:david).posts & Post.containing_the_letter_a + assert_equal expected.sort_by(&:id), authors(:david).posts.containing_the_letter_a.sort_by(&:id) end def test_scope_with_STI @@ -129,7 +130,8 @@ class NamedScopingTest < ActiveRecord::TestCase assert_not_equal Comment.containing_the_letter_e, authors(:david).comments assert !Comment.containing_the_letter_e.empty? - assert_equal authors(:david).comments & Comment.containing_the_letter_e, authors(:david).comments.containing_the_letter_e + expected = authors(:david).comments & Comment.containing_the_letter_e + assert_equal expected.sort_by(&:id), authors(:david).comments.containing_the_letter_e.sort_by(&:id) end def test_scopes_honor_current_scopes_from_when_defined diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 05420a4240..8f872c38ba 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -9,7 +9,7 @@ ActiveRecord::Schema.define do # ------------------------------------------------------------------- # create_table :accounts, force: true do |t| - t.integer :firm_id + t.references :firm, index: false t.string :firm_name t.integer :credit_limit end @@ -192,15 +192,16 @@ ActiveRecord::Schema.define do t.string :resource_type t.integer :developer_id t.datetime :deleted_at + t.integer :comments end create_table :companies, force: true do |t| t.string :type - t.integer :firm_id + t.references :firm, index: false t.string :firm_name t.string :name - t.integer :client_of - t.integer :rating, default: 1 + t.bigint :client_of + t.bigint :rating, default: 1 t.integer :account_id t.string :description, default: "" t.index [:firm_id, :type, :rating], name: "company_index", length: { type: 10 }, order: { rating: :desc } @@ -235,8 +236,8 @@ ActiveRecord::Schema.define do end create_table :contracts, force: true do |t| - t.integer :developer_id - t.integer :company_id + t.references :developer, index: false + t.references :company, index: false end create_table :customers, force: true do |t| @@ -262,7 +263,7 @@ ActiveRecord::Schema.define do t.string :name t.string :first_name t.integer :salary, default: 70000 - t.integer :firm_id + t.references :firm, index: false t.integer :mentor_id if subsecond_precision_supported? t.datetime :created_at, precision: 6 @@ -719,7 +720,7 @@ ActiveRecord::Schema.define do create_table :projects, force: true do |t| t.string :name t.string :type - t.integer :firm_id + t.references :firm, index: false t.integer :mentor_id end @@ -808,8 +809,7 @@ ActiveRecord::Schema.define do create_table :sponsors, force: true do |t| t.integer :club_id - t.integer :sponsorable_id - t.string :sponsorable_type + t.references :sponsorable, polymorphic: true, index: false end create_table :string_key_objects, id: false, force: true do |t| diff --git a/activestorage/README.md b/activestorage/README.md index 957adc05c3..d4ffe0c484 100644 --- a/activestorage/README.md +++ b/activestorage/README.md @@ -83,14 +83,6 @@ Variation of image attachment: <%= image_tag user.avatar.variant(resize: "100x100") %> ``` -## Installation - -1. Run `rails activestorage:install` to create needed directories, migrations, and configuration. -2. Optional: Add `gem "aws-sdk", "~> 2"` to your Gemfile if you want to use AWS S3. -3. Optional: Add `gem "google-cloud-storage", "~> 1.3"` to your Gemfile if you want to use Google Cloud Storage. -4. Optional: Add `gem "azure-storage"` to your Gemfile if you want to use Microsoft Azure. -5. Optional: Add `gem "mini_magick"` to your Gemfile if you want to use variants. - ## Direct uploads Active Storage, with its included JavaScript library, supports uploading directly from the client to the cloud. diff --git a/activestorage/Rakefile b/activestorage/Rakefile index a41e07f373..aa71a65f6e 100644 --- a/activestorage/Rakefile +++ b/activestorage/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "bundler/setup" require "bundler/gem_tasks" require "rake/testtask" diff --git a/activestorage/app/controllers/active_storage/blobs_controller.rb b/activestorage/app/controllers/active_storage/blobs_controller.rb index 05af29f8b2..00aa8567c8 100644 --- a/activestorage/app/controllers/active_storage/blobs_controller.rb +++ b/activestorage/app/controllers/active_storage/blobs_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Take a signed permanent reference for a blob and turn it into an expiring service URL for download. # Note: These URLs are publicly accessible. If you need to enforce access protection beyond the # security-through-obscurity factor of the signed blob references, you'll need to implement your own @@ -5,6 +7,7 @@ class ActiveStorage::BlobsController < ActionController::Base def show if blob = find_signed_blob + expires_in 5.minutes # service_url defaults to 5 minutes redirect_to blob.service_url(disposition: disposition_param) else head :not_found diff --git a/activestorage/app/controllers/active_storage/direct_uploads_controller.rb b/activestorage/app/controllers/active_storage/direct_uploads_controller.rb index 0d93985897..205d173648 100644 --- a/activestorage/app/controllers/active_storage/direct_uploads_controller.rb +++ b/activestorage/app/controllers/active_storage/direct_uploads_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Creates a new blob on the server side in anticipation of a direct-to-service upload from the client side. # When the client-side upload is completed, the signed_blob_id can be submitted as part of the form to reference # the blob that was created up front. diff --git a/activestorage/app/controllers/active_storage/disk_controller.rb b/activestorage/app/controllers/active_storage/disk_controller.rb index 76377a0f20..b10d4e2cac 100644 --- a/activestorage/app/controllers/active_storage/disk_controller.rb +++ b/activestorage/app/controllers/active_storage/disk_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Serves files stored with the disk service in the same way that the cloud services do. # This means using expiring, signed URLs that are meant for immediate access, not permanent linking. # Always go through the BlobsController, or your own authenticated controller, rather than directly diff --git a/activestorage/app/controllers/active_storage/variants_controller.rb b/activestorage/app/controllers/active_storage/variants_controller.rb index 994c57aafd..02e3010626 100644 --- a/activestorage/app/controllers/active_storage/variants_controller.rb +++ b/activestorage/app/controllers/active_storage/variants_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Take a signed permanent reference for a variant and turn it into an expiring service URL for download. # Note: These URLs are publicly accessible. If you need to enforce access protection beyond the # security-through-obscurity factor of the signed blob and variation reference, you'll need to implement your own @@ -5,6 +7,7 @@ class ActiveStorage::VariantsController < ActionController::Base def show if blob = find_signed_blob + expires_in 5.minutes # service_url defaults to 5 minutes redirect_to ActiveStorage::Variant.new(blob, decoded_variation).processed.service_url(disposition: disposition_param) else head :not_found diff --git a/activestorage/app/jobs/active_storage/purge_job.rb b/activestorage/app/jobs/active_storage/purge_job.rb index 815f908e6c..404ccefd05 100644 --- a/activestorage/app/jobs/active_storage/purge_job.rb +++ b/activestorage/app/jobs/active_storage/purge_job.rb @@ -1,4 +1,6 @@ -# Provides delayed purging of attachments or blobs using their `#purge_later` method. +# frozen_string_literal: true + +# Provides delayed purging of attachments or blobs using their +#purge_later+ method. class ActiveStorage::PurgeJob < ActiveJob::Base # FIXME: Limit this to a custom ActiveStorage error retry_on StandardError diff --git a/activestorage/app/models/active_storage/attachment.rb b/activestorage/app/models/active_storage/attachment.rb index e94fc69bba..adfa6d4aa5 100644 --- a/activestorage/app/models/active_storage/attachment.rb +++ b/activestorage/app/models/active_storage/attachment.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/module/delegation" # Attachments associate records with blobs. Usually that's a one record-many blobs relationship, @@ -21,7 +23,7 @@ class ActiveStorage::Attachment < ActiveRecord::Base # Purging an attachment means purging the blob, which means talking to the service, which means # talking over the internet. Whenever you're doing that, it's a good idea to put that work in a job, - # so it doesn't hold up other operations. That's what #purge_later provides. + # so it doesn't hold up other operations. That's what +#purge_later+ provides. def purge_later ActiveStorage::PurgeJob.perform_later(self) end diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb index c72073f9f6..dc91a9265f 100644 --- a/activestorage/app/models/active_storage/blob.rb +++ b/activestorage/app/models/active_storage/blob.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # A blob is a record that contains the metadata about a file and a key for where that file resides on the service. # Blobs can be created in two ways: # @@ -29,7 +31,7 @@ class ActiveStorage::Blob < ActiveRecord::Base find ActiveStorage.verifier.verify(id, purpose: :blob_id) end - # Returns a new, unsaved blob instance after the `io` has been uploaded to the service. + # Returns a new, unsaved blob instance after the +io+ has been uploaded to the service. def build_after_upload(io:, filename:, content_type: nil, metadata: nil) new.tap do |blob| blob.filename = filename @@ -40,8 +42,8 @@ class ActiveStorage::Blob < ActiveRecord::Base end end - # Returns a saved blob instance after the `io` has been uploaded to the service. Note, the blob is first built, - # then the `io` is uploaded, then the blob is saved. This is doing to avoid opening a transaction and talking to + # Returns a saved blob instance after the +io+ has been uploaded to the service. Note, the blob is first built, + # then the +io+ is uploaded, then the blob is saved. This is doing to avoid opening a transaction and talking to # the service during that (which is a bad idea and leads to deadlocks). def create_after_upload!(io:, filename:, content_type: nil, metadata: nil) build_after_upload(io: io, filename: filename, content_type: content_type, metadata: metadata).tap(&:save!) @@ -72,7 +74,7 @@ class ActiveStorage::Blob < ActiveRecord::Base self[:key] ||= self.class.generate_unique_secure_token end - # Returns a `ActiveStorage::Filename` instance of the filename that can be queried for basename, extension, and + # Returns a ActiveStorage::Filename instance of the filename that can be queried for basename, extension, and # a sanitized version of the filename that's safe to use in URLs. def filename ActiveStorage::Filename.new(self[:filename]) @@ -98,7 +100,7 @@ class ActiveStorage::Blob < ActiveRecord::Base content_type.start_with?("text") end - # Returns a `ActiveStorage::Variant` instance with the set of `transformations` passed in. This is only relevant + # Returns a ActiveStorage::Variant instance with the set of +transformations+ passed in. This is only relevant # for image files, and it allows any image to be transformed for size, colors, and the like. Example: # # avatar.variant(resize: "100x100").processed.service_url @@ -111,7 +113,7 @@ class ActiveStorage::Blob < ActiveRecord::Base # # <%= image_tag url_for(Current.user.avatar.variant(resize: "100x100")) %> # - # This will create a URL for that specific blob with that specific variant, which the `ActiveStorage::VariantsController` + # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::VariantsController # can then produce on-demand. def variant(transformations) ActiveStorage::Variant.new(self, ActiveStorage::Variation.new(transformations)) @@ -119,9 +121,9 @@ class ActiveStorage::Blob < ActiveRecord::Base # Returns the URL of the blob on the service. This URL is intended to be short-lived for security and not used directly - # with users. Instead, the `service_url` should only be exposed as a redirect from a stable, possibly authenticated URL. - # Hiding the `service_url` behind a redirect also gives you the power to change services without updating all URLs. And - # it allows permanent URLs that redirect to the `service_url` to be cached in the view. + # with users. Instead, the +service_url+ should only be exposed as a redirect from a stable, possibly authenticated URL. + # Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And + # it allows permanent URLs that redirect to the +service_url+ to be cached in the view. def service_url(expires_in: 5.minutes, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type end @@ -132,21 +134,21 @@ class ActiveStorage::Blob < ActiveRecord::Base service.url_for_direct_upload key, expires_in: expires_in, content_type: content_type, content_length: byte_size, checksum: checksum end - # Returns a Hash of headers for `service_url_for_direct_upload` requests. + # Returns a Hash of headers for +service_url_for_direct_upload+ requests. def service_headers_for_direct_upload service.headers_for_direct_upload key, filename: filename, content_type: content_type, content_length: byte_size, checksum: checksum end - # Uploads the `io` to the service on the `key` for this blob. Blobs are intended to be immutable, so you shouldn't be + # Uploads the +io+ to the service on the +key+ for this blob. Blobs are intended to be immutable, so you shouldn't be # using this method after a file has already been uploaded to fit with a blob. If you want to create a derivative blob, # you should instead simply create a new blob based on the old one. # # Prior to uploading, we compute the checksum, which is sent to the service for transit integrity validation. If the - # checksum does not match what the service receives, an exception will be raised. We also measure the size of the `io` - # and store that in `byte_size` on the blob record. + # checksum does not match what the service receives, an exception will be raised. We also measure the size of the +io+ + # and store that in +byte_size+ on the blob record. # - # Normally, you do not have to call this method directly at all. Use the factory class methods of `build_after_upload` - # and `create_after_upload!`. + # Normally, you do not have to call this method directly at all. Use the factory class methods of +build_after_upload+ + # and +create_after_upload!+. def upload(io) self.checksum = compute_checksum_in_chunks(io) self.byte_size = io.size @@ -162,7 +164,7 @@ class ActiveStorage::Blob < ActiveRecord::Base # Deletes the file on the service that's associated with this blob. This should only be done if the blob is going to be - # deleted as well or you will essentially have a dead reference. It's recommended to use the `#purge` and `#purge_later` + # deleted as well or you will essentially have a dead reference. It's recommended to use the +#purge+ and +#purge_later+ # methods in most circumstances. def delete service.delete key @@ -170,13 +172,13 @@ class ActiveStorage::Blob < ActiveRecord::Base # Deletes the file on the service and then destroys the blob record. This is the recommended way to dispose of unwanted # blobs. Note, though, that deleting the file off the service will initiate a HTTP connection to the service, which may - # be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use `#purge_later` instead. + # be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use +#purge_later+ instead. def purge delete destroy end - # Enqueues a `ActiveStorage::PurgeJob` job that'll call `#purge`. This is the recommended way to purge blobs when the call + # Enqueues a ActiveStorage::PurgeJob job that'll call +#purge+. This is the recommended way to purge blobs when the call # needs to be made from a transaction, a callback, or any other real-time scenario. def purge_later ActiveStorage::PurgeJob.perform_later(self) diff --git a/activestorage/app/models/active_storage/filename.rb b/activestorage/app/models/active_storage/filename.rb index 35f4a8ac59..6a9889addf 100644 --- a/activestorage/app/models/active_storage/filename.rb +++ b/activestorage/app/models/active_storage/filename.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Encapsulates a string representing a filename to provide convenience access to parts of it and a sanitized version. # This is what's returned by `ActiveStorage::Blob#filename`. A Filename instance is comparable so it can be used for sorting. class ActiveStorage::Filename diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb index e3f22bcb25..7b4ca18c8c 100644 --- a/activestorage/app/models/active_storage/variant.rb +++ b/activestorage/app/models/active_storage/variant.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Image blobs can have variants that are the result of a set of transformations applied to the original. # These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the # original. @@ -9,17 +11,17 @@ # into memory. The larger the image, the more memory is used. Because of this process, you also want to be # considerate about when the variant is actually processed. You shouldn't be processing variants inline in a # template, for example. Delay the processing to an on-demand controller, like the one provided in -# `ActiveStorage::VariantsController`. +# ActiveStorage::VariantsController. # # To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided # by Active Storage like so: # # <%= image_tag url_for(Current.user.avatar.variant(resize: "100x100")) %> # -# This will create a URL for that specific blob with that specific variant, which the `ActiveStorage::VariantsController` +# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::VariantsController # can then produce on-demand. # -# When you do want to actually produce the variant needed, call `#processed`. This will check that the variant +# When you do want to actually produce the variant needed, call +#processed+. This will check that the variant # has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform # the transformations, upload the variant to the service, and return itself again. Example: # @@ -52,12 +54,12 @@ class ActiveStorage::Variant end # Returns the URL of the variant on the service. This URL is intended to be short-lived for security and not used directly - # with users. Instead, the `service_url` should only be exposed as a redirect from a stable, possibly authenticated URL. - # Hiding the `service_url` behind a redirect also gives you the power to change services without updating all URLs. And - # it allows permanent URLs that redirec to the `service_url` to be cached in the view. + # with users. Instead, the +service_url+ should only be exposed as a redirect from a stable, possibly authenticated URL. + # Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And + # it allows permanent URLs that redirect to the +service_url+ to be cached in the view. # # Use `url_for(variant)` (or the implied form, like `link_to variant` or `redirect_to variant`) to get the stable URL - # for a variant that points to the `ActiveStorage::VariantsController`, which in turn will use this `#service_call` method + # for a variant that points to the ActiveStorage::VariantsController, which in turn will use this +#service_call+ method # for its redirection. def service_url(expires_in: 5.minutes, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename, content_type: blob.content_type diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb index e784506b4c..396ce85ef1 100644 --- a/activestorage/app/models/active_storage/variation.rb +++ b/activestorage/app/models/active_storage/variation.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/object/inclusion" # A set of transformations that can be applied to a blob to create a variant. This class is exposed via @@ -13,12 +15,12 @@ class ActiveStorage::Variation attr_reader :transformations class << self - # Returns a variation instance with the transformations that were encoded by `#encode`. + # Returns a variation instance with the transformations that were encoded by +#encode+. def decode(key) new ActiveStorage.verifier.verify(key, purpose: :variation) end - # Returns a signed key for the `transformations`, which can be used to refer to a specific + # Returns a signed key for the +transformations+, which can be used to refer to a specific # variation in a URL or combined key (like `ActiveStorage::Variant#key`). def encode(transformations) ActiveStorage.verifier.generate(transformations, purpose: :variation) @@ -30,7 +32,7 @@ class ActiveStorage::Variation end # Accepts an open MiniMagick image instance, like what's return by `MiniMagick::Image.read(io)`, - # and performs the `transformations` against it. The transformed image instance is then returned. + # and performs the +transformations+ against it. The transformed image instance is then returned. def transform(image) transformations.each do |(method, argument)| if eligible_argument?(argument) @@ -41,7 +43,7 @@ class ActiveStorage::Variation end end - # Returns a signed key for all the `transformations` that this variation was instantiated with. + # Returns a signed key for all the +transformations+ that this variation was instantiated with. def key self.class.encode(transformations) end diff --git a/activestorage/config/routes.rb b/activestorage/config/routes.rb index fddbb93255..168788475c 100644 --- a/activestorage/config/routes.rb +++ b/activestorage/config/routes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Rails.application.routes.draw do get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob diff --git a/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb b/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb new file mode 100644 index 0000000000..d333b6bf9c --- /dev/null +++ b/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class CreateActiveStorageTables < ActiveRecord::Migration[5.1] + def change + create_table :active_storage_blobs do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.integer :byte_size, null: false + t.string :checksum, null: false + t.datetime :created_at, null: false + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false + t.references :blob, null: false + + t.datetime :created_at, null: false + + t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true + end + end +end diff --git a/activestorage/lib/active_storage.rb b/activestorage/lib/active_storage.rb index 412f08e8f5..ccc1d4a163 100644 --- a/activestorage/lib/active_storage.rb +++ b/activestorage/lib/active_storage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- # Copyright (c) 2017 David Heinemeier Hansson # diff --git a/activestorage/lib/active_storage/attached.rb b/activestorage/lib/active_storage/attached.rb index 5ac8ba5377..e90b75afd0 100644 --- a/activestorage/lib/active_storage/attached.rb +++ b/activestorage/lib/active_storage/attached.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require "action_dispatch" require "action_dispatch/http/upload" require "active_support/core_ext/module/delegation" module ActiveStorage -# Abstract baseclass for the concrete `ActiveStorage::Attached::One` and `ActiveStorage::Attached::Many` +# Abstract baseclass for the concrete ActiveStorage::Attached::One and ActiveStorage::Attached::Many # classes that both provide proxy access to the blob association for a record. class Attached attr_reader :name, :record @@ -17,7 +19,7 @@ module ActiveStorage case attachable when ActiveStorage::Blob attachable - when ActionDispatch::Http::UploadedFile + when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile ActiveStorage::Blob.create_after_upload! \ io: attachable.open, filename: attachable.original_filename, diff --git a/activestorage/lib/active_storage/attached/macros.rb b/activestorage/lib/active_storage/attached/macros.rb index 5779348148..027112195f 100644 --- a/activestorage/lib/active_storage/attached/macros.rb +++ b/activestorage/lib/active_storage/attached/macros.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveStorage # Provides the class-level DSL for declaring that an Active Record model has attached blobs. module Attached::Macros @@ -10,25 +12,23 @@ module ActiveStorage # There is no column defined on the model side, Active Storage takes # care of the mapping between your records and the attachment. # - # Under the covers, this relationship is implemented as a `has_one` association to a - # `ActiveStorage::Attachment` record and a `has_one-through` association to a - # `ActiveStorage::Blob` record. These associations are available as `avatar_attachment` - # and `avatar_blob`. But you shouldn't need to work with these associations directly in + # Under the covers, this relationship is implemented as a +has_one+ association to a + # ActiveStorage::Attachment record and a +has_one-through+ association to a + # ActiveStorage::Blob record. These associations are available as +avatar_attachment+ + # and +avatar_blob+. But you shouldn't need to work with these associations directly in # most circumstances. # - # The system has been designed to having you go through the `ActiveStorage::Attached::One` - # proxy that provides the dynamic proxy to the associations and factory methods, like `#attach`. + # The system has been designed to having you go through the ActiveStorage::Attached::One + # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+. # # If the +:dependent+ option isn't set, the attachment will be purged # (i.e. destroyed) whenever the record is destroyed. def has_one_attached(name, dependent: :purge_later) - define_method(name) do - if instance_variable_defined?("@active_storage_attached_#{name}") - instance_variable_get("@active_storage_attached_#{name}") - else - instance_variable_set("@active_storage_attached_#{name}", ActiveStorage::Attached::One.new(name, self)) + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name} + @active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self) end - end + CODE has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob @@ -51,25 +51,23 @@ module ActiveStorage # # Gallery.where(user: Current.user).with_attached_photos # - # Under the covers, this relationship is implemented as a `has_many` association to a - # `ActiveStorage::Attachment` record and a `has_many-through` association to a - # `ActiveStorage::Blob` record. These associations are available as `photos_attachments` - # and `photos_blobs`. But you shouldn't need to work with these associations directly in + # Under the covers, this relationship is implemented as a +has_many+ association to a + # ActiveStorage::Attachment record and a +has_many-through+ association to a + # ActiveStorage::Blob record. These associations are available as +photos_attachments+ + # and +photos_blobs+. But you shouldn't need to work with these associations directly in # most circumstances. # - # The system has been designed to having you go through the `ActiveStorage::Attached::Many` - # proxy that provides the dynamic proxy to the associations and factory methods, like `#attach`. + # The system has been designed to having you go through the ActiveStorage::Attached::Many + # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+. # # If the +:dependent+ option isn't set, all the attachments will be purged # (i.e. destroyed) whenever the record is destroyed. def has_many_attached(name, dependent: :purge_later) - define_method(name) do - if instance_variable_defined?("@active_storage_attached_#{name}") - instance_variable_get("@active_storage_attached_#{name}") - else - instance_variable_set("@active_storage_attached_#{name}", ActiveStorage::Attached::Many.new(name, self)) + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name} + @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self) end - end + CODE has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment" has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob @@ -81,4 +79,4 @@ module ActiveStorage end end end -end
\ No newline at end of file +end diff --git a/activestorage/lib/active_storage/attached/many.rb b/activestorage/lib/active_storage/attached/many.rb index 82989e4605..59b7d7d559 100644 --- a/activestorage/lib/active_storage/attached/many.rb +++ b/activestorage/lib/active_storage/attached/many.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveStorage # Decorated proxy object representing of multiple attachments to a model. class Attached::Many < Attached @@ -5,7 +7,7 @@ module ActiveStorage # Returns all the associated attachment records. # - # All methods called on this proxy object that aren't listed here will automatically be delegated to `attachments`. + # All methods called on this proxy object that aren't listed here will automatically be delegated to +attachments+. def attachments record.public_send("#{name}_attachments") end @@ -51,4 +53,3 @@ module ActiveStorage end end end - diff --git a/activestorage/lib/active_storage/attached/one.rb b/activestorage/lib/active_storage/attached/one.rb index 6b34b30f1c..2e5831348e 100644 --- a/activestorage/lib/active_storage/attached/one.rb +++ b/activestorage/lib/active_storage/attached/one.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveStorage # Representation of a single attachment to a model. class Attached::One < Attached diff --git a/activestorage/lib/active_storage/engine.rb b/activestorage/lib/active_storage/engine.rb index da83d3908a..a5562b32d3 100644 --- a/activestorage/lib/active_storage/engine.rb +++ b/activestorage/lib/active_storage/engine.rb @@ -5,6 +5,8 @@ require "active_storage" module ActiveStorage class Engine < Rails::Engine # :nodoc: + isolate_namespace ActiveStorage + config.active_storage = ActiveSupport::OrderedOptions.new config.eager_load_namespaces << ActiveStorage @@ -34,22 +36,21 @@ module ActiveStorage end initializer "active_storage.services" do - config.after_initialize do |app| - if config_choice = app.config.active_storage.service - config_file = Pathname.new(Rails.root.join("config/storage.yml")) - raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist? + config.to_prepare do + if config_choice = Rails.configuration.active_storage.service + configs = Rails.configuration.active_storage.service_configurations ||= begin + config_file = Pathname.new(Rails.root.join("config/storage.yml")) + raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist? - require "yaml" - require "erb" + require "yaml" + require "erb" - configs = - begin - YAML.load(ERB.new(config_file.read).result) || {} - rescue Psych::SyntaxError => e - raise "YAML syntax error occurred while parsing #{config_file}. " \ - "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ - "Error: #{e.message}" - end + YAML.load(ERB.new(config_file.read).result) || {} + rescue Psych::SyntaxError => e + raise "YAML syntax error occurred while parsing #{config_file}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{e.message}" + end ActiveStorage::Blob.service = begin diff --git a/activestorage/lib/active_storage/gem_version.rb b/activestorage/lib/active_storage/gem_version.rb index db79c669bf..bb6241ae47 100644 --- a/activestorage/lib/active_storage/gem_version.rb +++ b/activestorage/lib/active_storage/gem_version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveStorage # Returns the version of the currently loaded Active Storage as a <tt>Gem::Version</tt> def self.gem_version diff --git a/activestorage/lib/active_storage/log_subscriber.rb b/activestorage/lib/active_storage/log_subscriber.rb index 5c1b8d23ef..80cc5d5c80 100644 --- a/activestorage/lib/active_storage/log_subscriber.rb +++ b/activestorage/lib/active_storage/log_subscriber.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require "active_support/log_subscriber" module ActiveStorage class LogSubscriber < ActiveSupport::LogSubscriber def service_upload(event) message = "Uploaded file to key: #{key_in(event)}" - message << " (checksum: #{event.payload[:checksum]})" if event.payload[:checksum] + message += " (checksum: #{event.payload[:checksum]})" if event.payload[:checksum] info event, color(message, GREEN) end diff --git a/activestorage/lib/active_storage/migration.rb b/activestorage/lib/active_storage/migration.rb deleted file mode 100644 index 2e35e163cd..0000000000 --- a/activestorage/lib/active_storage/migration.rb +++ /dev/null @@ -1,27 +0,0 @@ -class ActiveStorageCreateTables < ActiveRecord::Migration[5.1] - def change - create_table :active_storage_blobs do |t| - t.string :key - t.string :filename - t.string :content_type - t.text :metadata - t.integer :byte_size - t.string :checksum - t.datetime :created_at - - t.index [ :key ], unique: true - end - - create_table :active_storage_attachments do |t| - t.string :name - t.string :record_type - t.integer :record_id - t.integer :blob_id - - t.datetime :created_at - - t.index :blob_id - t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true - end - end -end diff --git a/activestorage/lib/active_storage/service.rb b/activestorage/lib/active_storage/service.rb index eb25e9f001..ce736b8728 100644 --- a/activestorage/lib/active_storage/service.rb +++ b/activestorage/lib/active_storage/service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_storage/log_subscriber" module ActiveStorage @@ -59,43 +61,43 @@ module ActiveStorage end end - # Upload the `io` to the `key` specified. If a `checksum` is provided, the service will - # ensure a match when the upload has completed or raise an `ActiveStorage::IntegrityError`. + # Upload the +io+ to the +key+ specified. If a +checksum+ is provided, the service will + # ensure a match when the upload has completed or raise an ActiveStorage::IntegrityError. def upload(key, io, checksum: nil) raise NotImplementedError end - # Return the content of the file at the `key`. + # Return the content of the file at the +key+. def download(key) raise NotImplementedError end - # Delete the file at the `key`. + # Delete the file at the +key+. def delete(key) raise NotImplementedError end - # Return true if a file exists at the `key`. + # Return true if a file exists at the +key+. def exist?(key) raise NotImplementedError end - # Returns a signed, temporary URL for the file at the `key`. The URL will be valid for the amount - # of seconds specified in `expires_in`. You most also provide the `disposition` (`:inline` or `:attachment`), - # `filename`, and `content_type` that you wish the file to be served with on request. + # Returns a signed, temporary URL for the file at the +key+. The URL will be valid for the amount + # of seconds specified in +expires_in+. You most also provide the +disposition+ (+:inline+ or +:attachment+), + # +filename+, and +content_type+ that you wish the file to be served with on request. def url(key, expires_in:, disposition:, filename:, content_type:) raise NotImplementedError end - # Returns a signed, temporary URL that a direct upload file can be PUT to on the `key`. - # The URL will be valid for the amount of seconds specified in `expires_in`. - # You most also provide the `content_type`, `content_length`, and `checksum` of the file + # Returns a signed, temporary URL that a direct upload file can be PUT to on the +key+. + # The URL will be valid for the amount of seconds specified in +expires_in+. + # You most also provide the +content_type+, +content_length+, and +checksum+ of the file # that will be uploaded. All these attributes will be validated by the service upon upload. def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) raise NotImplementedError end - # Returns a Hash of headers for `url_for_direct_upload` requests. + # Returns a Hash of headers for +url_for_direct_upload+ requests. def headers_for_direct_upload(key, filename:, content_type:, content_length:, checksum:) {} end diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb index c2e1b25a43..d066deaa20 100644 --- a/activestorage/lib/active_storage/service/azure_storage_service.rb +++ b/activestorage/lib/active_storage/service/azure_storage_service.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require "active_support/core_ext/numeric/bytes" require "azure/storage" require "azure/storage/core/auth/shared_access_signature" module ActiveStorage # Wraps the Microsoft Azure Storage Blob Service as a Active Storage service. - # See `ActiveStorage::Service` for the generic API documentation that applies to all services. + # See ActiveStorage::Service for the generic API documentation that applies to all services. class Service::AzureStorageService < Service attr_reader :client, :path, :blobs, :container, :signer @@ -57,11 +59,12 @@ module ActiveStorage end end - def url(key, expires_in:, disposition:, filename:) + def url(key, expires_in:, disposition:, filename:, content_type:) instrument :url, key do |payload| base_url = url_for(key) generated_url = signer.signed_uri(URI(base_url), false, permissions: "r", - expiry: format_expiry(expires_in), content_disposition: "#{disposition}; filename=\"#{filename}\"").to_s + expiry: format_expiry(expires_in), content_type: content_type, + content_disposition: "#{disposition}; filename=\"#{filename}\"").to_s payload[:url] = generated_url diff --git a/activestorage/lib/active_storage/service/configurator.rb b/activestorage/lib/active_storage/service/configurator.rb index 5d6475a8ae..39951fd026 100644 --- a/activestorage/lib/active_storage/service/configurator.rb +++ b/activestorage/lib/active_storage/service/configurator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ActiveStorage class Service::Configurator #:nodoc: attr_reader :configurations diff --git a/activestorage/lib/active_storage/service/disk_service.rb b/activestorage/lib/active_storage/service/disk_service.rb index 3d92102cf0..cf4019966f 100644 --- a/activestorage/lib/active_storage/service/disk_service.rb +++ b/activestorage/lib/active_storage/service/disk_service.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require "fileutils" require "pathname" require "digest/md5" require "active_support/core_ext/numeric/bytes" module ActiveStorage - # Wraps a local disk path as a Active Storage service. See `ActiveStorage::Service` for the generic API + # Wraps a local disk path as a Active Storage service. See ActiveStorage::Service for the generic API # documentation that applies to all services. class Service::DiskService < Service attr_reader :root @@ -124,4 +126,3 @@ module ActiveStorage end end end - diff --git a/activestorage/lib/active_storage/service/gcs_service.rb b/activestorage/lib/active_storage/service/gcs_service.rb index ea4ec5a790..32dfb4851b 100644 --- a/activestorage/lib/active_storage/service/gcs_service.rb +++ b/activestorage/lib/active_storage/service/gcs_service.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require "google/cloud/storage" require "active_support/core_ext/object/to_query" module ActiveStorage - # Wraps the Google Cloud Storage as a Active Storage service. See `ActiveStorage::Service` for the generic API + # Wraps the Google Cloud Storage as a Active Storage service. See ActiveStorage::Service for the generic API # documentation that applies to all services. class Service::GCSService < Service attr_reader :client, :bucket diff --git a/activestorage/lib/active_storage/service/mirror_service.rb b/activestorage/lib/active_storage/service/mirror_service.rb index 2403eeb1e9..39e922f7ab 100644 --- a/activestorage/lib/active_storage/service/mirror_service.rb +++ b/activestorage/lib/active_storage/service/mirror_service.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require "active_support/core_ext/module/delegation" module ActiveStorage - # Wraps a set of mirror services and provides a single `ActiveStorage::Service` object that will all - # have the files uploaded to them. A `primary` service is designated to answer calls to `download`, `exists?`, - # and `url`. + # Wraps a set of mirror services and provides a single ActiveStorage::Service object that will all + # have the files uploaded to them. A +primary+ service is designated to answer calls to +download+, +exists?+, + # and +url+. class Service::MirrorService < Service attr_reader :primary, :mirrors @@ -20,15 +22,15 @@ module ActiveStorage @primary, @mirrors = primary, mirrors end - # Upload the `io` to the `key` specified to all services. If a `checksum` is provided, all services will - # ensure a match when the upload has completed or raise an `ActiveStorage::IntegrityError`. + # Upload the +io+ to the +key+ specified to all services. If a +checksum+ is provided, all services will + # ensure a match when the upload has completed or raise an ActiveStorage::IntegrityError. def upload(key, io, checksum: nil) each_service.collect do |service| service.upload key, io.tap(&:rewind), checksum: checksum end end - # Delete the file at the `key` on all services. + # Delete the file at the +key+ on all services. def delete(key) perform_across_services :delete, key end diff --git a/activestorage/lib/active_storage/service/s3_service.rb b/activestorage/lib/active_storage/service/s3_service.rb index 5153f5db0d..98c9dd2972 100644 --- a/activestorage/lib/active_storage/service/s3_service.rb +++ b/activestorage/lib/active_storage/service/s3_service.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require "aws-sdk" require "active_support/core_ext/numeric/bytes" module ActiveStorage # Wraps the Amazon Simple Storage Service (S3) as a Active Storage service. - # See `ActiveStorage::Service` for the generic API documentation that applies to all services. + # See ActiveStorage::Service for the generic API documentation that applies to all services. class Service::S3Service < Service attr_reader :client, :bucket, :upload_options diff --git a/activestorage/lib/active_storage/version.rb b/activestorage/lib/active_storage/version.rb index 8f45480712..4b6631832b 100644 --- a/activestorage/lib/active_storage/version.rb +++ b/activestorage/lib/active_storage/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "gem_version" module ActiveStorage diff --git a/activestorage/lib/tasks/activestorage.rake b/activestorage/lib/tasks/activestorage.rake index 1d386e67df..746bc5b28a 100644 --- a/activestorage/lib/tasks/activestorage.rake +++ b/activestorage/lib/tasks/activestorage.rake @@ -1,13 +1,8 @@ -require "fileutils" +# frozen_string_literal: true namespace :activestorage do desc "Copy over the migration needed to the application" - task :install do - migration_file_path = "db/migrate/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_active_storage_create_tables.rb" - FileUtils.mkdir_p Rails.root.join("db/migrate") - FileUtils.cp File.expand_path("../../active_storage/migration.rb", __FILE__), Rails.root.join(migration_file_path) - puts "Copied migration to #{migration_file_path}" - - puts "Now run rails db:migrate to create the tables for Active Storage" + task install: :environment do + Rake::Task["active_storage:install:migrations"].invoke end end diff --git a/activestorage/test/controllers/blobs_controller_test.rb b/activestorage/test/controllers/blobs_controller_test.rb new file mode 100644 index 0000000000..d682893e1d --- /dev/null +++ b/activestorage/test/controllers/blobs_controller_test.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "test_helper" +require "database/setup" + +class ActiveStorage::BlobsControllerTest < ActionDispatch::IntegrationTest + setup do + @blob = create_image_blob filename: "racecar.jpg" + end + + test "showing blob utilizes browser caching" do + get rails_blob_url(@blob) + + assert_redirected_to(/racecar.jpg/) + assert_equal "max-age=300, private", @response.headers["Cache-Control"] + end +end diff --git a/activestorage/test/controllers/direct_uploads_controller_test.rb b/activestorage/test/controllers/direct_uploads_controller_test.rb index 4f335c7759..888767086c 100644 --- a/activestorage/test/controllers/direct_uploads_controller_test.rb +++ b/activestorage/test/controllers/direct_uploads_controller_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "database/setup" diff --git a/activestorage/test/controllers/disk_controller_test.rb b/activestorage/test/controllers/disk_controller_test.rb index df7954d6b4..53a086f214 100644 --- a/activestorage/test/controllers/disk_controller_test.rb +++ b/activestorage/test/controllers/disk_controller_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "database/setup" diff --git a/activestorage/test/controllers/variants_controller_test.rb b/activestorage/test/controllers/variants_controller_test.rb index b3ff83e95e..d9a85e16d9 100644 --- a/activestorage/test/controllers/variants_controller_test.rb +++ b/activestorage/test/controllers/variants_controller_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "database/setup" diff --git a/activestorage/test/database/create_users_migration.rb b/activestorage/test/database/create_users_migration.rb index a0b72a90ee..317daa87b5 100644 --- a/activestorage/test/database/create_users_migration.rb +++ b/activestorage/test/database/create_users_migration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ActiveStorageCreateUsers < ActiveRecord::Migration[5.1] def change create_table :users do |t| diff --git a/activestorage/test/database/setup.rb b/activestorage/test/database/setup.rb index b12038ee68..87564499e6 100644 --- a/activestorage/test/database/setup.rb +++ b/activestorage/test/database/setup.rb @@ -1,6 +1,7 @@ -require "active_storage/migration" +# frozen_string_literal: true + require_relative "create_users_migration" ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") -ActiveStorageCreateTables.migrate(:up) +ActiveRecord::Migrator.migrate File.expand_path("../../../db/migrate", __FILE__) ActiveStorageCreateUsers.migrate(:up) diff --git a/activestorage/test/dummy/Rakefile b/activestorage/test/dummy/Rakefile index d1baef0699..c4f9523878 100644 --- a/activestorage/test/dummy/Rakefile +++ b/activestorage/test/dummy/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "config/application" Rails.application.load_tasks diff --git a/activestorage/test/dummy/app/controllers/application_controller.rb b/activestorage/test/dummy/app/controllers/application_controller.rb index 1c07694e9d..280cc28ce2 100644 --- a/activestorage/test/dummy/app/controllers/application_controller.rb +++ b/activestorage/test/dummy/app/controllers/application_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ApplicationController < ActionController::Base protect_from_forgery with: :exception end diff --git a/activestorage/test/dummy/app/helpers/application_helper.rb b/activestorage/test/dummy/app/helpers/application_helper.rb index de6be7945c..15b06f0f67 100644 --- a/activestorage/test/dummy/app/helpers/application_helper.rb +++ b/activestorage/test/dummy/app/helpers/application_helper.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + module ApplicationHelper end diff --git a/activestorage/test/dummy/app/jobs/application_job.rb b/activestorage/test/dummy/app/jobs/application_job.rb index a009ace51c..d92ffddcb5 100644 --- a/activestorage/test/dummy/app/jobs/application_job.rb +++ b/activestorage/test/dummy/app/jobs/application_job.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + class ApplicationJob < ActiveJob::Base end diff --git a/activestorage/test/dummy/app/models/application_record.rb b/activestorage/test/dummy/app/models/application_record.rb index 10a4cba84d..71fbba5b32 100644 --- a/activestorage/test/dummy/app/models/application_record.rb +++ b/activestorage/test/dummy/app/models/application_record.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end diff --git a/activestorage/test/dummy/bin/bundle b/activestorage/test/dummy/bin/bundle index fe1874509a..277e128251 100755 --- a/activestorage/test/dummy/bin/bundle +++ b/activestorage/test/dummy/bin/bundle @@ -1,3 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__) load Gem.bin_path("bundler", "bundle") diff --git a/activestorage/test/dummy/bin/rails b/activestorage/test/dummy/bin/rails index efc0377492..22f2d8deee 100755 --- a/activestorage/test/dummy/bin/rails +++ b/activestorage/test/dummy/bin/rails @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + APP_PATH = File.expand_path("../config/application", __dir__) require_relative "../config/boot" require "rails/commands" diff --git a/activestorage/test/dummy/bin/rake b/activestorage/test/dummy/bin/rake index 4fbf10b960..e436ea54a1 100755 --- a/activestorage/test/dummy/bin/rake +++ b/activestorage/test/dummy/bin/rake @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + require_relative "../config/boot" require "rake" Rake.application.run diff --git a/activestorage/test/dummy/bin/yarn b/activestorage/test/dummy/bin/yarn index b8fe4eeaf3..c9b7498378 100755 --- a/activestorage/test/dummy/bin/yarn +++ b/activestorage/test/dummy/bin/yarn @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + VENDOR_PATH = File.expand_path("..", __dir__) Dir.chdir(VENDOR_PATH) do begin diff --git a/activestorage/test/dummy/config.ru b/activestorage/test/dummy/config.ru index 441e6ff0c3..bff88d608a 100644 --- a/activestorage/test/dummy/config.ru +++ b/activestorage/test/dummy/config.ru @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file is used by Rack-based servers to start the application. require_relative "config/environment" diff --git a/activestorage/test/dummy/config/application.rb b/activestorage/test/dummy/config/application.rb index 5c59401358..7ee6625bb5 100644 --- a/activestorage/test/dummy/config/application.rb +++ b/activestorage/test/dummy/config/application.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "boot" require "rails" diff --git a/activestorage/test/dummy/config/boot.rb b/activestorage/test/dummy/config/boot.rb index 116591a4ed..59459d4ae3 100644 --- a/activestorage/test/dummy/config/boot.rb +++ b/activestorage/test/dummy/config/boot.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Set up gems listed in the Gemfile. ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) diff --git a/activestorage/test/dummy/config/environment.rb b/activestorage/test/dummy/config/environment.rb index cac5315775..7df99e89c6 100644 --- a/activestorage/test/dummy/config/environment.rb +++ b/activestorage/test/dummy/config/environment.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Load the Rails application. require_relative "application" diff --git a/activestorage/test/dummy/config/environments/development.rb b/activestorage/test/dummy/config/environments/development.rb index 5f3d463210..47fc5bf25c 100644 --- a/activestorage/test/dummy/config/environments/development.rb +++ b/activestorage/test/dummy/config/environments/development.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. diff --git a/activestorage/test/dummy/config/environments/production.rb b/activestorage/test/dummy/config/environments/production.rb index 343da23f0b..34ac3bc561 100644 --- a/activestorage/test/dummy/config/environments/production.rb +++ b/activestorage/test/dummy/config/environments/production.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. diff --git a/activestorage/test/dummy/config/environments/test.rb b/activestorage/test/dummy/config/environments/test.rb index 4f2c048600..ce0889e8ae 100644 --- a/activestorage/test/dummy/config/environments/test.rb +++ b/activestorage/test/dummy/config/environments/test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. diff --git a/activestorage/test/dummy/config/initializers/application_controller_renderer.rb b/activestorage/test/dummy/config/initializers/application_controller_renderer.rb index 51639b67a0..315ac48a9a 100644 --- a/activestorage/test/dummy/config/initializers/application_controller_renderer.rb +++ b/activestorage/test/dummy/config/initializers/application_controller_renderer.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # ApplicationController.renderer.defaults.merge!( diff --git a/activestorage/test/dummy/config/initializers/assets.rb b/activestorage/test/dummy/config/initializers/assets.rb index c1f948d018..ba194685a2 100644 --- a/activestorage/test/dummy/config/initializers/assets.rb +++ b/activestorage/test/dummy/config/initializers/assets.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Version of your assets, change this if you want to expire all your assets. diff --git a/activestorage/test/dummy/config/initializers/backtrace_silencers.rb b/activestorage/test/dummy/config/initializers/backtrace_silencers.rb index 59385cdf37..d0f0d3b5df 100644 --- a/activestorage/test/dummy/config/initializers/backtrace_silencers.rb +++ b/activestorage/test/dummy/config/initializers/backtrace_silencers.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. diff --git a/activestorage/test/dummy/config/initializers/cookies_serializer.rb b/activestorage/test/dummy/config/initializers/cookies_serializer.rb index 5a6a32d371..ee8dff9c99 100644 --- a/activestorage/test/dummy/config/initializers/cookies_serializer.rb +++ b/activestorage/test/dummy/config/initializers/cookies_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Specify a serializer for the signed and encrypted cookie jars. diff --git a/activestorage/test/dummy/config/initializers/filter_parameter_logging.rb b/activestorage/test/dummy/config/initializers/filter_parameter_logging.rb index 4a994e1e7b..7a4f47b4c2 100644 --- a/activestorage/test/dummy/config/initializers/filter_parameter_logging.rb +++ b/activestorage/test/dummy/config/initializers/filter_parameter_logging.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. diff --git a/activestorage/test/dummy/config/initializers/inflections.rb b/activestorage/test/dummy/config/initializers/inflections.rb index ac033bf9dc..aa7435fbc9 100644 --- a/activestorage/test/dummy/config/initializers/inflections.rb +++ b/activestorage/test/dummy/config/initializers/inflections.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections diff --git a/activestorage/test/dummy/config/initializers/mime_types.rb b/activestorage/test/dummy/config/initializers/mime_types.rb index dc1899682b..6e1d16f027 100644 --- a/activestorage/test/dummy/config/initializers/mime_types.rb +++ b/activestorage/test/dummy/config/initializers/mime_types.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: diff --git a/activestorage/test/dummy/config/initializers/wrap_parameters.rb b/activestorage/test/dummy/config/initializers/wrap_parameters.rb index bbfc3961bf..2f3c0db471 100644 --- a/activestorage/test/dummy/config/initializers/wrap_parameters.rb +++ b/activestorage/test/dummy/config/initializers/wrap_parameters.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # This file contains settings for ActionController::ParamsWrapper which diff --git a/activestorage/test/dummy/config/routes.rb b/activestorage/test/dummy/config/routes.rb index 1daf9a4121..edf04d2d63 100644 --- a/activestorage/test/dummy/config/routes.rb +++ b/activestorage/test/dummy/config/routes.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + Rails.application.routes.draw do end diff --git a/activestorage/test/dummy/config/spring.rb b/activestorage/test/dummy/config/spring.rb index c9119b40c0..ff5ba06b6d 100644 --- a/activestorage/test/dummy/config/spring.rb +++ b/activestorage/test/dummy/config/spring.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + %w( .ruby-version .rbenv-vars diff --git a/activestorage/test/filename_test.rb b/activestorage/test/filename_test.rb index e448238675..f1e4a467ba 100644 --- a/activestorage/test/filename_test.rb +++ b/activestorage/test/filename_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" class ActiveStorage::FilenameTest < ActiveSupport::TestCase @@ -10,8 +12,8 @@ class ActiveStorage::FilenameTest < ActiveSupport::TestCase end test "sanitize transcodes to valid UTF-8" do - { "\xF6".force_encoding(Encoding::ISO8859_1) => "ö", - "\xC3".force_encoding(Encoding::ISO8859_1) => "Ã", + { "\xF6".dup.force_encoding(Encoding::ISO8859_1) => "ö", + "\xC3".dup.force_encoding(Encoding::ISO8859_1) => "Ã", "\xAD" => "�", "\xCF" => "�", "\x00" => "", diff --git a/activestorage/test/models/attachments_test.rb b/activestorage/test/models/attachments_test.rb index 82256e1f44..7cfd8683db 100644 --- a/activestorage/test/models/attachments_test.rb +++ b/activestorage/test/models/attachments_test.rb @@ -1,13 +1,8 @@ +# frozen_string_literal: true + require "test_helper" require "database/setup" -# ActiveRecord::Base.logger = Logger.new(STDOUT) - -class User < ActiveRecord::Base - has_one_attached :avatar - has_many_attached :highlights -end - class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase include ActiveJob::TestHelper @@ -25,11 +20,17 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase assert_equal "funky.jpg", @user.avatar.filename.to_s end - test "attach new blob" do + test "attach new blob from a Hash" do @user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" assert_equal "town.jpg", @user.avatar.filename.to_s end + test "attach new blob from an UploadedFile" do + file = file_fixture "racecar.jpg" + @user.avatar.attach Rack::Test::UploadedFile.new file + assert_equal "racecar.jpg", @user.avatar.filename.to_s + end + test "access underlying associations of new blob" do @user.avatar.attach create_blob(filename: "funky.jpg") assert_equal @user, @user.avatar_attachment.record diff --git a/activestorage/test/models/blob_test.rb b/activestorage/test/models/blob_test.rb index 8b14bcec87..1c50d18994 100644 --- a/activestorage/test/models/blob_test.rb +++ b/activestorage/test/models/blob_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "database/setup" diff --git a/activestorage/test/models/variant_test.rb b/activestorage/test/models/variant_test.rb index 04ebaccb6a..f4e1e9e51d 100644 --- a/activestorage/test/models/variant_test.rb +++ b/activestorage/test/models/variant_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "database/setup" diff --git a/activestorage/test/service/azure_storage_service_test.rb b/activestorage/test/service/azure_storage_service_test.rb index e2be510b60..dac154cf11 100644 --- a/activestorage/test/service/azure_storage_service_test.rb +++ b/activestorage/test/service/azure_storage_service_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "service/shared_service_tests" require "uri" @@ -6,8 +8,15 @@ if SERVICE_CONFIGURATIONS[:azure] SERVICE = ActiveStorage::Service.configure(:azure, SERVICE_CONFIGURATIONS) include ActiveStorage::Service::SharedServiceTests - end + test "signed URL generation" do + url = @service.url(FIXTURE_KEY, expires_in: 5.minutes, + disposition: :inline, filename: "avatar.png", content_type: "image/png") + + assert_match(/(\S+)&rsct=image%2Fpng&rscd=inline%3B\+filename%3D%22avatar.png/, url) + assert_match SERVICE_CONFIGURATIONS[:azure][:container], url + end + end else puts "Skipping Azure Storage Service tests because no Azure configuration was supplied" end diff --git a/activestorage/test/service/configurations.yml b/activestorage/test/service/configurations.yml index d7aa672573..56ed37be5d 100644 --- a/activestorage/test/service/configurations.yml +++ b/activestorage/test/service/configurations.yml @@ -1,10 +1,10 @@ -s3: - service: S3 - access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %> - secret_access_key: <%= ENV["AWS_SECRET_KEY"] %> - region: us-east-2 - bucket: rails-ci-activestorage - +# s3: +# service: S3 +# access_key_id: "" +# secret_access_key: "" +# region: "" +# bucket: "" +# # gcs: # service: GCS # keyfile: { diff --git a/activestorage/test/service/configurator_test.rb b/activestorage/test/service/configurator_test.rb index c69b8d5087..a2fd035e02 100644 --- a/activestorage/test/service/configurator_test.rb +++ b/activestorage/test/service/configurator_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "service/shared_service_tests" class ActiveStorage::Service::ConfiguratorTest < ActiveSupport::TestCase diff --git a/activestorage/test/service/disk_service_test.rb b/activestorage/test/service/disk_service_test.rb index 2fdb113616..835a3a2971 100644 --- a/activestorage/test/service/disk_service_test.rb +++ b/activestorage/test/service/disk_service_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "service/shared_service_tests" class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase diff --git a/activestorage/test/service/gcs_service_test.rb b/activestorage/test/service/gcs_service_test.rb index 03048731bc..5f53f61baf 100644 --- a/activestorage/test/service/gcs_service_test.rb +++ b/activestorage/test/service/gcs_service_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "service/shared_service_tests" require "net/http" diff --git a/activestorage/test/service/mirror_service_test.rb b/activestorage/test/service/mirror_service_test.rb index 129e11d06f..93e86eff70 100644 --- a/activestorage/test/service/mirror_service_test.rb +++ b/activestorage/test/service/mirror_service_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "service/shared_service_tests" class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase diff --git a/activestorage/test/service/s3_service_test.rb b/activestorage/test/service/s3_service_test.rb index 57e13e6538..98f520741d 100644 --- a/activestorage/test/service/s3_service_test.rb +++ b/activestorage/test/service/s3_service_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "service/shared_service_tests" require "net/http" diff --git a/activestorage/test/service/shared_service_tests.rb b/activestorage/test/service/shared_service_tests.rb index 07620d91e4..a9e1cb6ce9 100644 --- a/activestorage/test/service/shared_service_tests.rb +++ b/activestorage/test/service/shared_service_tests.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "test_helper" require "active_support/core_ext/securerandom" @@ -5,7 +7,7 @@ module ActiveStorage::Service::SharedServiceTests extend ActiveSupport::Concern FIXTURE_KEY = SecureRandom.base58(24) - FIXTURE_DATA = "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000\020\000\000\000\020\001\003\000\000\000%=m\"\000\000\000\006PLTE\000\000\000\377\377\377\245\331\237\335\000\000\0003IDATx\234c\370\377\237\341\377_\206\377\237\031\016\2603\334?\314p\1772\303\315\315\f7\215\031\356\024\203\320\275\317\f\367\201R\314\f\017\300\350\377\177\000Q\206\027(\316]\233P\000\000\000\000IEND\256B`\202".force_encoding(Encoding::BINARY) + FIXTURE_DATA = "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000\020\000\000\000\020\001\003\000\000\000%=m\"\000\000\000\006PLTE\000\000\000\377\377\377\245\331\237\335\000\000\0003IDATx\234c\370\377\237\341\377_\206\377\237\031\016\2603\334?\314p\1772\303\315\315\f7\215\031\356\024\203\320\275\317\f\367\201R\314\f\017\300\350\377\177\000Q\206\027(\316]\233P\000\000\000\000IEND\256B`\202".dup.force_encoding(Encoding::BINARY) included do setup do diff --git a/activestorage/test/template/image_tag_test.rb b/activestorage/test/template/image_tag_test.rb index 83c95c01a1..46dd97b3e9 100644 --- a/activestorage/test/template/image_tag_test.rb +++ b/activestorage/test/template/image_tag_test.rb @@ -1,10 +1,8 @@ +# frozen_string_literal: true + require "test_helper" require "database/setup" -class User < ActiveRecord::Base - has_one_attached :avatar -end - class ActiveStorage::ImageTagTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper diff --git a/activestorage/test/test_helper.rb b/activestorage/test/test_helper.rb index 5b6d3b64c2..0bb3a925b8 100644 --- a/activestorage/test/test_helper.rb +++ b/activestorage/test/test_helper.rb @@ -1,16 +1,24 @@ +# frozen_string_literal: true + require File.expand_path("../../test/dummy/config/environment.rb", __FILE__) require "bundler/setup" require "active_support" require "active_support/test_case" require "active_support/testing/autorun" -require "byebug" + +begin + require "byebug" +rescue LoadError +end require "active_job" ActiveJob::Base.queue_adapter = :test -ActiveJob::Base.logger = nil +ActiveJob::Base.logger = ActiveSupport::Logger.new(nil) -require "active_storage" +# Filter out Minitest backtrace while allowing backtrace from other libraries +# to be shown. +Minitest.backtrace_filter = Minitest::BacktraceFilter.new require "yaml" SERVICE_CONFIGURATIONS = begin @@ -54,3 +62,8 @@ end require "global_id" GlobalID.app = "ActiveStorageExampleApp" ActiveRecord::Base.send :include, GlobalID::Identification + +class User < ActiveRecord::Base + has_one_attached :avatar + has_many_attached :highlights +end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index d5087f67af..ac1043df78 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,8 @@ +* `Module#delegate_missing_to` now raises `DelegationError` if target is nil, + similar to `Module#delegate`. + + *Anton Khamets* + * Update `String#camelize` to provide feedback when wrong option is passed `String#camelize` was returning nil without any feedback when an diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index c979af9de3..1840dc942f 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -273,10 +273,16 @@ class Module def method_missing(method, *args, &block) if #{target}.respond_to?(method) #{target}.public_send(method, *args, &block) - elsif #{target}.nil? - raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil" else - super + begin + super + rescue NoMethodError + if #{target}.nil? + raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil" + else + raise + end + end end end RUBY diff --git a/activesupport/lib/active_support/current_attributes.rb b/activesupport/lib/active_support/current_attributes.rb index 9ab1546064..4e6d8e4585 100644 --- a/activesupport/lib/active_support/current_attributes.rb +++ b/activesupport/lib/active_support/current_attributes.rb @@ -33,7 +33,7 @@ module ActiveSupport # # private # def authenticate - # if authenticated_user = User.find_by(id: cookies.signed[:user_id]) + # if authenticated_user = User.find_by(id: cookies.encrypted[:user_id]) # Current.user = authenticated_user # else # redirect_to new_session_url diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 69c8a312d8..e918823074 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -374,6 +374,14 @@ class ModuleTest < ActiveSupport::TestCase assert_match(/undefined method `my_fake_method' for/, e.message) end + def test_delegate_missing_to_raises_delegation_error_if_target_nil + e = assert_raises(Module::DelegationError) do + DecoratedTester.new(nil).name + end + + assert_equal "name delegated to client, but client is nil", e.message + end + def test_delegate_missing_to_affects_respond_to assert DecoratedTester.new(@david).respond_to?(:name) assert_not DecoratedTester.new(@david).respond_to?(:private_name) diff --git a/ci/travis.rb b/ci/travis.rb index 8339e0d08b..b124358789 100755 --- a/ci/travis.rb +++ b/ci/travis.rb @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + require "fileutils" include FileUtils @@ -28,6 +30,7 @@ class Build "av" => "actionview", "aj" => "activejob", "ac" => "actioncable", + "ast" => "activestorage", "guides" => "guides" } @@ -163,6 +166,7 @@ ENV["GEM"].split(",").each do |gem| next if gem == "aj:integration" && isolated next if gem == "guides" && isolated next if gem == "av:ujs" && isolated + next if gem == "ast" && isolated build = Build.new(gem, isolated: isolated) results[build.key] = build.run! diff --git a/guides/Rakefile b/guides/Rakefile index 3a6f10040f..84e18e0972 100644 --- a/guides/Rakefile +++ b/guides/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + namespace :guides do desc 'Generate guides (for authors), use ONLY=foo to process just "foo.md"' task generate: "generate:html" diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb index 8b7aa893fd..4d8d8db3e5 100644 --- a/guides/bug_report_templates/action_controller_gem.rb +++ b/guides/bug_report_templates/action_controller_gem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb index 3dd66c95ec..1f862e07da 100644 --- a/guides/bug_report_templates/action_controller_master.rb +++ b/guides/bug_report_templates/action_controller_master.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e diff --git a/guides/bug_report_templates/active_job_gem.rb b/guides/bug_report_templates/active_job_gem.rb index 252b270a0c..af777a86ef 100644 --- a/guides/bug_report_templates/active_job_gem.rb +++ b/guides/bug_report_templates/active_job_gem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e diff --git a/guides/bug_report_templates/active_job_master.rb b/guides/bug_report_templates/active_job_master.rb index 7591470440..39fb3f60a6 100644 --- a/guides/bug_report_templates/active_job_master.rb +++ b/guides/bug_report_templates/active_job_master.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb index 61d4e8d395..168e2dcc66 100644 --- a/guides/bug_report_templates/active_record_gem.rb +++ b/guides/bug_report_templates/active_record_gem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb index 8bbc1ef19e..cbd2cff2b8 100644 --- a/guides/bug_report_templates/active_record_master.rb +++ b/guides/bug_report_templates/active_record_master.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb index 00ba3c1cd6..b931ed0beb 100644 --- a/guides/bug_report_templates/active_record_migrations_gem.rb +++ b/guides/bug_report_templates/active_record_migrations_gem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb index 52c9028b0f..2c009c0563 100644 --- a/guides/bug_report_templates/active_record_migrations_master.rb +++ b/guides/bug_report_templates/active_record_migrations_master.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e diff --git a/guides/bug_report_templates/benchmark.rb b/guides/bug_report_templates/benchmark.rb index a0b541d012..d0f5a634bc 100644 --- a/guides/bug_report_templates/benchmark.rb +++ b/guides/bug_report_templates/benchmark.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb index 4dcd04ea27..c990bda005 100644 --- a/guides/bug_report_templates/generic_gem.rb +++ b/guides/bug_report_templates/generic_gem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb index ed45726e92..1a9b99b624 100644 --- a/guides/bug_report_templates/generic_master.rb +++ b/guides/bug_report_templates/generic_master.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "bundler/inline" rescue LoadError => e diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb index 0f611c8f2b..f2d4d6f647 100644 --- a/guides/rails_guides.rb +++ b/guides/rails_guides.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $:.unshift __dir__ as_lib = File.expand_path("../activesupport/lib", __dir__) diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb index 35f014747c..7205f37be7 100644 --- a/guides/rails_guides/generator.rb +++ b/guides/rails_guides/generator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "set" require "fileutils" diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb index 520aa7f7cc..a6970fb90c 100644 --- a/guides/rails_guides/helpers.rb +++ b/guides/rails_guides/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "yaml" module RailsGuides diff --git a/guides/rails_guides/indexer.rb b/guides/rails_guides/indexer.rb index c58b6b85a2..c707464cdf 100644 --- a/guides/rails_guides/indexer.rb +++ b/guides/rails_guides/indexer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/object/blank" require "active_support/core_ext/string/inflections" diff --git a/guides/rails_guides/kindle.rb b/guides/rails_guides/kindle.rb index 9536d0bd3b..87a369a15a 100644 --- a/guides/rails_guides/kindle.rb +++ b/guides/rails_guides/kindle.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true require "kindlerb" require "nokogiri" diff --git a/guides/rails_guides/levenshtein.rb b/guides/rails_guides/levenshtein.rb index 40c6a5c372..bafa6bfe9d 100644 --- a/guides/rails_guides/levenshtein.rb +++ b/guides/rails_guides/levenshtein.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module RailsGuides module Levenshtein # This code is based directly on the Text gem implementation. diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb index 02d58601c4..84f95eec68 100644 --- a/guides/rails_guides/markdown.rb +++ b/guides/rails_guides/markdown.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "redcarpet" require "nokogiri" require "rails_guides/markdown/renderer" diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb index 7ac3d417a4..1f2fe91ea1 100644 --- a/guides/rails_guides/markdown/renderer.rb +++ b/guides/rails_guides/markdown/renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module RailsGuides class Markdown class Renderer < Redcarpet::Render::HTML diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md index 50a28571b4..31151e0329 100644 --- a/guides/source/action_cable_overview.md +++ b/guides/source/action_cable_overview.md @@ -64,7 +64,7 @@ module ApplicationCable private def find_verified_user - if verified_user = User.find_by(id: cookies.signed[:user_id]) + if verified_user = User.find_by(id: cookies.encrypted[:user_id]) verified_user else reject_unauthorized_connection diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 7751ac00df..6562dc3a98 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -64,7 +64,7 @@ Rails. Mailers are conceptually similar to controllers, and so we get a mailer, a directory for views, and a test. If you didn't want to use a generator, you could create your own file inside of -app/mailers, just make sure that it inherits from `ActionMailer::Base`: +`app/mailers`, just make sure that it inherits from `ActionMailer::Base`: ```ruby class MyMailer < ActionMailer::Base diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md index 443be77934..2606bfe280 100644 --- a/guides/source/active_job_basics.md +++ b/guides/source/active_job_basics.md @@ -260,40 +260,48 @@ backends you need to specify the queues to listen to. Callbacks --------- -Active Job provides hooks during the life cycle of a job. Callbacks allow you to -trigger logic during the life cycle of a job. - -### Available callbacks - -* `before_enqueue` -* `around_enqueue` -* `after_enqueue` -* `before_perform` -* `around_perform` -* `after_perform` - -### Usage +Active Job provides hooks to trigger logic during the life cycle of a job. Like +other callbacks in Rails, you can implement the callbacks as ordinary methods +and use a macro-style class method to register them as callbacks: ```ruby class GuestsCleanupJob < ApplicationJob queue_as :default - before_enqueue do |job| - # Do something with the job instance - end - - around_perform do |job, block| - # Do something before perform - block.call - # Do something after perform - end + around_perform :around_cleanup def perform # Do something later end + + private + def around_cleanup(job) + # Do something before perform + yield + # Do something after perform + end end ``` +The macro-style class methods can also receive a block. Consider using this +style if the code inside your block is so short that it fits in a single line. +For example, you could send metrics for every job enqueued: + +```ruby +class ApplicationJob + before_enqueue { |job| $statsd.increment "#{job.name.underscore}.enqueue" } +end +``` + +### Available callbacks + +* `before_enqueue` +* `around_enqueue` +* `after_enqueue` +* `before_perform` +* `around_perform` +* `after_perform` + Action Mailer ------------ diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index bead931529..1212ae53bc 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -811,6 +811,7 @@ When you declare a `belongs_to` association, the declaring class automatically g * `build_association(attributes = {})` * `create_association(attributes = {})` * `create_association!(attributes = {})` +* `reload_association` In all of these methods, `association` is replaced with the symbol passed as the first argument to `belongs_to`. For example, given the declaration: @@ -828,6 +829,7 @@ author= build_author create_author create_author! +reload_author ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. @@ -840,10 +842,10 @@ The `association` method returns the associated object, if any. If no associated @author = @book.author ``` -If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload` on the parent object. +If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload_association` on the parent object. ```ruby -@author = @book.reload.author +@author = @book.reload_author ``` ##### `association=(associate)` @@ -1161,6 +1163,7 @@ When you declare a `has_one` association, the declaring class automatically gain * `build_association(attributes = {})` * `create_association(attributes = {})` * `create_association!(attributes = {})` +* `reload_association` In all of these methods, `association` is replaced with the symbol passed as the first argument to `has_one`. For example, given the declaration: @@ -1178,6 +1181,7 @@ account= build_account create_account create_account! +reload_account ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. @@ -1190,10 +1194,10 @@ The `association` method returns the associated object, if any. If no associated @account = @supplier.account ``` -If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload` on the parent object. +If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload_association` on the parent object. ```ruby -@account = @supplier.reload.account +@account = @supplier.reload_account ``` ##### `association=(associate)` @@ -1443,6 +1447,7 @@ When you declare a `has_many` association, the declaring class automatically gai * `collection.build(attributes = {}, ...)` * `collection.create(attributes = {})` * `collection.create!(attributes = {})` +* `collection.reload` In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: @@ -1471,11 +1476,12 @@ books.exists?(...) books.build(attributes = {}, ...) books.create(attributes = {}) books.create!(attributes = {}) +books.reload ``` ##### `collection` -The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array. +The `collection` method returns a Relation of all of the associated objects. If there are no associated objects, it returns an empty Relation. ```ruby @books = @author.books @@ -1609,6 +1615,14 @@ The `collection.create` method returns a single or array of new objects of the a Does the same as `collection.create` above, but raises `ActiveRecord::RecordInvalid` if the record is invalid. +##### `collection.reload` + +The `collection.reload` method returns a Relation of all of the associated objects, forcing a database read. If there are no associated objects, it returns an empty Relation. + +```ruby +@books = @author.books.reload +``` + #### Options for `has_many` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: @@ -1965,6 +1979,7 @@ When you declare a `has_and_belongs_to_many` association, the declaring class au * `collection.build(attributes = {})` * `collection.create(attributes = {})` * `collection.create!(attributes = {})` +* `collection.reload` In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_and_belongs_to_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: @@ -1993,6 +2008,7 @@ assemblies.exists?(...) assemblies.build(attributes = {}, ...) assemblies.create(attributes = {}) assemblies.create!(attributes = {}) +assemblies.reload ``` ##### Additional Column Methods @@ -2004,7 +2020,7 @@ WARNING: The use of extra attributes on the join table in a `has_and_belongs_to_ ##### `collection` -The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array. +The `collection` method returns a Relation of all of the associated objects. If there are no associated objects, it returns an empty Relation. ```ruby @assemblies = @part.assemblies @@ -2116,6 +2132,14 @@ The `collection.create` method returns a new object of the associated type. This Does the same as `collection.create`, but raises `ActiveRecord::RecordInvalid` if the record is invalid. +##### `collection.reload` + +The `collection.reload` method returns a Relation of all of the associated objects, forcing a database read. If there are no associated objects, it returns an empty Relation. + +```ruby +@assemblies = @part.assemblies.reload +``` + #### Options for `has_and_belongs_to_many` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_and_belongs_to_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: diff --git a/guides/source/generators.md b/guides/source/generators.md index be1be75e7a..389224d908 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -627,7 +627,7 @@ This method also takes a block: ```ruby lib "super_special.rb" do - puts "Super special!" + "puts 'Super special!'" end ``` @@ -636,7 +636,7 @@ end Creates a Rake file in the `lib/tasks` directory of the application. ```ruby -rakefile "test.rake", "hello there" +rakefile "test.rake", 'task(:hello) { puts "Hello, there" }' ``` This method also takes a block: diff --git a/guides/source/testing.md b/guides/source/testing.md index f71e963716..1c648ac47f 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -614,7 +614,7 @@ $ bin/rails generate system_test users create test/system/users_test.rb ``` -Here's what a freshly-generated system test looks like: +Here's what a freshly generated system test looks like: ```ruby require "application_system_test_case" @@ -788,7 +788,7 @@ $ bin/rails generate integration_test user_flows create test/integration/user_flows_test.rb ``` -Here's what a freshly-generated integration test looks like: +Here's what a freshly generated integration test looks like: ```ruby require 'test_helper' diff --git a/guides/w3c_validator.rb b/guides/w3c_validator.rb index 4671e040ca..f38b6c2639 100644 --- a/guides/w3c_validator.rb +++ b/guides/w3c_validator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # --------------------------------------------------------------------------- # # This script validates the generated guides against the W3C Validator. diff --git a/rails.gemspec b/rails.gemspec index 31eee025bf..4b57377871 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -1,3 +1,5 @@ +# frozen_string_literal: true + version = File.read(File.expand_path("RAILS_VERSION", __dir__)).strip Gem::Specification.new do |s| diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 43210ead16..4ee30faabb 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,23 @@ +* Optimize indentation for generator actions. + + *Yoshiyuki Hirano* + +* Skip unused components when running `bin/rails` in Rails plugin. + + *Yoshiyuki Hirano* + +* Add git_source to `Gemfile` for plugin generator. + + *Yoshiyuki Hirano* + +* Add `--skip-action-cable` option to the plugin generator. + + *bogdanvlviv* + +* Deprecate support of use `Rails::Application` subclass to start Rails server. + + *Yuji Yaginuma* + * Add `ruby x.x.x` version to `Gemfile` and create `.ruby-version` root file containing the current Ruby version when new Rails applications are created. diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index ce258341f6..de69b4586b 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -2,6 +2,8 @@ require "fileutils" require "optparse" require "action_dispatch" require "rails" +require "active_support/deprecation" +require "active_support/core_ext/string/filters" require_relative "../../dev_caching" module Rails @@ -18,10 +20,15 @@ module Rails set_environment end - # TODO: this is no longer required but we keep it for the moment to support older config.ru files. def app @app ||= begin app = super + if app.is_a?(Class) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Use `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0. + Please change `run #{app}` to `run Rails.application` in config.ru. + MSG + end app.respond_to?(:to_app) ? app.to_app : app end end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index b9a0fe07c8..8915441d4c 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -10,6 +10,7 @@ require "active_support/core_ext/kernel/singleton_class" require "active_support/core_ext/array/extract_options" require "active_support/core_ext/hash/deep_merge" require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/string/indent" require "active_support/core_ext/string/inflections" module Rails diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 2792d7636f..9baf53c1d0 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -97,16 +97,16 @@ module Rails # "config.action_controller.asset_host = 'localhost:3000'" # end def environment(data = nil, options = {}) - sentinel = /class [a-z_:]+ < Rails::Application/i - env_file_sentinel = /Rails\.application\.configure do/ - data = yield if !data && block_given? + sentinel = "class Application < Rails::Application\n" + env_file_sentinel = "Rails.application.configure do\n" + data ||= yield if block_given? in_root do if options[:env].nil? - inject_into_file "config/application.rb", "\n #{data}", after: sentinel, verbose: false + inject_into_file "config/application.rb", optimize_indentation(data, 4), after: sentinel, verbose: false else Array(options[:env]).each do |env| - inject_into_file "config/environments/#{env}.rb", "\n #{data}", after: env_file_sentinel, verbose: false + inject_into_file "config/environments/#{env}.rb", optimize_indentation(data, 2), after: env_file_sentinel, verbose: false end end end @@ -137,9 +137,10 @@ module Rails # end # # vendor("foreign.rb", "# Foreign code is fun") - def vendor(filename, data = nil, &block) + def vendor(filename, data = nil) log :vendor, filename - create_file("vendor/#{filename}", data, verbose: false, &block) + data ||= yield if block_given? + create_file("vendor/#{filename}", optimize_indentation(data), verbose: false) end # Create a new file in the lib/ directory. Code can be specified @@ -150,9 +151,10 @@ module Rails # end # # lib("foreign.rb", "# Foreign code is fun") - def lib(filename, data = nil, &block) + def lib(filename, data = nil) log :lib, filename - create_file("lib/#{filename}", data, verbose: false, &block) + data ||= yield if block_given? + create_file("lib/#{filename}", optimize_indentation(data), verbose: false) end # Create a new +Rakefile+ with the provided code (either in a block or a string). @@ -170,9 +172,10 @@ module Rails # end # # rakefile('seed.rake', 'puts "Planting seeds"') - def rakefile(filename, data = nil, &block) + def rakefile(filename, data = nil) log :rakefile, filename - create_file("lib/tasks/#{filename}", data, verbose: false, &block) + data ||= yield if block_given? + create_file("lib/tasks/#{filename}", optimize_indentation(data), verbose: false) end # Create a new initializer with the provided code (either in a block or a string). @@ -188,9 +191,10 @@ module Rails # end # # initializer("api.rb", "API_KEY = '123456'") - def initializer(filename, data = nil, &block) + def initializer(filename, data = nil) log :initializer, filename - create_file("config/initializers/#{filename}", data, verbose: false, &block) + data ||= yield if block_given? + create_file("config/initializers/#{filename}", optimize_indentation(data), verbose: false) end # Generate something using a generator from Rails or a plugin. @@ -304,6 +308,17 @@ module Rails "'#{value}'" end end + + # Returns optimized string with indentation + def optimize_indentation(value, amount = 0) # :doc: + return "#{value}\n" unless value.is_a?(String) + + if value.lines.size > 1 + value.strip_heredoc.indent(amount) + else + "#{value.strip.indent(amount)}\n" + end + end end end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb index 51639b67a0..89d2efab2b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb @@ -1,6 +1,8 @@ # Be sure to restart your server when you modify this file. -# ApplicationController.renderer.defaults.merge!( -# http_host: 'example.org', -# https: false -# ) +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/railties/lib/rails/generators/rails/app/templates/config/storage.yml b/railties/lib/rails/generators/rails/app/templates/config/storage.yml index 1afe3eb4b3..089ed4567a 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/storage.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/storage.yml @@ -26,7 +26,7 @@ local: # service: AzureStorage # path: your_azure_storage_path # storage_account_name: your_account_name -# storage_access_key: <%%= Rails.application.secrets.dig(:azure_storage, :secret_access_key) %> +# storage_access_key: <%%= Rails.application.secrets.dig(:azure_storage, :storage_access_key) %> # container: your_container_name # mirror: diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 61c54b4222..a40fa78132 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -76,8 +76,8 @@ module Rails template "test/test_helper.rb" template "test/%namespaced_name%_test.rb" append_file "Rakefile", <<-EOF -#{rakefile_test_tasks} +#{rakefile_test_tasks} task default: :test EOF if engine? @@ -86,7 +86,7 @@ task default: :test end PASSTHROUGH_OPTIONS = [ - :skip_active_record, :skip_action_mailer, :skip_javascript, :skip_sprockets, :database, + :skip_active_record, :skip_action_mailer, :skip_javascript, :skip_action_cable, :skip_sprockets, :database, :javascript, :quiet, :pretend, :force, :skip ] diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile index 22a4548ff2..290259b4db 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile @@ -1,4 +1,5 @@ source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } <% if options[:skip_gemspec] -%> <%= '# ' if options.dev? || options.edge? -%>gem 'rails', '<%= Array(rails_version_specifier).join("', '") %>' diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile index 3581dd401a..f3efe21cf1 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile +++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile @@ -13,17 +13,16 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('README.md') rdoc.rdoc_files.include('lib/**/*.rb') end - <% if engine? && !options[:skip_active_record] && with_dummy_app? -%> + APP_RAKEFILE = File.expand_path("<%= dummy_path -%>/Rakefile", __dir__) load 'rails/tasks/engine.rake' -<% end %> - +<% end -%> <% if engine? -%> -load 'rails/tasks/statistics.rake' -<% end %> +load 'rails/tasks/statistics.rake' +<% end -%> <% unless options[:skip_gemspec] -%> require 'bundler/gem_tasks' -<% end %> +<% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt index ffa277e334..b3264509fc 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt @@ -3,11 +3,28 @@ ENGINE_ROOT = File.expand_path('..', __dir__) ENGINE_PATH = File.expand_path('../lib/<%= namespaced_name -%>/engine', __dir__) +<% if with_dummy_app? -%> APP_PATH = File.expand_path('../<%= dummy_path -%>/config/application', __dir__) +<% end -%> # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) +<% if include_all_railties? -%> require 'rails/all' +<% else -%> +require "rails" +# Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" +<%= comment_if :skip_active_record %>require "active_record/railtie" +require "action_controller/railtie" +<%= comment_if :skip_action_mailer %>require "action_mailer/railtie" +require "action_view/railtie" +require "active_storage/engine" +<%= comment_if :skip_action_cable %>require "action_cable/engine" +<%= comment_if :skip_sprockets %>require "sprockets/railtie" +<%= comment_if :skip_test %>require "rails/test_unit/railtie" +<% end -%> require 'rails/engine/commands' diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore b/railties/lib/rails/generators/rails/plugin/templates/gitignore index 54c78d7927..757172e6a6 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/gitignore +++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore @@ -1,7 +1,7 @@ .bundle/ log/*.log pkg/ -<% unless options[:skip_test] && options[:dummy_path] == 'test/dummy' -%> +<% if with_dummy_app? -%> <%= dummy_path %>/db/*.sqlite3 <%= dummy_path %>/db/*.sqlite3-journal <%= dummy_path %>/log/*.log diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb index 5e0cfd6c21..47b56ae3df 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb @@ -3,16 +3,18 @@ require_relative 'boot' <% if include_all_railties? -%> require 'rails/all' <% else -%> +require "rails" # Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" <%= comment_if :skip_active_record %>require "active_record/railtie" require "action_controller/railtie" -require "action_view/railtie" <%= comment_if :skip_action_mailer %>require "action_mailer/railtie" -require "active_job/railtie" +require "action_view/railtie" require "active_storage/engine" <%= comment_if :skip_action_cable %>require "action_cable/engine" -<%= comment_if :skip_test %>require "rails/test_unit/railtie" <%= comment_if :skip_sprockets %>require "sprockets/railtie" +<%= comment_if :skip_test %>require "rails/test_unit/railtie" <% end -%> Bundler.require(*Rails.groups) diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js b/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js index 8d21b2b6fb..03937cf8ff 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js @@ -1,4 +1,3 @@ - <% unless api? -%> //= link_tree ../images <% end -%> diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index c2f6a5a95c..482da98a45 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -238,6 +238,66 @@ module ApplicationTests assert_instance_of Pathname, Rails.public_path end + test "does not eager load controller actions in development" do + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ActionController::Base + def index;end + def show;end + end + RUBY + + app "development" + + assert_nil PostsController.instance_variable_get(:@action_methods) + end + + test "eager loads controller actions in production" do + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ActionController::Base + def index;end + def show;end + end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app "production" + + assert_equal %w(index show).to_set, PostsController.instance_variable_get(:@action_methods) + end + + test "does not eager load mailer actions in development" do + app_file "app/mailers/posts_mailer.rb", <<-RUBY + class PostsMailer < ActionMailer::Base + def noop_email;end + end + RUBY + + app "development" + + assert_nil PostsMailer.instance_variable_get(:@action_methods) + end + + test "eager loads mailer actions in production" do + app_file "app/mailers/posts_mailer.rb", <<-RUBY + class PostsMailer < ActionMailer::Base + def noop_email;end + end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app "production" + + assert_equal %w(noop_email).to_set, PostsMailer.instance_variable_get(:@action_methods) + end + test "initialize an eager loaded, cache classes app" do add_to_config <<-RUBY config.eager_load = true diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb new file mode 100644 index 0000000000..07880a5025 --- /dev/null +++ b/railties/test/application/server_test.rb @@ -0,0 +1,31 @@ +require "isolation/abstract_unit" +require "rails/command" +require "rails/commands/server/server_command" + +module ApplicationTests + class ServerTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "deprecate support of older `config.ru`" do + remove_file "config.ru" + app_file "config.ru", <<-RUBY + require_relative 'config/environment' + run AppTemplate::Application + RUBY + + server = Rails::Server.new(config: "#{app_path}/config.ru") + server.app + + log = File.read(Rails.application.config.paths["log"].first) + assert_match(/DEPRECATION WARNING: Use `Rails::Application` subclass to start the server is deprecated/, log) + end + end +end diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 03b29be907..d65ced55b8 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -139,14 +139,14 @@ class ActionsTest < Rails::Generators::TestCase run_generator autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]' action :environment, autoload_paths - assert_file "config/application.rb", / class Application < Rails::Application\n #{Regexp.escape(autoload_paths)}/ + assert_file "config/application.rb", / class Application < Rails::Application\n #{Regexp.escape(autoload_paths)}\n/ end def test_environment_should_include_data_in_environment_initializer_block_with_env_option run_generator autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]' action :environment, autoload_paths, env: "development" - assert_file "config/environments/development.rb", /Rails\.application\.configure do\n #{Regexp.escape(autoload_paths)}/ + assert_file "config/environments/development.rb", /Rails\.application\.configure do\n #{Regexp.escape(autoload_paths)}\n/ end def test_environment_with_block_should_include_block_contents_in_environment_initializer_block @@ -163,6 +163,26 @@ class ActionsTest < Rails::Generators::TestCase end end + def test_environment_with_block_should_include_block_contents_with_multiline_data_in_environment_initializer_block + run_generator + data = <<-RUBY + config.encoding = "utf-8" + config.time_zone = "UTC" + RUBY + action(:environment) { data } + assert_file "config/application.rb", / class Application < Rails::Application\n#{Regexp.escape(data.strip_heredoc.indent(4))}/ + end + + def test_environment_should_include_block_contents_with_multiline_data_in_environment_initializer_block_with_env_option + run_generator + data = <<-RUBY + config.encoding = "utf-8" + config.time_zone = "UTC" + RUBY + action(:environment, nil, env: "development") { data } + assert_file "config/environments/development.rb", /Rails\.application\.configure do\n#{Regexp.escape(data.strip_heredoc.indent(2))}/ + end + def test_git_with_symbol_should_run_command_using_git_scm assert_called_with(generator, :run, ["git init"]) do action :git, :init @@ -177,22 +197,62 @@ class ActionsTest < Rails::Generators::TestCase def test_vendor_should_write_data_to_file_in_vendor action :vendor, "vendor_file.rb", "# vendor data" - assert_file "vendor/vendor_file.rb", "# vendor data" + assert_file "vendor/vendor_file.rb", "# vendor data\n" + end + + def test_vendor_should_write_data_to_file_with_block_in_vendor + code = <<-RUBY + puts "one" + puts "two" + puts "three" + RUBY + action(:vendor, "vendor_file.rb") { code } + assert_file "vendor/vendor_file.rb", code.strip_heredoc end def test_lib_should_write_data_to_file_in_lib action :lib, "my_library.rb", "class MyLibrary" - assert_file "lib/my_library.rb", "class MyLibrary" + assert_file "lib/my_library.rb", "class MyLibrary\n" + end + + def test_lib_should_write_data_to_file_with_block_in_lib + code = <<-RUBY + class MyLib + MY_CONSTANT = 123 + end + RUBY + action(:lib, "my_library.rb") { code } + assert_file "lib/my_library.rb", code.strip_heredoc end def test_rakefile_should_write_date_to_file_in_lib_tasks action :rakefile, "myapp.rake", "task run: [:environment]" - assert_file "lib/tasks/myapp.rake", "task run: [:environment]" + assert_file "lib/tasks/myapp.rake", "task run: [:environment]\n" + end + + def test_rakefile_should_write_date_to_file_with_block_in_lib_tasks + code = <<-RUBY + task rock: :environment do + puts "Rockin'" + end + RUBY + action(:rakefile, "myapp.rake") { code } + assert_file "lib/tasks/myapp.rake", code.strip_heredoc end def test_initializer_should_write_date_to_file_in_config_initializers action :initializer, "constants.rb", "MY_CONSTANT = 42" - assert_file "config/initializers/constants.rb", "MY_CONSTANT = 42" + assert_file "config/initializers/constants.rb", "MY_CONSTANT = 42\n" + end + + def test_initializer_should_write_date_to_file_with_block_in_config_initializers + code = <<-RUBY + MyLib.configure do |config| + config.value = 123 + end + RUBY + action(:initializer, "constants.rb") { code } + assert_file "config/initializers/constants.rb", code.strip_heredoc end def test_generate_should_run_script_generate_with_argument_and_options diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 44c4688aa4..ff73014046 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -394,6 +394,16 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_default_frameworks_are_required_when_others_are_removed + run_generator [destination_root, "--skip-active-record", "--skip-action-mailer", "--skip-action-cable", "--skip-sprockets", "--skip-test"] + assert_file "config/application.rb", /require\s+["']rails["']/ + assert_file "config/application.rb", /require\s+["']active_model\/railtie["']/ + assert_file "config/application.rb", /require\s+["']active_job\/railtie["']/ + assert_file "config/application.rb", /require\s+["']action_controller\/railtie["']/ + assert_file "config/application.rb", /require\s+["']action_view\/railtie["']/ + assert_file "config/application.rb", /require\s+["']active_storage\/engine["']/ + end + def test_generator_defaults_to_puma_version run_generator [destination_root] assert_gem "puma", "'~> 3.7'" @@ -449,22 +459,26 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_generator_if_skip_sprockets_is_given run_generator [destination_root, "--skip-sprockets"] + assert_no_file "config/initializers/assets.rb" - assert_file "config/application.rb" do |content| - assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content) - end + + assert_file "config/application.rb", /#\s+require\s+["']sprockets\/railtie["']/ + assert_file "Gemfile" do |content| assert_no_match(/sass-rails/, content) assert_no_match(/uglifier/, content) assert_no_match(/coffee-rails/, content) end + assert_file "config/environments/development.rb" do |content| - assert_no_match(/config\.assets\.debug = true/, content) + assert_no_match(/config\.assets\.debug/, content) end + assert_file "config/environments/production.rb" do |content| - assert_no_match(/config\.assets\.digest = true/, content) - assert_no_match(/config\.assets\.js_compressor = :uglifier/, content) - assert_no_match(/config\.assets\.css_compressor = :sass/, content) + assert_no_match(/config\.assets\.digest/, content) + assert_no_match(/config\.assets\.js_compressor/, content) + assert_no_match(/config\.assets\.css_compressor/, content) + assert_no_match(/config\.assets\.compile/, content) end end @@ -473,7 +487,8 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/ assert_no_file "config/cable.yml" assert_no_file "app/assets/javascripts/cable.js" - assert_no_file "app/channels" + assert_no_directory "app/assets/javascripts/channels" + assert_no_directory "app/channels" assert_file "Gemfile" do |content| assert_no_match(/redis/, content) end @@ -486,10 +501,15 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_generator_if_skip_test_is_given run_generator [destination_root, "--skip-test"] + + assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ + assert_file "Gemfile" do |content| assert_no_match(/capybara/, content) assert_no_match(/selenium-webdriver/, content) end + + assert_no_directory("test") end def test_generator_if_skip_system_test_is_given @@ -498,6 +518,10 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_match(/capybara/, content) assert_no_match(/selenium-webdriver/, content) end + + assert_directory("test") + + assert_no_directory("test/system") end def test_does_not_generate_system_test_files_if_skip_system_test_is_given @@ -570,6 +594,11 @@ class AppGeneratorTest < Rails::Generators::TestCase run_generator([destination_root]) assert_file "package.json", /dependencies/ assert_file "config/initializers/assets.rb", /node_modules/ + + assert_file ".gitignore" do |content| + assert_match(/node_modules/, content) + assert_match(/yarn-error\.log/, content) + end end def test_generator_for_yarn_skipped @@ -654,18 +683,6 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "lib/test_file.rb", "heres test data" end - def test_tests_are_removed_from_frameworks_if_skip_test_is_given - run_generator [destination_root, "--skip-test"] - assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ - end - - def test_no_active_record_or_tests_if_skips_given - run_generator [destination_root, "--skip-test", "--skip-active-record"] - assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ - assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ - assert_file "config/application.rb", /\s+require\s+["']active_job\/railtie["']/ - end - def test_pretend_option output = run_generator [File.join(destination_root, "myapp"), "--pretend"] assert_no_match(/run bundle install/, output) @@ -896,18 +913,6 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_directory("test/system") end - def test_system_tests_are_not_generated_on_system_test_skip - run_generator [destination_root, "--skip-system-test"] - - assert_no_directory("test/system") - end - - def test_system_tests_are_not_generated_on_test_skip - run_generator [destination_root, "--skip-test"] - - assert_no_directory("test/system") - end - private def stub_rails_application(root) Rails.application.config.root = root diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 1dfff38543..01a9168f38 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -75,6 +75,18 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_no_file "bin/rails" end + def test_generating_in_full_mode_with_almost_of_all_skip_options + run_generator [destination_root, "--full", "-M", "-O", "-C", "-S", "-T"] + assert_file "bin/rails" do |content| + assert_no_match(/\s+require\s+["']rails\/all["']/, content) + end + assert_file "bin/rails", /#\s+require\s+["']active_record\/railtie["']/ + assert_file "bin/rails", /#\s+require\s+["']action_mailer\/railtie["']/ + assert_file "bin/rails", /#\s+require\s+["']action_cable\/engine["']/ + assert_file "bin/rails", /#\s+require\s+["']sprockets\/railtie["']/ + assert_file "bin/rails", /#\s+require\s+["']rails\/test_unit\/railtie["']/ + end + def test_generating_test_files_in_full_mode run_generator [destination_root, "--full"] assert_directory "test/integration/" @@ -82,6 +94,11 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/ end + def test_inclusion_of_git_source + run_generator [destination_root] + assert_file "Gemfile", /git_source/ + end + def test_inclusion_of_a_debugger run_generator [destination_root, "--full"] if defined?(JRUBY_VERSION) || RUBY_ENGINE == "rbx" @@ -97,23 +114,44 @@ class PluginGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "-T", "--full"] assert_no_directory "test/integration/" - assert_no_file "test" + assert_no_directory "test" assert_file "Rakefile" do |contents| assert_no_match(/APP_RAKEFILE/, contents) end + assert_file "bin/rails" do |contents| + assert_no_match(/APP_PATH/, contents) + end end - def test_generating_adds_dummy_app_in_full_mode_without_sprockets - run_generator [destination_root, "-S", "--full"] + def test_generating_adds_dummy_app_without_sprockets + run_generator [destination_root, "--skip-sprockets"] + + assert_no_file "test/dummy/config/initializers/assets.rb" + + assert_file "test/dummy/config/application.rb", /#\s+require\s+["']sprockets\/railtie["']/ + + assert_file "Gemfile" do |content| + assert_no_match(/sass-rails/, content) + assert_no_match(/uglifier/, content) + assert_no_match(/coffee-rails/, content) + end + + assert_file "test/dummy/config/environments/development.rb" do |content| + assert_no_match(/config\.assets\.debug/, content) + end - assert_file "test/dummy/config/environments/production.rb" do |contents| - assert_no_match(/config\.assets/, contents) + assert_file "test/dummy/config/environments/production.rb" do |content| + assert_no_match(/config\.assets\.digest/, content) + assert_no_match(/config\.assets\.js_compressor/, content) + assert_no_match(/config\.assets\.css_compressor/, content) + assert_no_match(/config\.assets\.compile/, content) end end def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files run_generator [destination_root, "-T", "--mountable", "--dummy-path", "my_dummy_app"] assert_file "Rakefile", /APP_RAKEFILE/ + assert_file "bin/rails", /APP_PATH/ end def test_generating_adds_dummy_app_without_javascript_and_assets_deps @@ -134,8 +172,8 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_ensure_that_test_dummy_can_be_generated_from_a_template FileUtils.cd(Rails.root) run_generator([destination_root, "-m", "lib/create_test_dummy_template.rb", "--skip-test"]) - assert_file "spec/dummy" - assert_no_file "test" + assert_directory "spec/dummy" + assert_no_directory "test" end def test_database_entry_is_generated_for_sqlite3_by_default_in_full_mode @@ -169,9 +207,19 @@ class PluginGeneratorTest < Rails::Generators::TestCase end assert_file "test/dummy/config/environments/production.rb" do |content| assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content) + assert_match(/^ config\.read_encrypted_secrets = true/, content) end end + def test_default_frameworks_are_required_when_others_are_removed + run_generator [destination_root, "--skip-active-record", "--skip-action-mailer", "--skip-action-cable", "--skip-sprockets"] + assert_file "test/dummy/config/application.rb", /require\s+["']rails["']/ + assert_file "test/dummy/config/application.rb", /require\s+["']active_model\/railtie["']/ + assert_file "test/dummy/config/application.rb", /require\s+["']active_job\/railtie["']/ + assert_file "test/dummy/config/application.rb", /require\s+["']action_controller\/railtie["']/ + assert_file "test/dummy/config/application.rb", /require\s+["']action_view\/railtie["']/ + end + def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_file "test/dummy/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ @@ -197,6 +245,18 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "test/dummy/config/environments/production.rb" do |content| assert_no_match(/config\.action_mailer/, content) end + assert_no_directory "test/dummy/app/mailers" + end + + def test_action_cable_is_removed_from_frameworks_if_skip_action_cable_is_given + run_generator [destination_root, "--skip-action-cable"] + assert_file "test/dummy/config/application.rb", /#\s+require\s+["']action_cable\/engine["']/ + assert_no_file "test/dummy/config/cable.yml" + assert_no_file "test/dummy/app/assets/javascripts/cable.js" + assert_no_directory "test/dummy/app/channels" + assert_file "Gemfile" do |content| + assert_no_match(/redis/, content) + end end def test_ensure_that_database_option_is_passed_to_app_generator @@ -277,7 +337,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "app/views" assert_file "app/helpers" assert_file "app/mailers" - assert_file "bin/rails" + assert_file "bin/rails", /\s+require\s+["']rails\/all["']/ assert_file "config/routes.rb", /Rails.application.routes.draw do/ assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < ::Rails::Engine\n end\nend/ assert_file "lib/bukkits.rb", /require "bukkits\/engine"/ @@ -459,10 +519,9 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_creating_dummy_without_tests_but_with_dummy_path run_generator [destination_root, "--dummy_path", "spec/dummy", "--skip-test"] - assert_file "spec/dummy" - assert_file "spec/dummy/config/application.rb" - assert_no_file "test" - assert_no_file "test/test_helper.rb" + assert_directory "spec/dummy" + assert_file "spec/dummy/config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ + assert_no_directory "test" assert_file ".gitignore" do |contents| assert_match(/spec\/dummy/, contents) end @@ -499,9 +558,9 @@ class PluginGeneratorTest < Rails::Generators::TestCase def test_skipping_test_files run_generator [destination_root, "--skip-test"] - assert_no_file "test" + assert_no_directory "test" assert_file ".gitignore" do |contents| - assert_no_match(/test\dummy/, contents) + assert_no_match(/test\/dummy/, contents) end end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 7496b5f84a..7fa523f58d 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -313,8 +313,6 @@ class ActiveSupport::TestCase include TestHelpers::Rack include TestHelpers::Generation include ActiveSupport::Testing::Stream - - self.test_order = :sorted end # Create a scope and build a fixture rails app diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 145c2ec7b6..ea942a3975 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -136,7 +136,7 @@ module RailtiesTest output = `bundle exec rake railties:install:migrations`.split("\n") assert_match(/Copied migration \d+_create_users\.bukkits\.rb from bukkits/, output.first) - assert_match(/Copied migration \d+_create_blogs\.blog_engine\.rb from blog_engine/, output.last) + assert_match(/Copied migration \d+_create_blogs\.blog_engine\.rb from blog_engine/, output.second) end end @@ -171,7 +171,7 @@ module RailtiesTest Dir.chdir(app_path) do output = `bundle exec rake railties:install:migrations`.split("\n") - assert_match(/Copied migration \d+_create_users\.core_engine\.rb from core_engine/, output.first) + assert_match(/Copied migration \d+_create_users\.core_engine\.rb from core_engine/, output.second) assert_match(/Copied migration \d+_create_keys\.api_engine\.rb from api_engine/, output.last) end end diff --git a/tasks/release.rb b/tasks/release.rb index 6ff37fb415..aa8ba44c1a 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack activejob actionmailer actioncable activestorage railties ) FRAMEWORK_NAMES = Hash.new { |h, k| k.split(/(?<=active|action)/).map(&:capitalize).join(" ") } @@ -72,9 +74,9 @@ npm_version = version.gsub(/\./).with_index { |s, i| i >= 2 ? "-" : s } task gem => %w(update_versions pkg) do cmd = "" - cmd << "cd #{framework} && " unless framework == "rails" - cmd << "bundle exec rake package && " unless framework == "rails" - cmd << "gem build #{gemspec} && mv #{framework}-#{version}.gem #{root}/pkg/" + cmd += "cd #{framework} && " unless framework == "rails" + cmd += "bundle exec rake package && " unless framework == "rails" + cmd += "gem build #{gemspec} && mv #{framework}-#{version}.gem #{root}/pkg/" sh cmd end @@ -104,7 +106,7 @@ namespace :changelog do current_contents = File.read(fname) header = "## Rails #{version} (#{Date.today.strftime('%B %d, %Y')}) ##\n\n" - header << "* No changes.\n\n\n" if current_contents =~ /\A##/ + header += "* No changes.\n\n\n" if current_contents =~ /\A##/ contents = header + current_contents File.open(fname, "wb") { |f| f.write contents } end diff --git a/tools/console b/tools/console index 58c76fb1b6..ee08e22502 100755 --- a/tools/console +++ b/tools/console @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + require "bundler" Bundler.setup diff --git a/tools/profile b/tools/profile index 01e513c67a..6fb571f43b 100755 --- a/tools/profile +++ b/tools/profile @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + # Profile require calls giving information about the time and the files that are called # when loading the provided file. # diff --git a/tools/test.rb b/tools/test.rb index 774e4ec6c7..1fd3ee30eb 100644 --- a/tools/test.rb +++ b/tools/test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $: << File.expand_path("test", COMPONENT_ROOT) require "bundler" diff --git a/version.rb b/version.rb index 7bacf2e0ba..92b5e0392a 100644 --- a/version.rb +++ b/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rails # Returns the version of the currently loaded Rails as a <tt>Gem::Version</tt> def self.gem_version |