diff options
Diffstat (limited to 'activerecord')
46 files changed, 362 insertions, 403 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 5ffbed28f6..1bd0199e4c 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,15 @@ +* Values constructed using multi-parameter assignment will now use the + post-type-cast value for rendering in single-field form inputs. + + *Sean Griffin* + +* `Relation#joins` is no longer affected by the target model's + `current_scope`, with the exception of `unscoped`. + + Fixes #29338. + + *Sean Griffin* + * Change sqlite3 boolean serialization to use 1 and 0 SQLite natively recognizes 1 and 0 as true and false, but does not natively diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 1138ae3462..c166cfd68f 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -171,8 +171,8 @@ module ActiveRecord skip_assign = [reflection.foreign_key, reflection.type].compact assigned_keys = record.changed_attribute_names_to_save assigned_keys += except_from_scope_attributes.keys.map(&:to_s) - attributes = create_scope.except(*(assigned_keys - skip_assign)) - record.assign_attributes(attributes) + attributes = scope_for_create.except!(*(assigned_keys - skip_assign)) + record.send(:_assign_attributes, attributes) if attributes.any? set_inverse_instance(record) end @@ -185,6 +185,9 @@ module ActiveRecord end private + def scope_for_create + scope.scope_for_create + end def find_target? !loaded? && (!owner.new_record? || foreign_key_present?) && klass diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index a49fb155ee..69401162aa 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -374,10 +374,6 @@ module ActiveRecord end end - def create_scope - scope.scope_for_create.stringify_keys - end - def delete_or_destroy(records, method) records = records.flatten records.each { |record| raise_on_type_mismatch!(record) } diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 04cdcb6a7f..4a3fb6eaec 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -33,12 +33,8 @@ module ActiveRecord end Table = Struct.new(:node, :columns) do # :nodoc: - def table - Arel::Nodes::TableAlias.new node.table, node.aliased_table_name - end - def column_aliases - t = table + t = node.table columns.map { |column| t[column.name].as Arel.sql column.alias } end end @@ -92,11 +88,11 @@ module ActiveRecord # associations # => [:appointments] # joins # => [] # - def initialize(base, associations, joins, eager_loading: true) + def initialize(base, table, associations, joins, eager_loading: true) @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins) @eager_loading = eager_loading tree = self.class.make_tree associations - @join_root = JoinBase.new base, build(tree, base) + @join_root = JoinBase.new(base, table, build(tree, base)) @join_root.children.each { |child| construct_tables! @join_root, child } end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index b14ddfeeeb..98b2f40357 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -57,10 +57,6 @@ module ActiveRecord def table tables.first end - - def aliased_table_name - table.table_alias || table.name - end end end end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb index 6e0963425d..d4e26d5397 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb @@ -4,17 +4,16 @@ module ActiveRecord module Associations class JoinDependency # :nodoc: class JoinBase < JoinPart # :nodoc: - def match?(other) - return true if self == other - super && base_klass == other.base_klass - end + attr_reader :table - def table - base_klass.arel_table + def initialize(base_klass, table, children) + super(base_klass, children) + @table = table end - def aliased_table_name - base_klass.table_name + def match?(other) + return true if self == other + super && base_klass == other.base_klass end end end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb index 80c9fde5d1..5666e069fc 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb @@ -36,11 +36,6 @@ module ActiveRecord raise NotImplementedError end - # The alias for the active_record's table - def aliased_table_name - raise NotImplementedError - end - def extract_record(row, column_names_with_alias) # This code is performance critical as it is called per row. # see: https://github.com/rails/rails/pull/12185 diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 85343040db..698fd29beb 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -114,8 +114,18 @@ module ActiveRecord @reflection_scope ||= reflection.scope_for(klass) end + def klass_scope + current_scope = klass.current_scope + + if current_scope && current_scope.empty_scope? + klass.unscoped + else + klass.default_scoped + end + end + def build_scope - scope = klass.default_scoped + scope = klass_scope if reflection.type scope.where!(reflection.type => model.base_class.sti_name) diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index f8bbe4c2ed..66993b9bf7 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -30,9 +30,8 @@ module ActiveRecord end private - - def create_scope - scope.scope_for_create.stringify_keys.except(klass.primary_key) + def scope_for_create + super.except!(klass.primary_key) end def find_target diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 78662433eb..f89261923b 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -174,7 +174,7 @@ module ActiveRecord end def came_from_user? - true + !type.value_constructed_by_mass_assignment?(value_before_type_cast) end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 83c61fad19..4e6a43a039 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -1,7 +1,4 @@ -require "active_support/core_ext/enumerable" -require "active_support/core_ext/string/filters" require "mutex_m" -require "concurrent/map" module ActiveRecord # = Active Record Attribute Methods diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 07d194cc57..5efe051125 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -17,8 +17,8 @@ module ActiveRecord class_attribute :partial_writes, instance_writer: false, default: true - after_create { changes_internally_applied } - after_update { changes_internally_applied } + after_create { changes_applied } + after_update { changes_applied } # Attribute methods for "changed in last call to save?" attribute_method_affix(prefix: "saved_change_to_", suffix: "?") @@ -30,25 +30,10 @@ module ActiveRecord attribute_method_suffix("_change_to_be_saved", "_in_database") end - # Attempts to +save+ the record and clears changed attributes if successful. - def save(*) - if status = super - changes_applied - end - status - end - - # Attempts to <tt>save!</tt> the record and clears changed attributes if successful. - def save!(*) - super.tap do - changes_applied - end - end - # <tt>reload</tt> the record and clears changed attributes. def reload(*) super.tap do - @previous_mutation_tracker = nil + @mutations_before_last_save = nil clear_mutation_trackers @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end @@ -62,20 +47,16 @@ module ActiveRecord clear_mutation_trackers end - def changes_internally_applied # :nodoc: + def changes_applied @mutations_before_last_save = mutation_tracker - forget_attribute_assignments @mutations_from_database = AttributeMutationTracker.new(@attributes) - end - - def changes_applied - @previous_mutation_tracker = mutation_tracker @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new + forget_attribute_assignments clear_mutation_trackers end def clear_changes_information - @previous_mutation_tracker = nil + @mutations_before_last_save = nil @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new forget_attribute_assignments clear_mutation_trackers @@ -100,28 +81,18 @@ module ActiveRecord if defined?(@cached_changed_attributes) @cached_changed_attributes else - emit_warning_if_needed("changed_attributes", "saved_changes.transform_values(&:first)") super.reverse_merge(mutation_tracker.changed_values).freeze end end def changes cache_changed_attributes do - emit_warning_if_needed("changes", "saved_changes") super end end def previous_changes - unless previous_mutation_tracker.equal?(mutations_before_last_save) - ActiveSupport::Deprecation.warn(<<-EOW.strip_heredoc) - The behavior of `previous_changes` inside of after callbacks is - deprecated without replacement. In the next release of Rails, - this method inside of `after_save` will return the changes that - were just saved. - EOW - end - previous_mutation_tracker.changes + mutations_before_last_save.changes end def attribute_changed_in_place?(attr_name) @@ -211,31 +182,6 @@ module ActiveRecord changes_to_save.transform_values(&:first) end - def attribute_was(*) - emit_warning_if_needed("attribute_was", "attribute_before_last_save") - super - end - - def attribute_change(*) - emit_warning_if_needed("attribute_change", "saved_change_to_attribute") - super - end - - def attribute_changed?(*) - emit_warning_if_needed("attribute_changed?", "saved_change_to_attribute?") - super - end - - def changed?(*) - emit_warning_if_needed("changed?", "saved_changes?") - super - end - - def changed(*) - emit_warning_if_needed("changed", "saved_changes.keys") - super - end - private def mutation_tracker @@ -245,18 +191,6 @@ module ActiveRecord @mutation_tracker ||= AttributeMutationTracker.new(@attributes) end - def emit_warning_if_needed(method_name, new_method_name) - unless mutation_tracker.equal?(mutations_from_database) - ActiveSupport::Deprecation.warn(<<-EOW.squish) - The behavior of `#{method_name}` inside of after callbacks will - be changing in the next version of Rails. The new return value will reflect the - behavior of calling the method after `save` returned (e.g. the opposite of what - it returns now). To maintain the current behavior, use `#{new_method_name}` - instead. - EOW - end - end - def mutations_from_database unless defined?(@mutations_from_database) @mutations_from_database = nil @@ -275,17 +209,7 @@ module ActiveRecord def attribute_will_change!(attr_name) super - if self.class.has_attribute?(attr_name) - mutations_from_database.force_change(attr_name) - else - ActiveSupport::Deprecation.warn(<<-EOW.squish) - #{attr_name} is not an attribute known to Active Record. - This behavior is deprecated and will be removed in the next - version of Rails. If you'd like #{attr_name} to be managed - by Active Record, add `attribute :#{attr_name}` to your class. - EOW - mutations_from_database.deprecated_force_change(attr_name) - end + mutations_from_database.force_change(attr_name) end def _update_record(*) @@ -307,15 +231,10 @@ module ActiveRecord def clear_mutation_trackers @mutation_tracker = nil @mutations_from_database = nil - @mutations_before_last_save = nil - end - - def previous_mutation_tracker - @previous_mutation_tracker ||= NullMutationTracker.instance end def mutations_before_last_save - @mutations_before_last_save ||= previous_mutation_tracker + @mutations_before_last_save ||= NullMutationTracker.instance end def cache_changed_attributes diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb index a01a58f8a5..e275ae72ba 100644 --- a/activerecord/lib/active_record/attribute_mutation_tracker.rb +++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb @@ -5,7 +5,6 @@ module ActiveRecord def initialize(attributes) @attributes = attributes @forced_changes = Set.new - @deprecated_forced_changes = Set.new end def changed_values @@ -33,7 +32,7 @@ module ActiveRecord end def any_changes? - attr_names.any? { |attr| changed?(attr) } || deprecated_forced_changes.any? + attr_names.any? { |attr| changed?(attr) } end def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) @@ -62,15 +61,11 @@ module ActiveRecord forced_changes << attr_name.to_s end - def deprecated_force_change(attr_name) - deprecated_forced_changes << attr_name.to_s - end - # TODO Change this to private once we've dropped Ruby 2.2 support. # Workaround for Ruby 2.2 "private attribute?" warning. protected - attr_reader :attributes, :forced_changes, :deprecated_forced_changes + attr_reader :attributes, :forced_changes private diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index 6399e3de70..a963ba015a 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -98,8 +98,6 @@ module ActiveRecord attributes == other.attributes end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. protected attr_reader :attributes diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb index d4f5906b33..0cc0ac74fe 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb @@ -39,6 +39,14 @@ module ActiveRecord def quoted_binary(value) "x'#{value.hex}'" end + + def _type_cast(value) + case value + when Type::Time::Value then value.__getobj__ + when Date, Time then value + else super + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index b79dbe0733..2fede7b847 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -82,11 +82,11 @@ module ActiveRecord # Conversion can be accomplished by setting up a rake task which runs # # ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1) - # ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 0) + # ExampleModel.where("boolean_column = 'f'").update_all(boolean_column: 0) # for all models and all boolean columns, after which the flag must be set # to true by adding the following to your application.rb file: # - # ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true + # Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true class_attribute :represent_boolean_as_integer, default: false class StatementPool < ConnectionAdapters::StatementPool diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 198c712abc..3ea7b644c2 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -1,6 +1,4 @@ -require "thread" require "active_support/core_ext/hash/indifferent_access" -require "active_support/core_ext/object/duplicable" require "active_support/core_ext/string/filters" module ActiveRecord @@ -265,16 +263,6 @@ module ActiveRecord @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster) end - # Returns the Arel engine. - def arel_engine # :nodoc: - @arel_engine ||= - if Base == self || connection_handler.retrieve_connection_pool(connection_specification_name) - self - else - superclass.arel_engine - end - end - def arel_attribute(name, table = arel_table) # :nodoc: name = attribute_alias(name) if attribute_alias?(name) table[name] diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 14e0f5bff7..2d3c9175bd 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -467,7 +467,6 @@ module ActiveRecord end def reload_schema_from_cache - @arel_engine = nil @arel_table = nil @column_names = nil @attribute_types = nil diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index a9509e562a..2e6901eb0e 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -175,7 +175,7 @@ module ActiveRecord # callbacks or any <tt>:dependent</tt> association # options, use <tt>#destroy</tt>. def delete - self.class.delete(id) if persisted? + _relation_for_itself.delete_all if persisted? @destroyed = true freeze end @@ -555,6 +555,10 @@ module ActiveRecord end def relation_for_destroy + _relation_for_itself + end + + def _relation_for_itself self.class.unscoped.where(self.class.primary_key => id) end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 962ed880b9..0b82926081 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -26,6 +26,9 @@ module ActiveRecord config.active_record.use_schema_cache_dump = true config.active_record.maintain_test_schema = true + config.active_record.sqlite3 = ActiveSupport::OrderedOptions.new + config.active_record.sqlite3.represent_boolean_as_integer = nil + config.eager_load_namespaces << ActiveRecord rake_tasks do @@ -108,7 +111,9 @@ module ActiveRecord initializer "active_record.set_configs" do |app| ActiveSupport.on_load(:active_record) do - app.config.active_record.each do |k, v| + configs = app.config.active_record.dup + configs.delete(:sqlite3) + configs.each do |k, v| send "#{k}=", v end end @@ -178,6 +183,11 @@ end_warning initializer "active_record.check_represent_sqlite3_boolean_as_integer" do config.after_initialize do ActiveSupport.on_load(:active_record_sqlite3adapter) do + represent_boolean_as_integer = Rails.application.config.active_record.sqlite3.delete(:represent_boolean_as_integer) + unless represent_boolean_as_integer.nil? + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = represent_boolean_as_integer + end + unless ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ActiveSupport::Deprecation.warn <<-MSG Leaving `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer` @@ -187,12 +197,12 @@ serialization) before setting this flag to true. Conversion can be accomplished by setting up a rake task which runs ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1) - ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 0) + ExampleModel.where("boolean_column = 'f'").update_all(boolean_column: 0) for all models and all boolean columns, after which the flag must be set to true by adding the following to your application.rb file: - ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true + Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true MSG end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index a453ca55c7..1026e20f79 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -219,10 +219,10 @@ module ActiveRecord end def klass_join_scope(table, predicate_builder) # :nodoc: - if klass.current_scope - klass.current_scope.clone.tap { |scope| - scope.joins_values = scope.left_outer_joins_values = [].freeze - } + current_scope = klass.current_scope + + if current_scope && current_scope.empty_scope? + build_scope(table, predicate_builder) else klass.default_scoped(build_scope(table, predicate_builder)) end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 76cf47a3ed..d9e1e5c39c 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -556,8 +556,7 @@ module ActiveRecord end def reset - @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil - @should_eager_load = @join_dependency = nil + @to_sql = @arel = @loaded = @should_eager_load = nil @records = [].freeze @offsets = {} self @@ -591,7 +590,7 @@ module ActiveRecord end def scope_for_create - @scope_for_create ||= where_values_hash.merge(create_with_value) + where_values_hash.merge!(create_with_value.stringify_keys) end # Returns true if relation needs eager loading. @@ -643,6 +642,10 @@ module ActiveRecord "#<#{self.class.name} [#{entries.join(', ')}]>" end + def empty_scope? # :nodoc: + @values == klass.unscoped.values + end + protected def load_records(records) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index ac0b4f597e..57b7d3c2e9 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -329,7 +329,7 @@ module ActiveRecord # 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: - conditions = arel.where_sql(@klass.arel_engine) + conditions = arel.where_sql(@klass) conditions = " [#{conditions}]" if conditions name = @klass.name @@ -397,7 +397,7 @@ module ActiveRecord def construct_join_dependency(joins = [], eager_loading: true) including = eager_load_values + includes_values - ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading) + ActiveRecord::Associations::JoinDependency.new(klass, table, including, joins, eager_loading: eager_loading) end def construct_relation_for_association_calculations diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 5dac00724a..6251f6fc33 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -119,9 +119,10 @@ module ActiveRecord end end - join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass, - joins_dependency, - []) + join_dependency = ActiveRecord::Associations::JoinDependency.new( + other.klass, other.table, joins_dependency, [] + ) + relation.joins! rest @relation = relation.joins join_dependency diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 79495ead91..efb55391e7 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -202,12 +202,13 @@ module ActiveRecord # Works in two unique ways. # - # First: takes a block so it can be used just like +Array#select+. + # First: takes a block so it can be used just like <tt>Array#select</tt>. # # Model.all.select { |m| m.field == value } # # This will build an array of objects from the database for the scope, - # converting them into an array and iterating through them using +Array#select+. + # converting them into an array and iterating through them using + # <tt>Array#select</tt>. # # Second: Modifies the SELECT statement for the query so that only certain # fields are retrieved: @@ -797,7 +798,7 @@ module ActiveRecord value = sanitize_forbidden_attributes(value) self.create_with_value = create_with_value.merge(value) else - self.create_with_value = {} + self.create_with_value = FROZEN_EMPTY_HASH end self @@ -1019,9 +1020,7 @@ module ActiveRecord join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) join_dependency = ActiveRecord::Associations::JoinDependency.new( - @klass, - association_joins, - join_list + klass, table, association_joins, join_list ) join_infos = join_dependency.join_constraints stashed_association_joins, join_type @@ -1047,7 +1046,7 @@ module ActiveRecord if select_values.any? arel.project(*arel_columns(select_values.uniq)) else - arel.project(@klass.arel_table[Arel.star]) + arel.project(table[Arel.star]) end end diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index 119910ee79..0feb2c6cb2 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -52,8 +52,8 @@ module ActiveRecord binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h equalities.map { |node| - name = node.left.name - [name, binds.fetch(name.to_s) { + name = node.left.name.to_s + [name, binds.fetch(name) { case node.right when Array then node.right.map(&:val) when Arel::Nodes::Casted, Arel::Nodes::Quoted diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb index 94e0ef6724..82e20a32d0 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -33,9 +33,8 @@ module ActiveRecord def populate_with_current_scope_attributes # :nodoc: return unless self.class.scope_attributes? - self.class.scope_attributes.each do |att, value| - send("#{att}=", value) if respond_to?("#{att}=") - end + attributes = self.class.scope_attributes + _assign_attributes(attributes) if attributes.any? end def initialize_internals_callback # :nodoc: diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index a727cc6e60..a7ac291f23 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -647,8 +647,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_new_record_with_foreign_key_but_no_object client = Client.new("firm_id" => 1) - # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - assert_equal Firm.all.merge!(order: "id").first, client.firm_with_basic_id + assert_equal Firm.first, client.firm_with_basic_id end def test_setting_foreign_key_after_nil_target_loaded diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index a7e16af88d..2682f256ff 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -497,21 +497,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_predicate person.references, :exists? end - # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first def test_counting_with_counter_sql - assert_equal 3, Firm.all.merge!(order: "id").first.clients.count + assert_equal 3, Firm.first.clients.count end def test_counting - assert_equal 3, Firm.all.merge!(order: "id").first.plain_clients.count + assert_equal 3, Firm.first.plain_clients.count end def test_counting_with_single_hash - assert_equal 1, Firm.all.merge!(order: "id").first.plain_clients.where(name: "Microsoft").count + assert_equal 1, Firm.first.plain_clients.where(name: "Microsoft").count end def test_counting_with_column_name_and_hash - assert_equal 3, Firm.all.merge!(order: "id").first.plain_clients.count(:name) + assert_equal 3, Firm.first.plain_clients.count(:name) end def test_counting_with_association_limit @@ -521,7 +520,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding - assert_equal 3, Firm.all.merge!(order: "id").first.clients.length + assert_equal 3, Firm.first.clients.length end def test_finding_array_compatibility @@ -593,27 +592,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding_default_orders - assert_equal "Summit", Firm.all.merge!(order: "id").first.clients.first.name + assert_equal "Summit", Firm.first.clients.first.name end def test_finding_with_different_class_name_and_order - assert_equal "Apex", Firm.all.merge!(order: "id").first.clients_sorted_desc.first.name + assert_equal "Apex", Firm.first.clients_sorted_desc.first.name end def test_finding_with_foreign_key - assert_equal "Microsoft", Firm.all.merge!(order: "id").first.clients_of_firm.first.name + assert_equal "Microsoft", Firm.first.clients_of_firm.first.name end def test_finding_with_condition - assert_equal "Microsoft", Firm.all.merge!(order: "id").first.clients_like_ms.first.name + assert_equal "Microsoft", Firm.first.clients_like_ms.first.name end def test_finding_with_condition_hash - assert_equal "Microsoft", Firm.all.merge!(order: "id").first.clients_like_ms_with_hash_conditions.first.name + assert_equal "Microsoft", Firm.first.clients_like_ms_with_hash_conditions.first.name end def test_finding_using_primary_key - assert_equal "Summit", Firm.all.merge!(order: "id").first.clients_using_primary_key.first.name + assert_equal "Summit", Firm.first.clients_using_primary_key.first.name end def test_update_all_on_association_accessed_before_save @@ -636,7 +635,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_ids - firm = Firm.all.merge!(order: "id").first + firm = Firm.first assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find } @@ -656,7 +655,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_one_message_on_primary_key - firm = Firm.all.merge!(order: "id").first + firm = Firm.first e = assert_raises(ActiveRecord::RecordNotFound) do firm.clients.find(0) @@ -682,7 +681,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all - firm = Firm.all.merge!(order: "id").first + firm = Firm.first assert_equal 3, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length assert_equal 1, firm.clients.where("name = 'Summit'").to_a.length end @@ -727,29 +726,28 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all_sanitized - # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - firm = Firm.all.merge!(order: "id").first + firm = Firm.first summit = firm.clients.where("name = 'Summit'").to_a assert_equal summit, firm.clients.where("name = ?", "Summit").to_a assert_equal summit, firm.clients.where("name = :name", name: "Summit").to_a end def test_find_first - firm = Firm.all.merge!(order: "id").first + firm = Firm.first client2 = Client.find(2) assert_equal firm.clients.first, firm.clients.order("id").first assert_equal client2, firm.clients.where("#{QUOTED_TYPE} = 'Client'").order("id").first end def test_find_first_sanitized - firm = Firm.all.merge!(order: "id").first + firm = Firm.first client2 = Client.find(2) - assert_equal client2, firm.clients.merge!(where: ["#{QUOTED_TYPE} = ?", "Client"], order: "id").first - assert_equal client2, firm.clients.merge!(where: ["#{QUOTED_TYPE} = :type", { type: "Client" }], order: "id").first + assert_equal client2, firm.clients.where("#{QUOTED_TYPE} = ?", "Client").first + assert_equal client2, firm.clients.where("#{QUOTED_TYPE} = :type", type: "Client").first end def test_find_first_after_reset_scope - firm = Firm.all.merge!(order: "id").first + firm = Firm.first collection = firm.clients original_object = collection.first @@ -760,7 +758,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_first_after_reset - firm = Firm.all.merge!(order: "id").first + firm = Firm.first collection = firm.clients original_object = collection.first @@ -772,7 +770,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_first_after_reload - firm = Firm.all.merge!(order: "id").first + firm = Firm.first collection = firm.clients original_object = collection.first @@ -875,7 +873,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create_with_bang_on_has_many_raises_when_record_not_saved assert_raise(ActiveRecord::RecordInvalid) do - firm = Firm.all.merge!(order: "id").first + firm = Firm.first firm.plain_clients.create! end end @@ -1557,8 +1555,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_destroy_dependent_when_deleted_from_association - # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - firm = Firm.all.merge!(order: "id").first + firm = Firm.first assert_equal 3, firm.clients.size client = firm.clients.first @@ -1668,7 +1665,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_less - firm = Firm.all.merge!(order: "id").first + firm = Firm.first firm.clients = [companies(:first_client)] assert firm.save, "Could not save firm" firm.reload @@ -1682,7 +1679,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_new - firm = Firm.all.merge!(order: "id").first + firm = Firm.first firm.clients = [companies(:second_client), Client.new("name" => "New Client")] firm.save firm.reload @@ -2080,7 +2077,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_creating_using_primary_key - firm = Firm.all.merge!(order: "id").first + firm = Firm.first client = firm.clients_using_primary_key.create!(name: "test") assert_equal firm.name, client.firm_name end 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 1c2138a3d0..a76159fb99 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -946,6 +946,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end + def test_has_many_through_polymorphic_with_rewhere + post = TaggedPost.create!(title: "Tagged", body: "Post") + tag = post.tags.create!(name: "Tag") + assert_equal [tag], TaggedPost.preload(:tags).last.tags + assert_equal [tag], TaggedPost.eager_load(:tags).last.tags + end + def test_has_many_through_polymorphic_with_primary_key_option assert_equal [categories(:general)], authors(:david).essay_categories diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index 1fc63a49d4..934d5bf8b2 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -8,11 +8,10 @@ module ActiveRecord end def setup - @klass = Class.new do + @klass = Class.new(Class.new { def self.initialize_generated_modules; end }) do def self.superclass; Base; end def self.base_class; self; end def self.decorate_matching_attribute_types(*); end - def self.initialize_generated_modules; end include ActiveRecord::DefineCallbacks include ActiveRecord::AttributeMethods diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 4d24a980dc..366577761c 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -1005,7 +1005,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase class_eval(&block) end - assert_empty klass.generated_attribute_methods.instance_methods(false) + assert_empty klass.send(:generated_attribute_methods).instance_methods(false) klass end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index f72e0d2ead..bb584b0c6f 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -299,11 +299,9 @@ class DirtyTest < ActiveRecord::TestCase end def test_virtual_attribute_will_change - assert_deprecated do - parrot = Parrot.create!(name: "Ruby") - parrot.send(:attribute_will_change!, :cancel_save_from_callback) - assert parrot.has_changes_to_save? - end + parrot = Parrot.create!(name: "Ruby") + parrot.send(:attribute_will_change!, :cancel_save_from_callback) + assert parrot.has_changes_to_save? end def test_association_assignment_changes_foreign_key @@ -839,15 +837,14 @@ class DirtyTest < ActiveRecord::TestCase assert_equal %w(first_name lock_version updated_at).sort, person.saved_changes.keys.sort end - test "changed? in after callbacks returns true but is deprecated" do + test "changed? in after callbacks returns false" do klass = Class.new(ActiveRecord::Base) do self.table_name = "people" after_save do - ActiveSupport::Deprecation.silence do - raise "changed? should be true" unless changed? - end + raise "changed? should be false" if changed? raise "has_changes_to_save? should be false" if has_changes_to_save? + raise "saved_changes? should be true" unless saved_changes? end end diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb index 9b4b61b16e..a6ea27bf48 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -160,10 +160,8 @@ class JsonSerializationTest < ActiveRecord::TestCase end def test_serializable_hash_should_not_modify_options_in_argument - options = { only: :name } - @contact.serializable_hash(options) - - assert_nil options[:except] + options = { only: :name }.freeze + assert_nothing_raised { @contact.serializable_hash(options) } end end diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb index ceb5724377..aa521e907c 100644 --- a/activerecord/test/cases/multiparameter_attributes_test.rb +++ b/activerecord/test/cases/multiparameter_attributes_test.rb @@ -271,6 +271,12 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase ensure Topic.reset_column_information end + + def test_multiparameter_attributes_setting_time_attribute + topic = Topic.new("bonus_time(4i)" => "01", "bonus_time(5i)" => "05") + assert_equal 1, topic.bonus_time.hour + assert_equal 5, topic.bonus_time.min + end end def test_multiparameter_attributes_on_time_with_empty_seconds @@ -285,14 +291,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase end end - unless current_adapter? :OracleAdapter - def test_multiparameter_attributes_setting_time_attribute - topic = Topic.new("bonus_time(4i)" => "01", "bonus_time(5i)" => "05") - assert_equal 1, topic.bonus_time.hour - assert_equal 5, topic.bonus_time.min - end - end - def test_multiparameter_attributes_setting_date_attribute topic = Topic.new("written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11") assert_equal 1952, topic.written_on.year @@ -300,13 +298,34 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase assert_equal 11, topic.written_on.day end + def test_create_with_multiparameter_attributes_setting_date_attribute + topic = Topic.create_with("written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11").new + assert_equal 1952, topic.written_on.year + assert_equal 3, topic.written_on.month + assert_equal 11, topic.written_on.day + end + def test_multiparameter_attributes_setting_date_and_time_attribute topic = Topic.new( - "written_on(1i)" => "1952", - "written_on(2i)" => "3", - "written_on(3i)" => "11", - "written_on(4i)" => "13", - "written_on(5i)" => "55") + "written_on(1i)" => "1952", + "written_on(2i)" => "3", + "written_on(3i)" => "11", + "written_on(4i)" => "13", + "written_on(5i)" => "55") + assert_equal 1952, topic.written_on.year + assert_equal 3, topic.written_on.month + assert_equal 11, topic.written_on.day + assert_equal 13, topic.written_on.hour + assert_equal 55, topic.written_on.min + end + + def test_create_with_multiparameter_attributes_setting_date_and_time_attribute + topic = Topic.create_with( + "written_on(1i)" => "1952", + "written_on(2i)" => "3", + "written_on(3i)" => "11", + "written_on(4i)" => "13", + "written_on(5i)" => "55").new assert_equal 1952, topic.written_on.year assert_equal 3, topic.written_on.month assert_equal 11, topic.written_on.day @@ -364,4 +383,15 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase assert_equal("address", ex.errors[0].attribute) end + + def test_multiparameter_assigned_attributes_did_not_come_from_user + topic = Topic.new( + "written_on(1i)" => "1952", + "written_on(2i)" => "3", + "written_on(3i)" => "11", + "written_on(4i)" => "13", + "written_on(5i)" => "55", + ) + refute_predicate topic, :written_on_came_from_user? + end end diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index e3bb51bd77..4e3daeabb6 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -90,14 +90,9 @@ class MultipleDbTest < ActiveRecord::TestCase assert_equal "Ruby Developer", Entrant.find(1).name end - def test_arel_table_engines - assert_not_equal Entrant.arel_engine, Bird.arel_engine - assert_not_equal Entrant.arel_engine, Course.arel_engine - end - def test_connection - assert_equal Entrant.arel_engine.connection.object_id, Bird.arel_engine.connection.object_id - assert_not_equal Entrant.arel_engine.connection.object_id, Course.arel_engine.connection.object_id + assert_same Entrant.connection, Bird.connection + assert_not_same Entrant.connection, Course.connection end unless in_memory_db? diff --git a/activerecord/test/cases/null_relation_test.rb b/activerecord/test/cases/null_relation_test.rb new file mode 100644 index 0000000000..56f5a89686 --- /dev/null +++ b/activerecord/test/cases/null_relation_test.rb @@ -0,0 +1,82 @@ +require "cases/helper" +require "models/developer" +require "models/comment" +require "models/post" +require "models/topic" + +class NullRelationTest < ActiveRecord::TestCase + fixtures :posts, :comments + + def test_none + assert_no_queries(ignore_none: false) do + assert_equal [], Developer.none + assert_equal [], Developer.all.none + end + end + + def test_none_chainable + assert_no_queries(ignore_none: false) do + assert_equal [], Developer.none.where(name: "David") + end + end + + def test_none_chainable_to_existing_scope_extension_method + assert_no_queries(ignore_none: false) do + assert_equal 1, Topic.anonymous_extension.none.one + end + end + + def test_none_chained_to_methods_firing_queries_straight_to_db + assert_no_queries(ignore_none: false) do + assert_equal [], Developer.none.pluck(:id, :name) + assert_equal 0, Developer.none.delete_all + assert_equal 0, Developer.none.update_all(name: "David") + assert_equal 0, Developer.none.delete(1) + assert_equal false, Developer.none.exists?(1) + end + end + + def test_null_relation_content_size_methods + assert_no_queries(ignore_none: false) do + assert_equal 0, Developer.none.size + assert_equal 0, Developer.none.count + assert_equal true, Developer.none.empty? + assert_equal true, Developer.none.none? + assert_equal false, Developer.none.any? + assert_equal false, Developer.none.one? + assert_equal false, Developer.none.many? + end + end + + def test_null_relation_metadata_methods + assert_equal "", Developer.none.to_sql + assert_equal({}, Developer.none.where_values_hash) + end + + def test_null_relation_where_values_hash + assert_equal({ "salary" => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash) + end + + [:count, :sum].each do |method| + define_method "test_null_relation_#{method}" do + assert_no_queries(ignore_none: false) do + assert_equal 0, Comment.none.public_send(method, :id) + assert_equal Hash.new, Comment.none.group(:post_id).public_send(method, :id) + end + end + end + + [:average, :minimum, :maximum].each do |method| + define_method "test_null_relation_#{method}" do + assert_no_queries(ignore_none: false) do + assert_nil Comment.none.public_send(method, :id) + assert_equal Hash.new, Comment.none.group(:post_id).public_send(method, :id) + end + end + end + + def test_null_relation_in_where_condition + assert_operator Comment.count, :>, 0 # precondition, make sure there are comments. + assert_equal 0, Comment.where(post_id: Post.none).count + end +end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 5895c51714..5830f9916a 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -437,6 +437,13 @@ class PersistenceTest < ActiveRecord::TestCase assert_not_nil Topic.find(2) end + def test_delete_isnt_affected_by_scoping + topic = Topic.find(1) + assert_difference("Topic.count", -1) do + Topic.where("1=0").scoping { topic.delete } + end + end + def test_destroy topic = Topic.find(1) assert_equal topic, topic.destroy, "topic.destroy did not return self" diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index b21adccc4b..98b20915c6 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -174,13 +174,21 @@ module ActiveRecord def test_type_cast_date date = Date.today - expected = @conn.quoted_date(date) + if current_adapter?(:Mysql2Adapter) + expected = date + else + expected = @conn.quoted_date(date) + end assert_equal expected, @conn.type_cast(date) end def test_type_cast_time time = Time.now - expected = @conn.quoted_date(time) + if current_adapter?(:Mysql2Adapter) + expected = time + else + expected = @conn.quoted_date(time) + end assert_equal expected, @conn.type_cast(time) end @@ -257,7 +265,7 @@ module ActiveRecord def test_type_cast_ar_object value = DatetimePrimaryKey.new(id: @time) - assert_equal "2017-02-14 12:34:56.789000", @connection.type_cast(value) + assert_equal @connection.type_cast(value.id), @connection.type_cast(value) end end end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 382aa17c34..82b071fb98 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -52,8 +52,8 @@ module ActiveRecord def test_has_values relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) - relation.where! relation.table[:id].eq(10) - assert_equal({ id: 10 }, relation.where_values_hash) + relation.where!(id: 10) + assert_equal({ "id" => 10 }, relation.where_values_hash) end def test_values_wrong_table @@ -83,28 +83,19 @@ module ActiveRecord def test_create_with_value relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) - hash = { hello: "world" } - relation.create_with_value = hash - assert_equal hash, relation.scope_for_create - end - - def test_create_with_value_with_wheres - relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) - relation.where! relation.table[:id].eq(10) relation.create_with_value = { hello: "world" } - assert_equal({ hello: "world", id: 10 }, relation.scope_for_create) + assert_equal({ "hello" => "world" }, relation.scope_for_create) end - # FIXME: is this really wanted or expected behavior? - def test_scope_for_create_is_cached + def test_create_with_value_with_wheres relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) assert_equal({}, relation.scope_for_create) - relation.where! relation.table[:id].eq(10) - assert_equal({}, relation.scope_for_create) + relation.where!(id: 10) + assert_equal({ "id" => 10 }, relation.scope_for_create) relation.create_with_value = { hello: "world" } - assert_equal({}, relation.scope_for_create) + assert_equal({ "hello" => "world", "id" => 10 }, relation.scope_for_create) end def test_bad_constants_raise_errors diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index eb3449b331..316ea75e36 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -15,7 +15,6 @@ require "models/car" require "models/engine" require "models/tyre" require "models/minivan" -require "models/aircraft" require "models/possession" require "models/reader" require "models/categorization" @@ -420,123 +419,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal [2, 4, 6, 8, 10], even_ids.sort end - def test_none - assert_no_queries(ignore_none: false) do - assert_equal [], Developer.none - assert_equal [], Developer.all.none - end - end - - def test_none_chainable - assert_no_queries(ignore_none: false) do - assert_equal [], Developer.none.where(name: "David") - end - end - - def test_none_chainable_to_existing_scope_extension_method - assert_no_queries(ignore_none: false) do - assert_equal 1, Topic.anonymous_extension.none.one - end - end - - def test_none_chained_to_methods_firing_queries_straight_to_db - assert_no_queries(ignore_none: false) do - assert_equal [], Developer.none.pluck(:id, :name) - assert_equal 0, Developer.none.delete_all - assert_equal 0, Developer.none.update_all(name: "David") - assert_equal 0, Developer.none.delete(1) - assert_equal false, Developer.none.exists?(1) - end - end - - def test_null_relation_content_size_methods - assert_no_queries(ignore_none: false) do - assert_equal 0, Developer.none.size - assert_equal 0, Developer.none.count - assert_equal true, Developer.none.empty? - assert_equal true, Developer.none.none? - assert_equal false, Developer.none.any? - assert_equal false, Developer.none.one? - assert_equal false, Developer.none.many? - end - end - - def test_null_relation_calculations_methods - assert_no_queries(ignore_none: false) do - assert_equal 0, Developer.none.count - assert_equal 0, Developer.none.calculate(:count, nil) - assert_nil Developer.none.calculate(:average, "salary") - end - end - - def test_null_relation_metadata_methods - assert_equal "", Developer.none.to_sql - assert_equal({}, Developer.none.where_values_hash) - end - - def test_null_relation_where_values_hash - assert_equal({ "salary" => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash) - end - - def test_null_relation_sum - ac = Aircraft.new - assert_equal Hash.new, ac.engines.group(:id).sum(:id) - assert_equal 0, ac.engines.count - ac.save - assert_equal Hash.new, ac.engines.group(:id).sum(:id) - assert_equal 0, ac.engines.count - end - - def test_null_relation_count - ac = Aircraft.new - assert_equal Hash.new, ac.engines.group(:id).count - assert_equal 0, ac.engines.count - ac.save - assert_equal Hash.new, ac.engines.group(:id).count - assert_equal 0, ac.engines.count - end - - def test_null_relation_size - ac = Aircraft.new - assert_equal Hash.new, ac.engines.group(:id).size - assert_equal 0, ac.engines.size - ac.save - assert_equal Hash.new, ac.engines.group(:id).size - assert_equal 0, ac.engines.size - end - - def test_null_relation_average - ac = Aircraft.new - assert_equal Hash.new, ac.engines.group(:car_id).average(:id) - assert_nil ac.engines.average(:id) - ac.save - assert_equal Hash.new, ac.engines.group(:car_id).average(:id) - assert_nil ac.engines.average(:id) - end - - def test_null_relation_minimum - ac = Aircraft.new - assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id) - assert_nil ac.engines.minimum(:id) - ac.save - assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id) - assert_nil ac.engines.minimum(:id) - end - - def test_null_relation_maximum - ac = Aircraft.new - assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id) - assert_nil ac.engines.maximum(:id) - ac.save - assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id) - assert_nil ac.engines.maximum(:id) - end - - def test_null_relation_in_where_condition - assert_operator Comment.count, :>, 0 # precondition, make sure there are comments. - assert_equal 0, Comment.where(post_id: Post.none).to_a.size - end - def test_joins_with_nil_argument assert_nothing_raised { DependentFirm.joins(nil).first } end @@ -586,14 +468,6 @@ class RelationTest < ActiveRecord::TestCase assert_nothing_raised { Topic.reorder([]) } end - def test_scoped_responds_to_delegated_methods - relation = Topic.all - - ["map", "uniq", "sort", "insert", "delete", "update"].each do |method| - assert_respond_to relation, method, "Topic.all should respond to #{method.inspect}" - end - end - def test_respond_to_delegates_to_arel relation = Topic.all fake_arel = Struct.new(:responds) { @@ -1482,7 +1356,7 @@ class RelationTest < ActiveRecord::TestCase assert_equal bird, Bird.find_or_initialize_by(name: "bob") end - def test_explicit_create_scope + def test_explicit_create_with hens = Bird.where(name: "hen") assert_equal "hen", hens.new.name @@ -1911,15 +1785,15 @@ class RelationTest < ActiveRecord::TestCase end test "using a custom table affects the wheres" do - table_alias = Post.arel_table.alias("omg_posts") + post = posts(:welcome) - table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias) - predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata) - relation = ActiveRecord::Relation.new(Post, table_alias, predicate_builder) - relation.where!(foo: "bar") + assert_equal post, custom_post_relation.where!(title: post.title).take + end + + test "using a custom table with joins affects the joins" do + post = posts(:welcome) - node = relation.arel.constraints.first.grep(Arel::Attributes::Attribute).first - assert_equal table_alias, node.relation + assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).take end test "#load" do @@ -2076,4 +1950,13 @@ class RelationTest < ActiveRecord::TestCase end end end + + private + def custom_post_relation + table_alias = Post.arel_table.alias("omg_posts") + table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias) + predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata) + + ActiveRecord::Relation.create(Post, table_alias, predicate_builder) + end end diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 89fb434b27..a8c79628e7 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -332,7 +332,7 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_create_with_merge aaron = PoorDeveloperCalledJamis.create_with(name: "foo", salary: 20).merge( - PoorDeveloperCalledJamis.create_with(name: "Aaron")).new + PoorDeveloperCalledJamis.create_with(name: "Aaron")).new assert_equal 20, aaron.salary assert_equal "Aaron", aaron.name @@ -342,6 +342,11 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal "Aaron", aaron.name end + def test_create_with_using_both_string_and_symbol + jamis = PoorDeveloperCalledJamis.create_with(name: "foo").create_with("name" => "Aaron").new + assert_equal "Aaron", jamis.name + end + def test_create_with_reset jamis = PoorDeveloperCalledJamis.create_with(name: "Aaron").create_with(nil).new assert_equal "Jamis", jamis.name @@ -372,11 +377,39 @@ class DefaultScopingTest < ActiveRecord::TestCase Comment.joins(:post).count end + def test_joins_not_affected_by_scope_other_than_default_or_unscoped + without_scope_on_post = Comment.joins(:post).to_a + with_scope_on_post = nil + Post.where(id: [1, 5, 6]).scoping do + with_scope_on_post = Comment.joins(:post).to_a + end + + assert_equal with_scope_on_post, without_scope_on_post + end + def test_unscoped_with_joins_should_not_have_default_scope assert_equal SpecialPostWithDefaultScope.unscoped { Comment.joins(:special_post_with_default_scope).to_a }, Comment.joins(:post).to_a end + def test_sti_association_with_unscoped_not_affected_by_default_scope + post = posts(:thinking) + comments = [comments(:does_it_hurt)] + + post.special_comments.update_all(deleted_at: Time.now) + + assert_raises(ActiveRecord::RecordNotFound) { Post.joins(:special_comments).find(post.id) } + assert_equal [], post.special_comments + + SpecialComment.unscoped do + assert_equal post, Post.joins(:special_comments).find(post.id) + assert_equal comments, Post.joins(:special_comments).find(post.id).special_comments + assert_equal comments, Post.eager_load(:special_comments).find(post.id).special_comments + assert_equal comments, Post.includes(:special_comments).find(post.id).special_comments + assert_equal comments, Post.preload(:special_comments).find(post.id).special_comments + end + end + def test_default_scope_select_ignored_by_aggregations assert_equal DeveloperWithSelect.all.to_a.count, DeveloperWithSelect.count end diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index eecf923046..dc2d421cd7 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -54,6 +54,7 @@ class Comment < ActiveRecord::Base end class SpecialComment < Comment + default_scope { where(deleted_at: nil) } end class SubSpecialComment < SpecialComment diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb index 1e5f9285a8..255ac19365 100644 --- a/activerecord/test/models/parrot.rb +++ b/activerecord/test/models/parrot.rb @@ -8,7 +8,7 @@ class Parrot < ActiveRecord::Base validates_presence_of :name - attr_accessor :cancel_save_from_callback + attribute :cancel_save_from_callback before_save :cancel_save_callback_method, if: :cancel_save_from_callback def cancel_save_callback_method throw(:abort) diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index a99ab12b19..4aba336b01 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -199,6 +199,11 @@ class FirstPost < ActiveRecord::Base has_one :comment, foreign_key: :post_id end +class TaggedPost < Post + has_many :taggings, -> { rewhere(taggable_type: "TaggedPost") }, as: :taggable + has_many :tags, through: :taggings +end + class PostWithDefaultInclude < ActiveRecord::Base self.inheritance_column = :disabled self.table_name = "posts" diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index f534e9c00e..90185ff6c5 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -189,6 +189,7 @@ ActiveRecord::Schema.define do t.string :resource_id t.string :resource_type t.integer :developer_id + t.datetime :deleted_at end create_table :companies, force: true do |t| |