diff options
-rw-r--r-- | actionview/lib/action_view/template.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb | 17 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb | 26 | ||||
-rw-r--r-- | activerecord/lib/active_record/fixture_set/render_context.rb | 17 | ||||
-rw-r--r-- | activerecord/lib/active_record/fixtures.rb | 216 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation.rb | 38 | ||||
-rw-r--r-- | activerecord/lib/active_record/test_fixtures.rb | 201 | ||||
-rw-r--r-- | activerecord/lib/arel/visitors/mysql.rb | 48 | ||||
-rw-r--r-- | activerecord/lib/arel/visitors/to_sql.rb | 69 | ||||
-rw-r--r-- | activesupport/lib/active_support/configurable.rb | 2 | ||||
-rw-r--r-- | activesupport/test/broadcast_logger_test.rb | 2 | ||||
-rw-r--r-- | guides/source/active_support_core_extensions.md | 32 |
12 files changed, 331 insertions, 339 deletions
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index d7aee18a84..070d82cf17 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -235,7 +235,7 @@ module ActionView end end - + # Exceptions are marshalled when using the parallel test runner with DRb, so we need # to ensure that references to the template object can be marshalled as well. This means forgoing # the marshalling of the compiler mutex and instantiating that again on unmarshalling. diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index feacdf6931..0059f0b773 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -410,16 +410,6 @@ module ActiveRecord end end - # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work - # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in - # an UPDATE statement, so in the MySQL adapters we redefine this to do that. - def join_to_update(update, select, key) # :nodoc: - subselect = subquery_for(key, select) - - update.where key.in(subselect) - end - alias join_to_delete join_to_update - private def default_insert_value(column) Arel.sql("DEFAULT") @@ -460,13 +450,6 @@ module ActiveRecord total_sql.join(";\n") end - # Returns a subquery for the given key using the join information. - def subquery_for(key, select) - subselect = select.clone - subselect.projections = [key] - subselect - end - # Returns an ActiveRecord::Result instance. def select(sql, name = nil, binds = []) exec_query(sql, name, binds, prepare: false) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 09242a0f14..d40f38fb77 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -223,18 +223,6 @@ module ActiveRecord execute "ROLLBACK" end - # In the simple case, MySQL allows us to place JOINs directly into the UPDATE - # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support - # these, we must use a subquery. - def join_to_update(update, select, key) # :nodoc: - if select.limit || select.offset || select.orders.any? - super - else - update.table select.source - update.wheres = select.constraints - end - end - def empty_insert_statement_value(primary_key = nil) "VALUES ()" end @@ -733,20 +721,6 @@ module ActiveRecord [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)] end - # MySQL is too stupid to create a temporary table for use subquery, so we have - # to give it some prompting in the form of a subsubquery. Ugh! - def subquery_for(key, select) - subselect = select.clone - subselect.projections = [key] - - # Materialize subquery by adding distinct - # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' - subselect.distinct unless select.limit || select.offset || select.orders.any? - - key_name = quote_column_name(key.name) - Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name)) - end - def supports_rename_index? mariadb? ? false : version >= "5.7.6" end diff --git a/activerecord/lib/active_record/fixture_set/render_context.rb b/activerecord/lib/active_record/fixture_set/render_context.rb new file mode 100644 index 0000000000..c90b5343dc --- /dev/null +++ b/activerecord/lib/active_record/fixture_set/render_context.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# NOTE: This class has to be defined in compact style in +# order for rendering context subclassing to work correctly. +class ActiveRecord::FixtureSet::RenderContext # :nodoc: + def self.create_subclass + Class.new(ActiveRecord::FixtureSet.context_class) do + def get_binding + binding() + end + + def binary(path) + %(!!binary "#{Base64.strict_encode64(File.read(path))}") + end + end + end +end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 0d1fdcfb28..e697c30bb6 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -7,6 +7,8 @@ require "set" require "active_support/dependencies" require "active_support/core_ext/digest/uuid" require "active_record/fixture_set/file" +require "active_record/fixture_set/render_context" +require "active_record/test_fixtures" require "active_record/errors" module ActiveRecord @@ -851,217 +853,3 @@ module ActiveRecord end end end - -module ActiveRecord - module TestFixtures - extend ActiveSupport::Concern - - def before_setup # :nodoc: - setup_fixtures - super - end - - def after_teardown # :nodoc: - super - teardown_fixtures - end - - included do - class_attribute :fixture_path, instance_writer: false - class_attribute :fixture_table_names, default: [] - class_attribute :fixture_class_names, default: {} - class_attribute :use_transactional_tests, default: true - class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances - class_attribute :pre_loaded_fixtures, default: false - class_attribute :config, default: ActiveRecord::Base - class_attribute :lock_threads, default: true - end - - module ClassMethods - # Sets the model class for a fixture when the class name cannot be inferred from the fixture name. - # - # Examples: - # - # set_fixture_class some_fixture: SomeModel, - # 'namespaced/fixture' => Another::Model - # - # The keys must be the fixture names, that coincide with the short paths to the fixture files. - def set_fixture_class(class_names = {}) - self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys) - end - - def fixtures(*fixture_set_names) - if fixture_set_names.first == :all - raise StandardError, "No fixture path found. Please set `#{self}.fixture_path`." if fixture_path.blank? - fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq - fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] } - else - fixture_set_names = fixture_set_names.flatten.map(&:to_s) - end - - self.fixture_table_names |= fixture_set_names - setup_fixture_accessors(fixture_set_names) - end - - def setup_fixture_accessors(fixture_set_names = nil) - fixture_set_names = Array(fixture_set_names || fixture_table_names) - methods = Module.new do - fixture_set_names.each do |fs_name| - fs_name = fs_name.to_s - accessor_name = fs_name.tr("/", "_").to_sym - - define_method(accessor_name) do |*fixture_names| - force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload - return_single_record = fixture_names.size == 1 - fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty? - - @fixture_cache[fs_name] ||= {} - - instances = fixture_names.map do |f_name| - f_name = f_name.to_s if f_name.is_a?(Symbol) - @fixture_cache[fs_name].delete(f_name) if force_reload - - if @loaded_fixtures[fs_name][f_name] - @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find - else - raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'" - end - end - - return_single_record ? instances.first : instances - end - private accessor_name - end - end - include methods - end - - def uses_transaction(*methods) - @uses_transaction = [] unless defined?(@uses_transaction) - @uses_transaction.concat methods.map(&:to_s) - end - - def uses_transaction?(method) - @uses_transaction = [] unless defined?(@uses_transaction) - @uses_transaction.include?(method.to_s) - end - end - - def run_in_transaction? - use_transactional_tests && - !self.class.uses_transaction?(method_name) - end - - def setup_fixtures(config = ActiveRecord::Base) - if pre_loaded_fixtures && !use_transactional_tests - raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests" - end - - @fixture_cache = {} - @fixture_connections = [] - @@already_loaded_fixtures ||= {} - @connection_subscriber = nil - - # Load fixtures once and begin transaction. - if run_in_transaction? - if @@already_loaded_fixtures[self.class] - @loaded_fixtures = @@already_loaded_fixtures[self.class] - else - @loaded_fixtures = load_fixtures(config) - @@already_loaded_fixtures[self.class] = @loaded_fixtures - end - - # Begin transactions for connections already established - @fixture_connections = enlist_fixture_connections - @fixture_connections.each do |connection| - connection.begin_transaction joinable: false - connection.pool.lock_thread = true if lock_threads - end - - # When connections are established in the future, begin a transaction too - @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload| - spec_name = payload[:spec_name] if payload.key?(:spec_name) - - if spec_name - begin - connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name) - rescue ConnectionNotEstablished - connection = nil - end - - if connection && !@fixture_connections.include?(connection) - connection.begin_transaction joinable: false - connection.pool.lock_thread = true if lock_threads - @fixture_connections << connection - end - end - end - - # Load fixtures for every test. - else - ActiveRecord::FixtureSet.reset_cache - @@already_loaded_fixtures[self.class] = nil - @loaded_fixtures = load_fixtures(config) - end - - # Instantiate fixtures for every test if requested. - instantiate_fixtures if use_instantiated_fixtures - end - - def teardown_fixtures - # Rollback changes if a transaction is active. - if run_in_transaction? - ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber - @fixture_connections.each do |connection| - connection.rollback_transaction if connection.transaction_open? - connection.pool.lock_thread = false - end - @fixture_connections.clear - else - ActiveRecord::FixtureSet.reset_cache - end - - ActiveRecord::Base.clear_active_connections! - end - - def enlist_fixture_connections - ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection) - end - - private - def load_fixtures(config) - fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config) - Hash[fixtures.map { |f| [f.name, f] }] - end - - def instantiate_fixtures - if pre_loaded_fixtures - raise RuntimeError, "Load fixtures before instantiating them." if ActiveRecord::FixtureSet.all_loaded_fixtures.empty? - ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?) - else - raise RuntimeError, "Load fixtures before instantiating them." if @loaded_fixtures.nil? - @loaded_fixtures.each_value do |fixture_set| - ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?) - end - end - end - - def load_instances? - use_instantiated_fixtures != :no_instances - end - end -end - -class ActiveRecord::FixtureSet::RenderContext # :nodoc: - def self.create_subclass - Class.new ActiveRecord::FixtureSet.context_class do - def get_binding - binding() - end - - def binary(path) - %(!!binary "#{Base64.strict_encode64(File.read(path))}") - end - end - end -end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d8cff30b88..d5b6082d13 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -348,7 +348,12 @@ module ActiveRecord end stmt = Arel::UpdateManager.new - stmt.table(table) + stmt.table(arel.join_sources.empty? ? table : arel.source) + stmt.key = arel_attribute(primary_key) + stmt.take(arel.limit) + stmt.offset(arel.offset) + stmt.order(*arel.orders) + stmt.wheres = arel.constraints if updates.is_a?(Hash) stmt.set _substitute_values(updates) @@ -356,16 +361,6 @@ module ActiveRecord stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name)) end - if has_join_values? - @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key)) - else - stmt.key = arel_attribute(primary_key) - stmt.take(arel.limit) - stmt.offset(arel.offset) - stmt.order(*arel.orders) - stmt.wheres = arel.constraints - end - @klass.connection.update stmt, "#{@klass} Update All" end @@ -483,17 +478,12 @@ module ActiveRecord end stmt = Arel::DeleteManager.new - stmt.from(table) - - if has_join_values? - @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key)) - else - stmt.key = arel_attribute(primary_key) - stmt.take(arel.limit) - stmt.offset(arel.offset) - stmt.order(*arel.orders) - stmt.wheres = arel.constraints - end + stmt.from(arel.join_sources.empty? ? table : arel.source) + stmt.key = arel_attribute(primary_key) + stmt.take(arel.limit) + stmt.offset(arel.offset) + stmt.order(*arel.orders) + stmt.wheres = arel.constraints affected = @klass.connection.delete(stmt, "#{@klass} Destroy") @@ -648,10 +638,6 @@ module ActiveRecord end end - def has_join_values? - joins_values.any? || left_outer_joins_values.any? - end - def exec_queries(&block) skip_query_cache_if_necessary do @records = diff --git a/activerecord/lib/active_record/test_fixtures.rb b/activerecord/lib/active_record/test_fixtures.rb new file mode 100644 index 0000000000..7b7b3f7112 --- /dev/null +++ b/activerecord/lib/active_record/test_fixtures.rb @@ -0,0 +1,201 @@ +# frozen_string_literal: true + +module ActiveRecord + module TestFixtures + extend ActiveSupport::Concern + + def before_setup # :nodoc: + setup_fixtures + super + end + + def after_teardown # :nodoc: + super + teardown_fixtures + end + + included do + class_attribute :fixture_path, instance_writer: false + class_attribute :fixture_table_names, default: [] + class_attribute :fixture_class_names, default: {} + class_attribute :use_transactional_tests, default: true + class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances + class_attribute :pre_loaded_fixtures, default: false + class_attribute :config, default: ActiveRecord::Base + class_attribute :lock_threads, default: true + end + + module ClassMethods + # Sets the model class for a fixture when the class name cannot be inferred from the fixture name. + # + # Examples: + # + # set_fixture_class some_fixture: SomeModel, + # 'namespaced/fixture' => Another::Model + # + # The keys must be the fixture names, that coincide with the short paths to the fixture files. + def set_fixture_class(class_names = {}) + self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys) + end + + def fixtures(*fixture_set_names) + if fixture_set_names.first == :all + raise StandardError, "No fixture path found. Please set `#{self}.fixture_path`." if fixture_path.blank? + fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq + fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] } + else + fixture_set_names = fixture_set_names.flatten.map(&:to_s) + end + + self.fixture_table_names |= fixture_set_names + setup_fixture_accessors(fixture_set_names) + end + + def setup_fixture_accessors(fixture_set_names = nil) + fixture_set_names = Array(fixture_set_names || fixture_table_names) + methods = Module.new do + fixture_set_names.each do |fs_name| + fs_name = fs_name.to_s + accessor_name = fs_name.tr("/", "_").to_sym + + define_method(accessor_name) do |*fixture_names| + force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload + return_single_record = fixture_names.size == 1 + fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty? + + @fixture_cache[fs_name] ||= {} + + instances = fixture_names.map do |f_name| + f_name = f_name.to_s if f_name.is_a?(Symbol) + @fixture_cache[fs_name].delete(f_name) if force_reload + + if @loaded_fixtures[fs_name][f_name] + @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find + else + raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'" + end + end + + return_single_record ? instances.first : instances + end + private accessor_name + end + end + include methods + end + + def uses_transaction(*methods) + @uses_transaction = [] unless defined?(@uses_transaction) + @uses_transaction.concat methods.map(&:to_s) + end + + def uses_transaction?(method) + @uses_transaction = [] unless defined?(@uses_transaction) + @uses_transaction.include?(method.to_s) + end + end + + def run_in_transaction? + use_transactional_tests && + !self.class.uses_transaction?(method_name) + end + + def setup_fixtures(config = ActiveRecord::Base) + if pre_loaded_fixtures && !use_transactional_tests + raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests" + end + + @fixture_cache = {} + @fixture_connections = [] + @@already_loaded_fixtures ||= {} + @connection_subscriber = nil + + # Load fixtures once and begin transaction. + if run_in_transaction? + if @@already_loaded_fixtures[self.class] + @loaded_fixtures = @@already_loaded_fixtures[self.class] + else + @loaded_fixtures = load_fixtures(config) + @@already_loaded_fixtures[self.class] = @loaded_fixtures + end + + # Begin transactions for connections already established + @fixture_connections = enlist_fixture_connections + @fixture_connections.each do |connection| + connection.begin_transaction joinable: false + connection.pool.lock_thread = true if lock_threads + end + + # When connections are established in the future, begin a transaction too + @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload| + spec_name = payload[:spec_name] if payload.key?(:spec_name) + + if spec_name + begin + connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name) + rescue ConnectionNotEstablished + connection = nil + end + + if connection && !@fixture_connections.include?(connection) + connection.begin_transaction joinable: false + connection.pool.lock_thread = true if lock_threads + @fixture_connections << connection + end + end + end + + # Load fixtures for every test. + else + ActiveRecord::FixtureSet.reset_cache + @@already_loaded_fixtures[self.class] = nil + @loaded_fixtures = load_fixtures(config) + end + + # Instantiate fixtures for every test if requested. + instantiate_fixtures if use_instantiated_fixtures + end + + def teardown_fixtures + # Rollback changes if a transaction is active. + if run_in_transaction? + ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber + @fixture_connections.each do |connection| + connection.rollback_transaction if connection.transaction_open? + connection.pool.lock_thread = false + end + @fixture_connections.clear + else + ActiveRecord::FixtureSet.reset_cache + end + + ActiveRecord::Base.clear_active_connections! + end + + def enlist_fixture_connections + ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection) + end + + private + def load_fixtures(config) + fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config) + Hash[fixtures.map { |f| [f.name, f] }] + end + + def instantiate_fixtures + if pre_loaded_fixtures + raise RuntimeError, "Load fixtures before instantiating them." if ActiveRecord::FixtureSet.all_loaded_fixtures.empty? + ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?) + else + raise RuntimeError, "Load fixtures before instantiating them." if @loaded_fixtures.nil? + @loaded_fixtures.each_value do |fixture_set| + ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?) + end + end + end + + def load_instances? + use_instantiated_fixtures != :no_instances + end + end +end diff --git a/activerecord/lib/arel/visitors/mysql.rb b/activerecord/lib/arel/visitors/mysql.rb index eb8a449079..081452caeb 100644 --- a/activerecord/lib/arel/visitors/mysql.rb +++ b/activerecord/lib/arel/visitors/mysql.rb @@ -65,12 +65,42 @@ module Arel # :nodoc: all collector end + # In the simple case, MySQL allows us to place JOINs directly into the UPDATE + # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support + # these, we must use a subquery. + def prepare_update_statement(o) + if has_join_sources?(o) + if has_limit_or_offset_or_orders?(o) + super + else + o + end + elsif o.offset + super + else + o + end + end + + def prepare_delete_statement(o) + if has_join_sources?(o) || o.offset + super + else + o + end + end + + # MySQL is too stupid to create a temporary table for use subquery, so we have + # to give it some prompting in the form of a subsubquery. def build_subselect(key, o) subselect = super # Materialize subquery by adding distinct # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' - subselect.distinct unless subselect.limit || subselect.offset || subselect.orders.any? + unless has_limit_or_offset_or_orders?(subselect) + core = subselect.cores.last + core.set_quantifier = Arel::Nodes::Distinct.new + end Nodes::SelectStatement.new.tap do |stmt| core = stmt.cores.last @@ -78,22 +108,6 @@ module Arel # :nodoc: all core.projections = [Arel.sql(quote_column_name(key.name))] end end - - def collect_where_for(o, collector) - return super if o.offset - - unless o.wheres.empty? - collector << " WHERE " - collector = inject_join o.wheres, collector, " AND " - end - - unless o.orders.empty? - collector << " ORDER BY " - collector = inject_join o.orders, collector, ", " - end - - maybe_visit o.limit, collector - end end end end diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb index 0172204fc8..7c0f6c2e97 100644 --- a/activerecord/lib/arel/visitors/to_sql.rb +++ b/activerecord/lib/arel/visitors/to_sql.rb @@ -74,26 +74,17 @@ module Arel # :nodoc: all private def visit_Arel_Nodes_DeleteStatement(o, collector) + o = prepare_delete_statement(o) + collector << "DELETE FROM " collector = visit o.relation, collector collect_where_for(o, collector) end - # FIXME: we should probably have a 2-pass visitor for this - def build_subselect(key, o) - stmt = Nodes::SelectStatement.new - core = stmt.cores.first - core.froms = o.relation - core.wheres = o.wheres - core.projections = [key] - stmt.limit = o.limit - stmt.offset = o.offset - stmt.orders = o.orders - stmt - end - def visit_Arel_Nodes_UpdateStatement(o, collector) + o = prepare_update_statement(o) + collector << "UPDATE " collector = visit o.relation, collector unless o.values.empty? @@ -800,19 +791,57 @@ module Arel # :nodoc: all } end - def collect_where_for(o, collector) - if o.orders.empty? && o.limit.nil? && o.offset.nil? - wheres = o.wheres + def has_join_sources?(o) + o.relation.is_a?(Nodes::JoinSource) && !o.relation.right.empty? + end + + def has_limit_or_offset_or_orders?(o) + o.limit || o.offset || !o.orders.empty? + end + + # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work + # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in + # an UPDATE statement, so in the MySQL visitor we redefine this to do that. + def prepare_update_statement(o) + if o.key && (has_limit_or_offset_or_orders?(o) || has_join_sources?(o)) + stmt = o.clone + stmt.limit = nil + stmt.offset = nil + stmt.orders = [] + stmt.wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])] + stmt.relation = o.relation.left if has_join_sources?(o) + stmt else - wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])] + o end + end + alias :prepare_delete_statement :prepare_update_statement + + # FIXME: we should probably have a 2-pass visitor for this + def build_subselect(key, o) + stmt = Nodes::SelectStatement.new + core = stmt.cores.first + core.froms = o.relation + core.wheres = o.wheres + core.projections = [key] + stmt.limit = o.limit + stmt.offset = o.offset + stmt.orders = o.orders + stmt + end - unless wheres.empty? + def collect_where_for(o, collector) + unless o.wheres.empty? collector << " WHERE " - collector = inject_join wheres, collector, " AND " + collector = inject_join o.wheres, collector, " AND " end - collector + unless o.orders.empty? + collector << " ORDER BY " + collector = inject_join o.orders, collector, ", " + end + + maybe_visit o.limit, collector end def infix_value(o, collector, value) diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index 2610114d8f..6159e45230 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -105,7 +105,7 @@ module ActiveSupport # end # # User.hair_colors # => [:brown, :black, :blonde, :red] - def config_accessor(*names) + def config_accessor(*names) #:doc: options = names.extract_options! names.each do |name| diff --git a/activesupport/test/broadcast_logger_test.rb b/activesupport/test/broadcast_logger_test.rb index 02bc317c66..7dfa8a62bd 100644 --- a/activesupport/test/broadcast_logger_test.rb +++ b/activesupport/test/broadcast_logger_test.rb @@ -116,7 +116,7 @@ module ActiveSupport test "Including top constant LoggerSilence is deprecated" do assert_deprecated("Please use `ActiveSupport::LoggerSilence`") do - logger = Class.new(CustomLogger) do + Class.new(CustomLogger) do include ::LoggerSilence end end diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index f9fc7044ba..6b0554bb5f 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -590,9 +590,9 @@ NOTE: Defined in `active_support/core_ext/module/attribute_accessors.rb`. ### Parents -#### `parent` +#### `module_parent` -The `parent` method on a nested named module returns the module that contains its corresponding constant: +The `module_parent` method on a nested named module returns the module that contains its corresponding constant: ```ruby module X @@ -603,19 +603,19 @@ module X end M = X::Y::Z -X::Y::Z.parent # => X::Y -M.parent # => X::Y +X::Y::Z.module_parent # => X::Y +M.module_parent # => X::Y ``` -If the module is anonymous or belongs to the top-level, `parent` returns `Object`. +If the module is anonymous or belongs to the top-level, `module_parent` returns `Object`. -WARNING: Note that in that case `parent_name` returns `nil`. +WARNING: Note that in that case `module_parent_name` returns `nil`. NOTE: Defined in `active_support/core_ext/module/introspection.rb`. -#### `parent_name` +#### `module_parent_name` -The `parent_name` method on a nested named module returns the fully qualified name of the module that contains its corresponding constant: +The `module_parent_name` method on a nested named module returns the fully qualified name of the module that contains its corresponding constant: ```ruby module X @@ -626,19 +626,19 @@ module X end M = X::Y::Z -X::Y::Z.parent_name # => "X::Y" -M.parent_name # => "X::Y" +X::Y::Z.module_parent_name # => "X::Y" +M.module_parent_name # => "X::Y" ``` -For top-level or anonymous modules `parent_name` returns `nil`. +For top-level or anonymous modules `module_parent_name` returns `nil`. -WARNING: Note that in that case `parent` returns `Object`. +WARNING: Note that in that case `module_parent` returns `Object`. NOTE: Defined in `active_support/core_ext/module/introspection.rb`. -#### `parents` +#### `module_parents` -The method `parents` calls `parent` on the receiver and upwards until `Object` is reached. The chain is returned in an array, from bottom to top: +The method `module_parents` calls `module_parent` on the receiver and upwards until `Object` is reached. The chain is returned in an array, from bottom to top: ```ruby module X @@ -649,8 +649,8 @@ module X end M = X::Y::Z -X::Y::Z.parents # => [X::Y, X, Object] -M.parents # => [X::Y, X, Object] +X::Y::Z.module_parents # => [X::Y, X, Object] +M.module_parents # => [X::Y, X, Object] ``` NOTE: Defined in `active_support/core_ext/module/introspection.rb`. |