diff options
Diffstat (limited to 'activerecord')
109 files changed, 929 insertions, 968 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index beead181f3..f75f1a9108 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,13 +1,27 @@ -* Add ActiveRecord::Base#cache_version to support recyclable cache keys via the new versioned entries - in ActiveSupport::Cache. This also means that ActiveRecord::Base#cache_key will now return a stable key +* Prevent making bind param if casted value is nil. + + *Ryuta Kamizono* + +* Deprecate passing arguments and block at the same time to `count` and `sum` in `ActiveRecord::Calculations`. + + *Ryuta Kamizono* + +* Loading model schema from database is now thread-safe. + + Fixes #28589. + + *Vikrant Chaudhary*, *David Abdemoulaie* + +* Add `ActiveRecord::Base#cache_version` to support recyclable cache keys via the new versioned entries + in `ActiveSupport::Cache`. This also means that `ActiveRecord::Base#cache_key` will now return a stable key that does not include a timestamp any more. - - NOTE: This feature is turned off by default, and #cache_key will still return cache keys with timestamps - until you set ActiveRecord::Base.cache_versioning = true. That's the setting for all new apps on Rails 5.2+ - + + NOTE: This feature is turned off by default, and `#cache_key` will still return cache keys with timestamps + until you set `ActiveRecord::Base.cache_versioning = true`. That's the setting for all new apps on Rails 5.2+ + *DHH* -* Respect 'SchemaDumper.ignore_tables' in rake tasks for databases structure dump +* Respect `SchemaDumper.ignore_tables` in rake tasks for databases structure dump *Rusty Geldmacher*, *Guillermo Iguaran* diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 7be3d851f1..2d0d5bd657 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -1,7 +1,7 @@ require "rake/testtask" -require File.expand_path(File.dirname(__FILE__)) + "/test/config" -require File.expand_path(File.dirname(__FILE__)) + "/test/support/config" +require File.expand_path("test/config", __dir__) +require File.expand_path("test/support/config", __dir__) def run_without_aborting(*tasks) errors = [] @@ -134,7 +134,7 @@ task drop_postgresql_databases: "db:postgresql:drop" task rebuild_postgresql_databases: "db:postgresql:rebuild" task :lines do - load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics" + load File.expand_path("../tools/line_statistics", __dir__) files = FileList["lib/active_record/**/*.rb"] CodeTools::LineStatistics.new(files).print_loc end diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 0b37e9076c..450ec0bba9 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -1,4 +1,4 @@ -version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip +version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 96b8545dfc..29f49c6195 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -177,5 +177,5 @@ ActiveSupport.on_load(:active_record) do end ActiveSupport.on_load(:i18n) do - I18n.load_path << File.dirname(__FILE__) + "/active_record/locale/en.yml" + I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__) end diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 3963008a76..4a5c821607 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -4,8 +4,6 @@ module ActiveRecord module Associations # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency class AliasTracker # :nodoc: - attr_reader :aliases - def self.create(connection, initial_table, type_caster) aliases = Hash.new(0) aliases[initial_table] = 1 @@ -80,6 +78,11 @@ module ActiveRecord end 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 :aliases + private def truncate(name) diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 1cb2b2d7c6..ee2e79889f 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -133,6 +133,16 @@ module ActiveRecord AssociationRelation.create(klass, klass.arel_table, klass.predicate_builder, self).merge!(klass.all) end + def extensions + extensions = klass.default_extensions | reflection.extensions + + if scope = reflection.scope + extensions |= klass.unscoped.instance_exec(owner, &scope).extensions + end + + extensions + end + # Loads the \target if needed and returns it. # # This method is abstract in the sense that it relies on +find_target+, @@ -152,14 +162,6 @@ module ActiveRecord reset end - def interpolate(sql, record = nil) - if sql.respond_to?(:to_proc) - owner.instance_exec(record, &sql) - else - sql - end - end - # We can't dump @reflection since it contains the scope proc def marshal_dump ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] } diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 120d75416c..1593b94f0c 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -24,7 +24,7 @@ module ActiveRecord alias_tracker = AliasTracker.create connection, association.klass.table_name, klass.type_caster chain_head, chain_tail = get_chain(reflection, association, alias_tracker) - scope.extending! Array(reflection.options[:extend]) + scope.extending! reflection.extensions add_constraints(scope, owner, reflection, chain_head, chain_tail) end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 62c944fce3..edc53e2517 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -30,7 +30,8 @@ module ActiveRecord reload end - CollectionProxy.create(klass, self) + @proxy ||= CollectionProxy.create(klass, self) + @proxy.reset_scope end # Implements the writer method, e.g. foo.items= for Foo.has_many :items diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 74a4d515c2..8cdb508c43 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -31,6 +31,9 @@ module ActiveRecord def initialize(klass, association) #:nodoc: @association = association super klass, klass.arel_table, klass.predicate_builder + + extensions = association.extensions + extend(*extensions) if extensions.any? end def target @@ -1084,9 +1087,8 @@ module ActiveRecord # person.pets(true) # fetches pets from the database # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>] def reload - @scope = nil proxy_association.reload - self + reset_scope end # Unloads the association. Returns +self+. @@ -1106,9 +1108,13 @@ module ActiveRecord # person.pets # fetches pets from the database # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>] def reset - @scope = nil proxy_association.reset proxy_association.reset_scope + reset_scope + end + + def reset_scope # :nodoc: + @scope = nil self end @@ -1121,19 +1127,6 @@ module ActiveRecord delegate(*delegate_methods, to: :scope) - module DelegateExtending # :nodoc: - private - def method_missing(method, *args, &block) - extending_values = association_scope.extending_values - if extending_values.any? && (extending_values - self.class.included_modules).any? - self.class.include(*extending_values) - public_send(method, *args, &block) - else - super - end - end - end - private def find_nth_with_limit(index, limit) @@ -1154,17 +1147,9 @@ module ActiveRecord @association.find_from_target? end - def association_scope - @association.association_scope - end - def exec_queries load_target end - - def respond_to_missing?(method, _) - association_scope.respond_to?(method) || super - end end end end diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 8995b1e352..643226267c 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -106,12 +106,7 @@ module ActiveRecord def join_constraints(outer_joins, join_type) joins = join_root.children.flat_map { |child| - - if join_type == Arel::Nodes::OuterJoin - make_left_outer_joins join_root, child - else - make_inner_joins join_root, child - end + make_join_constraints(join_root, child, join_type) } joins.concat outer_joins.flat_map { |oj| @@ -175,27 +170,15 @@ module ActiveRecord end def make_outer_joins(parent, child) - tables = table_aliases_for(parent, child) - join_type = Arel::Nodes::OuterJoin - info = make_constraints parent, child, tables, join_type - - [info] + child.children.flat_map { |c| make_outer_joins(child, c) } - end - - def make_left_outer_joins(parent, child) - tables = child.tables join_type = Arel::Nodes::OuterJoin - info = make_constraints parent, child, tables, join_type - - [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) } + make_join_constraints(parent, child, join_type, true) end - def make_inner_joins(parent, child) - tables = child.tables - join_type = Arel::Nodes::InnerJoin - info = make_constraints parent, child, tables, join_type + def make_join_constraints(parent, child, join_type, aliasing = false) + tables = aliasing ? table_aliases_for(parent, child) : child.tables + info = make_constraints(parent, child, tables, join_type) - [info] + child.children.flat_map { |c| make_inner_joins(child, c) } + [info] + child.children.flat_map { |c| make_join_constraints(child, c, join_type, aliasing) } end def table_aliases_for(parent, node) 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 61cec5403a..80c9fde5d1 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb @@ -22,10 +22,6 @@ module ActiveRecord @children = children end - def name - reflection.name - end - def match?(other) self.class == other.class end diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 4072d19380..63ef3f2d8c 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -51,11 +51,10 @@ module ActiveRecord raise NotImplementedError end - def options - reflection.options - end - private + def options + reflection.options + end def associated_records_by_owner(preloader) records = load_records do |record| diff --git a/activerecord/lib/active_record/associations/preloader/belongs_to.rb b/activerecord/lib/active_record/associations/preloader/belongs_to.rb index 38e231826c..c20145770f 100644 --- a/activerecord/lib/active_record/associations/preloader/belongs_to.rb +++ b/activerecord/lib/active_record/associations/preloader/belongs_to.rb @@ -3,7 +3,7 @@ module ActiveRecord class Preloader class BelongsTo < SingularAssociation #:nodoc: def association_key_name - reflection.options[:primary_key] || klass && klass.primary_key + options[:primary_key] || klass && klass.primary_key end def owner_key_name diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index d53d3f777b..8b954138cd 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -65,7 +65,7 @@ module ActiveRecord def reset_association(owners, association_name) should_reset = (through_scope != through_reflection.klass.unscoped) || - (reflection.options[:source_type] && through_reflection.collection?) + (options[:source_type] && through_reflection.collection?) # Don't cache the association - we would only be caching a subset if should_reset diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb index c39e9ce4c5..5bc8527745 100644 --- a/activerecord/lib/active_record/attribute_decorators.rb +++ b/activerecord/lib/active_record/attribute_decorators.rb @@ -3,8 +3,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :attribute_type_decorations, instance_accessor: false # :internal: - self.attribute_type_decorations = TypeDecorator.new + class_attribute :attribute_type_decorations, instance_accessor: false, default: TypeDecorator.new # :internal: end module ClassMethods # :nodoc: diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index ebe06566cc..83c61fad19 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -62,7 +62,6 @@ module ActiveRecord super(attribute_names) @attribute_methods_generated = true end - true end def undefine_attribute_methods # :nodoc: diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index bd5003d63a..76987fb8f4 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -14,8 +14,7 @@ module ActiveRecord raise "You cannot include Dirty after Timestamp" end - class_attribute :partial_writes, instance_writer: false - self.partial_writes = true + class_attribute :partial_writes, instance_writer: false, default: true after_create { changes_internally_applied } after_update { changes_internally_applied } diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 321d039ed4..4a8d231503 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -57,11 +57,8 @@ module ActiveRecord mattr_accessor :time_zone_aware_attributes, instance_writer: false self.time_zone_aware_attributes = false - class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false - self.skip_time_zone_conversion_for_attributes = [] - - class_attribute :time_zone_aware_types, instance_writer: false - self.time_zone_aware_types = [:datetime, :time] + class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: [] + class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ] end module ClassMethods diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index 66b278219a..01f9d815d5 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -64,7 +64,7 @@ module ActiveRecord end def deep_dup - dup.tap do |copy| + self.class.allocate.tap do |copy| copy.instance_variable_set(:@attributes, attributes.deep_dup) end end diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 75f5ba3a96..475b9beec4 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -6,8 +6,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false # :internal: - self.attributes_to_define_after_schema_loads = {} + class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal: end module ClassMethods diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index ebe92e7878..829a4f6e86 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -216,13 +216,7 @@ module ActiveRecord method = :validate_single_association end - define_non_cyclic_method(validation_method) do - send(method, reflection) - # TODO: remove the following line as soon as the return value of - # callbacks is ignored, that is, returning `false` does not - # display a deprecation warning or halts the callback chain. - true - end + define_non_cyclic_method(validation_method) { send(method, reflection) } validate validation_method after_validation :_ensure_no_duplicate_errors end @@ -369,7 +363,6 @@ module ActiveRecord # association whether or not the parent was a new record before saving. def before_save_collection_association @new_record_before_save = new_record? - true end def after_save_collection_association diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 46d7f84efd..a30fbe0e05 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -146,7 +146,7 @@ module ActiveRecord end def polymorphic_options - as_options(polymorphic) + as_options(polymorphic).merge(null: options[:null]) end def index_options 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 6015c38ab5..6cf8645cab 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1299,9 +1299,10 @@ module ActiveRecord end def foreign_key_name(table_name, options) - identifier = "#{table_name}_#{options.fetch(:column)}_fk" - hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) options.fetch(:name) do + identifier = "#{table_name}_#{options.fetch(:column)}_fk" + hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) + "fk_rails_#{hashed_identifier}" end end 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 abc15f595f..648c869915 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -29,8 +29,7 @@ module ActiveRecord # to your application.rb file: # # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false - class_attribute :emulate_booleans - self.emulate_booleans = true + class_attribute :emulate_booleans, default: true NATIVE_DATABASE_TYPES = { primary_key: "bigint auto_increment PRIMARY KEY", @@ -544,7 +543,7 @@ module ActiveRecord m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) m.register_type %r(^float)i, Type::Float.new(limit: 24) m.register_type %r(^double)i, Type::Float.new(limit: 53) - m.register_type %r(^json)i, MysqlJson.new + m.register_type %r(^json)i, Type::Json.new register_integer_type m, %r(^bigint)i, limit: 8 register_integer_type m, %r(^int)i, limit: 4 @@ -838,12 +837,7 @@ module ActiveRecord end end - class MysqlJson < Type::Internal::AbstractJson # :nodoc: - def changed_in_place?(raw_old_value, new_value) - # Normalization is required because MySQL JSON data format includes - # the space between the elements. - super(serialize(deserialize(raw_old_value)), new_value) - end + class MysqlJson < Type::Json # :nodoc: end class MysqlString < Type::String # :nodoc: @@ -866,7 +860,6 @@ module ActiveRecord end end - ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2) ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb index dbc879ffd4..3c706c27c4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb @@ -2,7 +2,7 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: - class Json < Type::Internal::AbstractJson + class Json < Type::Json # :nodoc: end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb index 87391b5dc7..a1fec289d4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb @@ -2,20 +2,10 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: - class Jsonb < Json # :nodoc: + class Jsonb < Type::Json # :nodoc: def type :jsonb end - - def changed_in_place?(raw_old_value, new_value) - # Postgres does not preserve insignificant whitespaces when - # round-tripping jsonb columns. This causes some false positives for - # the comparison here. Therefore, we need to parse and re-dump the - # raw value here to ensure the insignificant whitespaces are - # consistent with our encoder's output. - raw_old_value = serialize(deserialize(raw_old_value)) - super(raw_old_value, new_value) - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index da8d0c6992..44eb666965 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -62,7 +62,7 @@ module ActiveRecord def quote_default_expression(value, column) # :nodoc: if value.is_a?(Proc) value.call - elsif column.type == :uuid && value.include?("()") + elsif column.type == :uuid && /\(\)/.match?(value) value # Does not quote function default values for UUID columns elsif column.respond_to?(:array?) value = type_cast_from_column(column, value) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 5b483ad4ab..cb45d7ba6b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -186,17 +186,17 @@ module ActiveRecord # Returns the current database encoding format. def encoding - select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA") + select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA") end # Returns the current database collation. def collation - select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA") + select_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA") end # Returns the current database ctype. def ctype - select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA") + select_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA") end # Returns an array of schema names. @@ -290,9 +290,17 @@ module ActiveRecord if pk && sequence quoted_sequence = quote_table_name(sequence) + max_pk = select_value("select MAX(#{quote_column_name pk}) from #{quote_table_name(table)}") + if max_pk.nil? + if postgresql_version >= 100000 + minvalue = select_value("SELECT seqmin from pg_sequence where seqrelid = '#{quoted_sequence}'::regclass") + else + minvalue = select_value("SELECT min_value FROM #{quoted_sequence}") + end + end select_value(<<-end_sql, "SCHEMA") - SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false) + SELECT setval('#{quoted_sequence}', #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false}) end_sql end end @@ -377,14 +385,15 @@ module ActiveRecord clear_cache! execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}" pk, seq = pk_and_sequence_for(new_name) - if seq && seq.identifier == "#{table_name}_#{pk}_seq" - new_seq = "#{new_name}_#{pk}_seq" + if pk idx = "#{table_name}_pkey" new_idx = "#{new_name}_pkey" - execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}" execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}" + if seq && seq.identifier == "#{table_name}_#{pk}_seq" + new_seq = "#{new_name}_#{pk}_seq" + execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}" + end end - rename_table_indexes(table_name, new_name) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index f74f966fd9..5287dd6a51 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -458,7 +458,7 @@ module ActiveRecord m.register_type "bytea", OID::Bytea.new m.register_type "point", OID::Point.new m.register_type "hstore", OID::Hstore.new - m.register_type "json", OID::Json.new + m.register_type "json", Type::Json.new m.register_type "jsonb", OID::Jsonb.new m.register_type "cidr", OID::Cidr.new m.register_type "inet", OID::Inet.new @@ -843,7 +843,6 @@ module ActiveRecord ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql) ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql) ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql) - ActiveRecord::Type.register(:json, OID::Json, adapter: :postgresql) ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql) ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql) ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql) diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 2ede92feff..b8fbb489b6 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -1,6 +1,6 @@ module ActiveRecord module ConnectionHandling - RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] } + RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence } DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" } # Establishes the connection to the database. Accepts a hash as input where diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 0ab03b2ab3..12ef58a941 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -95,8 +95,7 @@ module ActiveRecord module Enum def self.extended(base) # :nodoc: - base.class_attribute(:defined_enums, instance_writer: false) - base.defined_enums = {} + base.class_attribute(:defined_enums, instance_writer: false, default: {}) end def inherited(base) # :nodoc: @@ -154,11 +153,12 @@ module ActiveRecord definitions.each do |name, values| # statuses = { } enum_values = ActiveSupport::HashWithIndifferentAccess.new - name = name.to_sym + name = name.to_s # def self.statuses() statuses end - detect_enum_conflict!(name, name.to_s.pluralize, true) - klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values } + detect_enum_conflict!(name, name.pluralize, true) + singleton_class.send(:define_method, name.pluralize) { enum_values } + defined_enums[name] = enum_values detect_enum_conflict!(name, name) detect_enum_conflict!(name, "#{name}=") @@ -170,7 +170,7 @@ module ActiveRecord _enum_methods_module.module_eval do pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index - pairs.each do |value, i| + pairs.each do |label, value| if enum_prefix == true prefix = "#{name}_" elsif enum_prefix @@ -182,23 +182,23 @@ module ActiveRecord suffix = "_#{enum_suffix}" end - value_method_name = "#{prefix}#{value}#{suffix}" - enum_values[value] = i + value_method_name = "#{prefix}#{label}#{suffix}" + enum_values[label] = value + label = label.to_s - # def active?() status == 0 end + # def active?() status == "active" end klass.send(:detect_enum_conflict!, name, "#{value_method_name}?") - define_method("#{value_method_name}?") { self[attr] == value.to_s } + define_method("#{value_method_name}?") { self[attr] == label } - # def active!() update! status: :active end + # def active!() update!(status: 0) end klass.send(:detect_enum_conflict!, name, "#{value_method_name}!") define_method("#{value_method_name}!") { update!(attr => value) } - # scope :active, -> { where status: 0 } + # scope :active, -> { where(status: 0) } klass.send(:detect_enum_conflict!, name, value_method_name, true) klass.scope value_method_name, -> { where(attr => value) } end end - defined_enums[name.to_s] = enum_values end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index c19216702c..bad6542be2 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -878,20 +878,12 @@ module ActiveRecord included do class_attribute :fixture_path, instance_writer: false - class_attribute :fixture_table_names - class_attribute :fixture_class_names - class_attribute :use_transactional_tests - class_attribute :use_instantiated_fixtures # true, false, or :no_instances - class_attribute :pre_loaded_fixtures - class_attribute :config - - self.fixture_table_names = [] - self.use_instantiated_fixtures = false - self.pre_loaded_fixtures = false - self.config = ActiveRecord::Base - - self.fixture_class_names = {} - self.use_transactional_tests = true + 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 end module ClassMethods diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index fbdaeaae51..5776807507 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -38,8 +38,7 @@ module ActiveRecord included do # Determines whether to store the full constant name including namespace when using STI. # This is true, by default. - class_attribute :store_full_sti_class, instance_writer: false - self.store_full_sti_class = true + class_attribute :store_full_sti_class, instance_writer: false, default: true end module ClassMethods @@ -217,7 +216,7 @@ module ActiveRecord def subclass_from_attributes(attrs) attrs = attrs.to_h if attrs.respond_to?(:permitted?) if attrs.is_a?(Hash) - subclass_name = attrs.with_indifferent_access[inheritance_column] + subclass_name = attrs[inheritance_column] || attrs[inheritance_column.to_sym] if subclass_name.present? find_sti_class(subclass_name) diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb index ed652e26aa..cf954852bc 100644 --- a/activerecord/lib/active_record/integration.rb +++ b/activerecord/lib/active_record/integration.rb @@ -7,12 +7,11 @@ module ActiveRecord included do ## # :singleton-method: - # Indicates the format used to generate the timestamp in the cache key. - # Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>. + # Indicates the format used to generate the timestamp in the cache key, if + # versioning is off. Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>. # # This is +:usec+, by default. - class_attribute :cache_timestamp_format, instance_writer: false - self.cache_timestamp_format = :usec + class_attribute :cache_timestamp_format, instance_writer: false, default: :usec ## # :singleton-method: @@ -20,8 +19,7 @@ module ActiveRecord # by a changing version in the #cache_version method. # # This is +false+, by default until Rails 6.0. - class_attribute :cache_versioning, instance_writer: false - self.cache_versioning = false + class_attribute :cache_versioning, instance_writer: false, default: false end # Returns a +String+, which Action Pack uses for constructing a URL to this @@ -51,24 +49,16 @@ module ActiveRecord id && id.to_s # Be sure to stringify the id for routes end - # Returns a cache key that can be used to identify this record. + # Returns a stable cache key that can be used to identify this record. # # Product.new.cache_key # => "products/new" - # Product.find(5).cache_key # => "products/5" (updated_at not available) - # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available) - # - # You can also pass a list of named timestamps, and the newest in the list will be - # used to generate the key: - # - # Person.find(5).cache_key(:updated_at, :last_reviewed_at) + # Product.find(5).cache_key # => "products/5" # - # If ActiveRecord::Base.cache_versioning is turned on, no version will be included - # in the cache key. The version will instead be supplied by #cache_version. This - # separation enables recycling of cache keys. + # If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier, + # the cache key will also include a version. # - # Product.cache_versioning = true - # Product.new.cache_key # => "products/new" - # Person.find(5).cache_key # => "people/5" (even if updated_at available) + # Product.cache_versioning = false + # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available) def cache_key(*timestamp_names) if new_record? "#{model_name.cache_key}/new" @@ -77,6 +67,11 @@ module ActiveRecord "#{model_name.cache_key}/#{id}" else timestamp = if timestamp_names.any? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Specifying a timestamp name for #cache_key has been deprecated in favor of + the explicit #cache_version method that can be overwritten. + MSG + max_updated_column_timestamp(timestamp_names) else max_updated_column_timestamp @@ -99,7 +94,18 @@ module ActiveRecord # Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to # +false+ (which it is by default until Rails 6.0). def cache_version - try(:updated_at).try(:to_i) if cache_versioning + if cache_versioning && timestamp = try(:updated_at) + timestamp.utc.to_s(:usec) + end + end + + # Returns a cache key along with the version. + def cache_key_with_version + if version = cache_version + "#{cache_key}-#{version}" + else + cache_key + end end module ClassMethods diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 78ce9f8291..3c7110369b 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -51,8 +51,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :lock_optimistically, instance_writer: false - self.lock_optimistically = true + class_attribute :lock_optimistically, instance_writer: false, default: true end def locking_enabled? #:nodoc: diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 54216caaaf..1179a60e9b 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -1,3 +1,5 @@ +require "monitor" + module ActiveRecord module ModelSchema extend ActiveSupport::Concern @@ -128,30 +130,19 @@ module ActiveRecord included do mattr_accessor :primary_key_prefix_type, instance_writer: false - class_attribute :table_name_prefix, instance_writer: false - self.table_name_prefix = "" - - class_attribute :table_name_suffix, instance_writer: false - self.table_name_suffix = "" - - class_attribute :schema_migrations_table_name, instance_accessor: false - self.schema_migrations_table_name = "schema_migrations" - - class_attribute :internal_metadata_table_name, instance_accessor: false - self.internal_metadata_table_name = "ar_internal_metadata" - - class_attribute :protected_environments, instance_accessor: false - self.protected_environments = ["production"] - - class_attribute :pluralize_table_names, instance_writer: false - self.pluralize_table_names = true - - class_attribute :ignored_columns, instance_accessor: false - self.ignored_columns = [].freeze + class_attribute :table_name_prefix, instance_writer: false, default: "" + class_attribute :table_name_suffix, instance_writer: false, default: "" + class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations" + class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata" + class_attribute :protected_environments, instance_accessor: false, default: [ "production" ] + class_attribute :pluralize_table_names, instance_writer: false, default: true + class_attribute :ignored_columns, instance_accessor: false, default: [].freeze self.inheritance_column = "type" delegate :type_for_attribute, to: :class + + initialize_load_schema_monitor end # Derives the join table name for +first_table+ and +second_table+. The @@ -377,7 +368,7 @@ module ActiveRecord # default values when instantiating the Active Record object for this table. def column_defaults load_schema - _default_attributes.to_hash + @column_defaults ||= _default_attributes.to_hash end def _default_attributes # :nodoc: @@ -435,15 +426,27 @@ module ActiveRecord initialize_find_by_cache end + protected + + def initialize_load_schema_monitor + @load_schema_monitor = Monitor.new + end + private + def inherited(child_class) + super + child_class.initialize_load_schema_monitor + end + def schema_loaded? - defined?(@columns_hash) && @columns_hash + defined?(@schema_loaded) && @schema_loaded end def load_schema - unless schema_loaded? - load_schema! + return if schema_loaded? + @load_schema_monitor.synchronize do + load_schema! unless defined?(@columns_hash) && @columns_hash end end @@ -457,6 +460,8 @@ module ActiveRecord user_provided_default: false ) end + + @schema_loaded = true end def reload_schema_from_cache @@ -466,10 +471,12 @@ module ActiveRecord @attribute_types = nil @content_columns = nil @default_attributes = nil + @column_defaults = nil @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column @attributes_builder = nil @columns = nil @columns_hash = nil + @schema_loaded = false @attribute_names = nil @yaml_encoder = nil direct_descendants.each do |descendant| diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 01ecd79b8f..917bc76993 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -10,8 +10,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :nested_attributes_options, instance_writer: false - self.nested_attributes_options = {} + class_attribute :nested_attributes_options, instance_writer: false, default: {} end # = Active Record Nested Attributes @@ -458,7 +457,7 @@ module ActiveRecord end unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) - raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" + raise ArgumentError, "Hash or Array expected for attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" end check_record_limit!(options[:limit], attributes_collection) diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index c4a22398f0..b16e178358 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -8,7 +8,7 @@ module ActiveRecord delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all delegate :find_each, :find_in_batches, :in_batches, to: :all delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or, - :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, + :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending, :having, :create_with, :distinct, :references, :none, :unscope, :merge, to: :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all delegate :pluck, :ids, to: :all diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb index 6274996ab8..af6473d250 100644 --- a/activerecord/lib/active_record/readonly_attributes.rb +++ b/activerecord/lib/active_record/readonly_attributes.rb @@ -3,8 +3,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :_attr_readonly, instance_accessor: false - self._attr_readonly = [] + class_attribute :_attr_readonly, instance_accessor: false, default: [] end module ClassMethods diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 1a9e0a4a40..e8ee8279fd 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -8,10 +8,8 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :_reflections, instance_writer: false - class_attribute :aggregate_reflections, instance_writer: false - self._reflections = {} - self.aggregate_reflections = {} + class_attribute :_reflections, instance_writer: false, default: {} + class_attribute :aggregate_reflections, instance_writer: false, default: {} end def self.create(macro, name, scope, options, ar) @@ -199,7 +197,7 @@ module ActiveRecord def klass_join_scope(table, predicate_builder) # :nodoc: if klass.current_scope klass.current_scope.clone.tap { |scope| - scope.joins_values = [] + scope.joins_values = scope.left_outer_joins_values = [].freeze } else relation = ActiveRecord::Relation.create( @@ -289,6 +287,11 @@ module ActiveRecord JoinKeys.new(join_pk(association_klass), join_fk) end + protected + def actual_source_reflection # FIXME: this is a horrible name + self + end + private def join_pk(_) @@ -581,11 +584,9 @@ module ActiveRecord seed + [self] end - protected - - def actual_source_reflection # FIXME: this is a horrible name - self - end + def extensions + Array(options[:extend]) + end private @@ -759,7 +760,6 @@ module ActiveRecord # Holds all the metadata about a :through association as it was specified # in the Active Record class. class ThroughReflection < AbstractReflection #:nodoc: - attr_reader :delegate_reflection delegate :foreign_key, :foreign_type, :association_foreign_key, :active_record_primary_key, :type, :get_join_keys, to: :source_reflection @@ -985,19 +985,23 @@ module ActiveRecord collect_join_reflections(seed + [self]) end - def collect_join_reflections(seed) - a = source_reflection.add_as_source seed - if options[:source_type] - through_reflection.add_as_polymorphic_through self, a - else - through_reflection.add_as_through a + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + attr_reader :delegate_reflection + + def actual_source_reflection # FIXME: this is a horrible name + source_reflection.actual_source_reflection end - end private - - def actual_source_reflection # FIXME: this is a horrible name - source_reflection.send(:actual_source_reflection) + def collect_join_reflections(seed) + a = source_reflection.add_as_source seed + if options[:source_type] + through_reflection.add_as_polymorphic_through self, a + else + through_reflection.add_as_through a + end end def primary_key(klass) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 333ad16e11..7a8f9abb36 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -2,7 +2,7 @@ module ActiveRecord # = Active Record \Relation class Relation MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, - :order, :joins, :left_joins, :left_outer_joins, :references, + :order, :joins, :left_outer_joins, :references, :extending, :unscope] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, @@ -18,6 +18,7 @@ module ActiveRecord attr_reader :table, :klass, :loaded, :predicate_builder alias :model :klass alias :loaded? :loaded + alias :locked? :locked def initialize(klass, table, predicate_builder, values = {}) @klass = klass @@ -403,9 +404,9 @@ module ActiveRecord # # Note: Updating a large number of records will run an # UPDATE query for each record, which may cause a performance - # issue. So if it is not needed to run callbacks for each update, it is - # preferred to use #update_all for updating all records using - # a single query. + # issue. When running callbacks is not needed for each record update, + # it is preferred to use #update_all for updating all records + # in a single query. def update(id = :all, attributes) if id.is_a?(Array) id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) } diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 76031515fd..13a2c3f511 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -30,14 +30,14 @@ module ActiveRecord # end # # ==== Options - # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000. + # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000. # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value. # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value. # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when - # an order is present in the relation. + # an order is present in the relation. # # Limits are honored, and if present there is no requirement for the batch - # size, it can be less than, equal, or greater than the limit. + # size: it can be less than, equal to, or greater than the limit. # # The options +start+ and +finish+ are especially useful if you want # multiple workers dealing with the same processing queue. You can make @@ -89,14 +89,14 @@ module ActiveRecord # To be yielded each record one by one, use #find_each instead. # # ==== Options - # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000. + # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000. # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value. # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value. # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when - # an order is present in the relation. + # an order is present in the relation. # # Limits are honored, and if present there is no requirement for the batch - # size, it can be less than, equal, or greater than the limit. + # size: it can be less than, equal to, or greater than the limit. # # The options +start+ and +finish+ are especially useful if you want # multiple workers dealing with the same processing queue. You can make @@ -140,9 +140,9 @@ module ActiveRecord # If you do not provide a block to #in_batches, it will return a # BatchEnumerator which is enumerable. # - # Person.in_batches.with_index do |relation, batch_index| + # Person.in_batches.each_with_index do |relation, batch_index| # puts "Processing relation ##{batch_index}" - # relation.each { |relation| relation.delete_all } + # relation.delete_all # end # # Examples of calling methods on the returned BatchEnumerator object: @@ -152,12 +152,12 @@ module ActiveRecord # Person.in_batches.each_record(&:party_all_night!) # # ==== Options - # * <tt>:of</tt> - Specifies the size of the batch. Default to 1000. - # * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false. + # * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000. + # * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false. # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value. # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value. # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when - # an order is present in the relation. + # an order is present in the relation. # # Limits are honored, and if present there is no requirement for the batch # size, it can be less than, equal, or greater than the limit. @@ -186,7 +186,7 @@ module ActiveRecord # # NOTE: It's not possible to set the order. That is automatically set to # ascending on the primary key ("id ASC") to make the batch ordering - # consistent. Therefore the primary key must be orderable, e.g an integer + # consistent. Therefore the primary key must be orderable, e.g. an integer # or a string. # # NOTE: By its nature, batch processing is subject to race conditions if diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 861b2125d4..c562f214c9 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -37,7 +37,16 @@ module ActiveRecord # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ # between databases. In invalid cases, an error from the database is thrown. def count(column_name = nil) - return super() if block_given? + if block_given? + unless column_name.nil? + ActiveSupport::Deprecation.warn \ + "When `count' is called with a block, it ignores other arguments. " \ + "This behavior is now deprecated and will result in an ArgumentError in Rails 5.3." + end + + return super() + end + calculate(:count, column_name) end @@ -73,7 +82,16 @@ module ActiveRecord # # Person.sum(:age) # => 4562 def sum(column_name = nil) - return super() if block_given? + if block_given? + unless column_name.nil? + ActiveSupport::Deprecation.warn \ + "When `sum' is called with a block, it ignores other arguments. " \ + "This behavior is now deprecated and will result in an ArgumentError in Rails 5.3." + end + + return super() + end + calculate(:sum, column_name) end diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 257ae04ff4..dada0fddf8 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -25,8 +25,6 @@ module ActiveRecord def inherited(child_class) child_class.initialize_relation_delegate_cache - delegate = child_class.relation_delegate_class(ActiveRecord::Associations::CollectionProxy) - delegate.include ActiveRecord::Associations::CollectionProxy::DelegateExtending super end end @@ -46,6 +44,8 @@ module ActiveRecord delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :columns_hash, to: :klass + delegate :ast, :locked, to: :arel + module ClassSpecificRelation # :nodoc: extend ActiveSupport::Concern diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 183fe91c05..7dea5deec5 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -1,12 +1,3 @@ -require "active_record/relation/predicate_builder/array_handler" -require "active_record/relation/predicate_builder/base_handler" -require "active_record/relation/predicate_builder/basic_object_handler" -require "active_record/relation/predicate_builder/range_handler" -require "active_record/relation/predicate_builder/relation_handler" - -require "active_record/relation/predicate_builder/association_query_value" -require "active_record/relation/predicate_builder/polymorphic_array_value" - module ActiveRecord class PredicateBuilder # :nodoc: delegate :resolve_column_aliases, to: :table @@ -116,21 +107,26 @@ module ActiveRecord first = value.begin last = value.end unless first.respond_to?(:infinite?) && first.infinite? - binds << build_bind_param(column_name, first) + binds << build_bind_attribute(column_name, first) first = Arel::Nodes::BindParam.new end unless last.respond_to?(:infinite?) && last.infinite? - binds << build_bind_param(column_name, last) + binds << build_bind_attribute(column_name, last) last = Arel::Nodes::BindParam.new end result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?) + when value.is_a?(Relation) + binds.concat(value.bound_attributes) else if can_be_bound?(column_name, value) - result[column_name] = Arel::Nodes::BindParam.new - binds << build_bind_param(column_name, value) - elsif value.is_a?(Relation) - binds.concat(value.bound_attributes) + bind_attribute = build_bind_attribute(column_name, value) + if value.is_a?(StatementCache::Substitute) || !bind_attribute.value_for_database.nil? + result[column_name] = Arel::Nodes::BindParam.new + binds << bind_attribute + else + result[column_name] = nil + end end end end @@ -173,8 +169,17 @@ module ActiveRecord end end - def build_bind_param(column_name, value) + def build_bind_attribute(column_name, value) Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) end end end + +require "active_record/relation/predicate_builder/array_handler" +require "active_record/relation/predicate_builder/base_handler" +require "active_record/relation/predicate_builder/basic_object_handler" +require "active_record/relation/predicate_builder/range_handler" +require "active_record/relation/predicate_builder/relation_handler" + +require "active_record/relation/predicate_builder/association_query_value" +require "active_record/relation/predicate_builder/polymorphic_array_value" 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 2fe0f81cab..3e19646ae5 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 @@ -1,8 +1,6 @@ module ActiveRecord class PredicateBuilder class AssociationQueryValue # :nodoc: - attr_reader :associated_table, :value - def initialize(associated_table, value) @associated_table = associated_table @value = value @@ -12,6 +10,11 @@ module ActiveRecord [associated_table.association_foreign_key.to_s => ids] 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 :associated_table, :value + private def ids case value diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb index 9bb2f8c8dc..7029ae5f47 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb @@ -1,8 +1,6 @@ module ActiveRecord class PredicateBuilder class PolymorphicArrayValue # :nodoc: - attr_reader :associated_table, :values - def initialize(associated_table, values) @associated_table = associated_table @values = values @@ -17,6 +15,11 @@ module ActiveRecord end 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 :associated_table, :values + private def type_to_ids_mapping default_hash = Hash.new { |hsh, key| hsh[key] = [] } diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 76e529f2de..79e65baae5 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1100,14 +1100,16 @@ module ActiveRecord end VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC, - "asc", "desc", "ASC", "DESC"] # :nodoc: + "asc", "desc", "ASC", "DESC"].to_set # :nodoc: def validate_order_args(args) args.each do |arg| next unless arg.is_a?(Hash) arg.each do |_key, value| - raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \ - "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value) + unless VALID_DIRECTIONS.include?(value) + raise ArgumentError, + "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}" + end end end end diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index 04bee73e8f..b862dd56a5 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -57,7 +57,7 @@ module ActiveRecord else column = klass.column_for_attribute(attribute) - binds << predicate_builder.send(:build_bind_param, attribute, value) + binds << predicate_builder.send(:build_bind_attribute, attribute, value) value = Arel::Nodes::BindParam.new predicate = if options[:case_sensitive] diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 2daa48859a..70b2693b28 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -5,11 +5,8 @@ module ActiveRecord included do # Stores the default scope for the class. - class_attribute :default_scopes, instance_writer: false, instance_predicate: false - class_attribute :default_scope_override, instance_writer: false, instance_predicate: false - - self.default_scopes = [] - self.default_scope_override = nil + class_attribute :default_scopes, instance_writer: false, instance_predicate: false, default: [] + class_attribute :default_scope_override, instance_writer: false, instance_predicate: false, default: nil end module ClassMethods @@ -110,7 +107,11 @@ module ActiveRecord if default_scope_override # The user has defined their own default scope method, so call that - evaluate_default_scope { default_scope } + evaluate_default_scope do + if scope = default_scope + (base_rel ||= relation).merge(scope) + end + end elsif default_scopes.any? base_rel ||= relation evaluate_default_scope do diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 27cdf8cb7e..a61fdd6454 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -30,12 +30,15 @@ module ActiveRecord end def default_scoped # :nodoc: - scope = build_default_scope + scope = relation + build_default_scope(scope) || scope + end - if scope - relation.spawn.merge!(scope) + def default_extensions # :nodoc: + if scope = current_scope || build_default_scope + scope.extensions else - relation + [] end end @@ -156,17 +159,17 @@ module ActiveRecord if body.respond_to?(:to_proc) singleton_class.send(:define_method, name) do |*args| - scope = all.scoping { instance_exec(*args, &body) } + scope = all + scope = scope.scoping { instance_exec(*args, &body) || scope } scope = scope.extending(extension) if extension - - scope || all + scope end else singleton_class.send(:define_method, name) do |*args| - scope = all.scoping { body.call(*args) } + scope = all + scope = scope.scoping { body.call(*args) || scope } scope = scope.extending(extension) if extension - - scope || all + scope end end end diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index c53dee43d7..ba686fc562 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -71,9 +71,9 @@ module ActiveRecord @tasks[pattern] = task end - register_task(/mysql/, 'ActiveRecord::Tasks::MySQLDatabaseTasks') - register_task(/postgresql/, 'ActiveRecord::Tasks::PostgreSQLDatabaseTasks') - register_task(/sqlite/, 'ActiveRecord::Tasks::SQLiteDatabaseTasks') + register_task(/mysql/, "ActiveRecord::Tasks::MySQLDatabaseTasks") + register_task(/postgresql/, "ActiveRecord::Tasks::PostgreSQLDatabaseTasks") + register_task(/sqlite/, "ActiveRecord::Tasks::SQLiteDatabaseTasks") def db_dir @db_dir ||= Rails.application.config.paths["db"].first diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb index 7043d2f0c8..01562b21e9 100644 --- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb @@ -42,7 +42,7 @@ module ActiveRecord ignore_tables = ActiveRecord::SchemaDumper.ignore_tables if ignore_tables.any? - condition = ignore_tables.map { |table| connection.quote_table_name(table) }.join(", ") + condition = ignore_tables.map { |table| connection.quote(table) }.join(", ") args << "SELECT sql FROM sqlite_master WHERE tbl_name NOT IN (#{condition}) ORDER BY tbl_name, type DESC, name" else args << ".schema" diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 09d8d1cdd4..55f3a194a9 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -43,8 +43,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :record_timestamps - self.record_timestamps = true + class_attribute :record_timestamps, default: true end def initialize_dup(other) # :nodoc: diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index 4f632660a8..9ed6c95bf9 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -1,11 +1,11 @@ require "active_model/type" -require "active_record/type/internal/abstract_json" require "active_record/type/internal/timezone" require "active_record/type/date" require "active_record/type/date_time" require "active_record/type/decimal_without_scale" +require "active_record/type/json" require "active_record/type/time" require "active_record/type/text" require "active_record/type/unsigned_integer" @@ -69,6 +69,7 @@ module ActiveRecord register(:decimal, Type::Decimal, override: false) register(:float, Type::Float, override: false) register(:integer, Type::Integer, override: false) + register(:json, Type::Json, override: false) register(:string, Type::String, override: false) register(:text, Type::Text, override: false) register(:time, Type::Time, override: false) diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb deleted file mode 100644 index e19c5a14da..0000000000 --- a/activerecord/lib/active_record/type/internal/abstract_json.rb +++ /dev/null @@ -1,33 +0,0 @@ -module ActiveRecord - module Type - module Internal # :nodoc: - class AbstractJson < ActiveModel::Type::Value # :nodoc: - include ActiveModel::Type::Helpers::Mutable - - def type - :json - end - - def deserialize(value) - if value.is_a?(::String) - ::ActiveSupport::JSON.decode(value) rescue nil - else - value - end - end - - def serialize(value) - if value.nil? - nil - else - ::ActiveSupport::JSON.encode(value) - end - end - - def accessor - ActiveRecord::Store::StringKeyedHashAccessor - end - end - end - end -end diff --git a/activerecord/lib/active_record/type/json.rb b/activerecord/lib/active_record/type/json.rb new file mode 100644 index 0000000000..c4732fe388 --- /dev/null +++ b/activerecord/lib/active_record/type/json.rb @@ -0,0 +1,28 @@ +module ActiveRecord + module Type + class Json < ActiveModel::Type::Value + include ActiveModel::Type::Helpers::Mutable + + def type + :json + end + + def deserialize(value) + return value unless value.is_a?(::String) + ActiveSupport::JSON.decode(value) rescue nil + end + + def serialize(value) + ActiveSupport::JSON.encode(value) unless value.nil? + end + + def changed_in_place?(raw_old_value, new_value) + deserialize(raw_old_value) != new_value + end + + def accessor + ActiveRecord::Store::StringKeyedHashAccessor + end + end + end +end diff --git a/activerecord/lib/rails/generators/active_record.rb b/activerecord/lib/rails/generators/active_record.rb index 68fca44e3b..a79b8eafea 100644 --- a/activerecord/lib/rails/generators/active_record.rb +++ b/activerecord/lib/rails/generators/active_record.rb @@ -10,7 +10,7 @@ module ActiveRecord # Set the current directory as base for the inherited generators. def self.base_root - File.dirname(__FILE__) + __dir__ end end end diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb index 6954006003..26c69edc7b 100644 --- a/activerecord/test/cases/adapters/mysql2/json_test.rb +++ b/activerecord/test/cases/adapters/mysql2/json_test.rb @@ -1,195 +1,22 @@ require "cases/helper" -require "support/schema_dumping_helper" +require "cases/json_shared_test_cases" if ActiveRecord::Base.connection.supports_json? class Mysql2JSONTest < ActiveRecord::Mysql2TestCase - include SchemaDumpingHelper + include JSONSharedTestCases self.use_transactional_tests = false - class JsonDataType < ActiveRecord::Base - self.table_name = "json_data_type" - - store_accessor :settings, :resolution - end - def setup - @connection = ActiveRecord::Base.connection - begin - @connection.create_table("json_data_type") do |t| - t.json "payload" - t.json "settings" - end + super + @connection.create_table("json_data_type") do |t| + t.json "payload" + t.json "settings" end end - def teardown - @connection.drop_table :json_data_type, if_exists: true - JsonDataType.reset_column_information - end - - def test_column - column = JsonDataType.columns_hash["payload"] - assert_equal :json, column.type - assert_equal "json", column.sql_type - - type = JsonDataType.type_for_attribute("payload") - assert_not type.binary? - end - - def test_change_table_supports_json - @connection.change_table("json_data_type") do |t| - t.json "users" + private + def column_type + :json end - JsonDataType.reset_column_information - column = JsonDataType.columns_hash["users"] - assert_equal :json, column.type - end - - def test_schema_dumping - output = dump_table_schema("json_data_type") - assert_match(/t\.json\s+"settings"/, output) - end - - def test_cast_value_on_write - x = JsonDataType.new payload: { "string" => "foo", :symbol => :bar } - assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast) - assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload) - x.save - assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload) - end - - def test_type_cast_json - type = JsonDataType.type_for_attribute("payload") - - data = "{\"a_key\":\"a_value\"}" - hash = type.deserialize(data) - assert_equal({ "a_key" => "a_value" }, hash) - assert_equal({ "a_key" => "a_value" }, type.deserialize(data)) - - assert_equal({}, type.deserialize("{}")) - assert_equal({ "key" => nil }, type.deserialize('{"key": null}')) - assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"}))) - end - - def test_rewrite - @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" - x = JsonDataType.first - x.payload = { '"a\'' => "b" } - assert x.save! - end - - def test_select - @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" - x = JsonDataType.first - assert_equal({ "k" => "v" }, x.payload) - end - - def test_select_multikey - @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')| - x = JsonDataType.first - assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload) - end - - def test_null_json - @connection.execute "insert into json_data_type (payload) VALUES(null)" - x = JsonDataType.first - assert_nil(x.payload) - end - - def test_select_array_json_value - @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| - x = JsonDataType.first - assert_equal(["v0", { "k1" => "v1" }], x.payload) - end - - def test_select_nil_json_after_create - json = JsonDataType.create(payload: nil) - x = JsonDataType.where(payload: nil).first - assert_equal(json, x) - end - - def test_select_nil_json_after_update - json = JsonDataType.create(payload: "foo") - x = JsonDataType.where(payload: nil).first - assert_nil(x) - - json.update_attributes payload: nil - x = JsonDataType.where(payload: nil).first - assert_equal(json.reload, x) - end - - def test_rewrite_array_json_value - @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| - x = JsonDataType.first - x.payload = ["v1", { "k2" => "v2" }, "v3"] - assert x.save! - end - - def test_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution - - x.save! - x = JsonDataType.first - assert_equal "320×480", x.resolution - - x.resolution = "640×1136" - x.save! - - x = JsonDataType.first - assert_equal "640×1136", x.resolution - end - - def test_duplication_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution - - y = x.dup - assert_equal "320×480", y.resolution - end - - def test_yaml_round_trip_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution - - y = YAML.load(YAML.dump(x)) - assert_equal "320×480", y.resolution - end - - def test_changes_in_place - json = JsonDataType.new - assert_not json.changed? - - json.payload = { "one" => "two" } - assert json.changed? - assert json.payload_changed? - - json.save! - assert_not json.changed? - - json.payload["three"] = "four" - assert json.payload_changed? - - json.save! - json.reload - - assert_equal({ "one" => "two", "three" => "four" }, json.payload) - assert_not json.changed? - end - - def test_assigning_string_literal - json = JsonDataType.create(payload: "foo") - assert_equal "foo", json.payload - end - - def test_assigning_number - json = JsonDataType.create(payload: 1.234) - assert_equal 1.234, json.payload - end - - def test_assigning_boolean - json = JsonDataType.create(payload: true) - assert_equal true, json.payload - end end end diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index 99175e8091..539c90f0bc 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -96,7 +96,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase end def test_write_binary - data = File.read(File.join(File.dirname(__FILE__), "..", "..", "..", "assets", "example.log")) + data = File.read(File.join(__dir__, "..", "..", "..", "assets", "example.log")) assert(data.size > 1) record = ByteaDataType.create(payload: data) assert_not record.new_record? diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 3deb007513..32afe331fa 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -31,15 +31,21 @@ module ActiveRecord end def test_encoding - assert_not_nil @connection.encoding + assert_queries(1) do + assert_not_nil @connection.encoding + end end def test_collation - assert_not_nil @connection.collation + assert_queries(1) do + assert_not_nil @connection.collation + end end def test_ctype - assert_not_nil @connection.ctype + assert_queries(1) do + assert_not_nil @connection.ctype + end end def test_default_client_min_messages diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index d4e627001c..6aa60630c2 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -1,223 +1,36 @@ require "cases/helper" -require "support/schema_dumping_helper" +require "cases/json_shared_test_cases" module PostgresqlJSONSharedTestCases - include SchemaDumpingHelper - - class JsonDataType < ActiveRecord::Base - self.table_name = "json_data_type" - - store_accessor :settings, :resolution - end + include JSONSharedTestCases def setup - @connection = ActiveRecord::Base.connection - begin - @connection.create_table("json_data_type") do |t| - t.public_send column_type, "payload", default: {} # t.json 'payload', default: {} - t.public_send column_type, "settings" # t.json 'settings' - t.public_send column_type, "objects", array: true # t.json 'objects', array: true - end - rescue ActiveRecord::StatementInvalid - skip "do not test on PostgreSQL without #{column_type} type." + super + @connection.create_table("json_data_type") do |t| + t.public_send column_type, "payload", default: {} # t.json 'payload', default: {} + t.public_send column_type, "settings" # t.json 'settings' + t.public_send column_type, "objects", array: true # t.json 'objects', array: true end - end - - def teardown - @connection.drop_table :json_data_type, if_exists: true - JsonDataType.reset_column_information - end - - def test_column - column = JsonDataType.columns_hash["payload"] - assert_equal column_type, column.type - assert_equal column_type.to_s, column.sql_type - assert_not column.array? - - type = JsonDataType.type_for_attribute("payload") - assert_not type.binary? + rescue ActiveRecord::StatementInvalid + skip "do not test on PostgreSQL without #{column_type} type." end def test_default @connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] } - JsonDataType.reset_column_information + 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) - ensure - JsonDataType.reset_column_information - end - - def test_change_table_supports_json - @connection.transaction do - @connection.change_table("json_data_type") do |t| - t.public_send column_type, "users", default: "{}" # t.json 'users', default: '{}' - end - JsonDataType.reset_column_information - column = JsonDataType.columns_hash["users"] - assert_equal column_type, column.type - - raise ActiveRecord::Rollback # reset the schema change - end - ensure - JsonDataType.reset_column_information - end - - def test_schema_dumping - output = dump_table_schema("json_data_type") - assert_match(/t\.#{column_type.to_s}\s+"payload",\s+default: {}/, output) - end - - def test_cast_value_on_write - x = JsonDataType.new payload: { "string" => "foo", :symbol => :bar } - assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast) - assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload) - x.save - assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload) end def test_deserialize_with_array - x = JsonDataType.new(objects: ["foo" => "bar"]) + x = klass.new(objects: ["foo" => "bar"]) assert_equal ["foo" => "bar"], x.objects x.save! assert_equal ["foo" => "bar"], x.objects x.reload assert_equal ["foo" => "bar"], x.objects end - - def test_type_cast_json - type = JsonDataType.type_for_attribute("payload") - - data = "{\"a_key\":\"a_value\"}" - hash = type.deserialize(data) - assert_equal({ "a_key" => "a_value" }, hash) - assert_equal({ "a_key" => "a_value" }, type.deserialize(data)) - - assert_equal({}, type.deserialize("{}")) - assert_equal({ "key" => nil }, type.deserialize('{"key": null}')) - assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"}))) - end - - def test_rewrite - @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" - x = JsonDataType.first - x.payload = { '"a\'' => "b" } - assert x.save! - end - - def test_select - @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')" - x = JsonDataType.first - assert_equal({ "k" => "v" }, x.payload) - end - - def test_select_multikey - @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')| - x = JsonDataType.first - assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload) - end - - def test_null_json - @connection.execute "insert into json_data_type (payload) VALUES(null)" - x = JsonDataType.first - assert_nil(x.payload) - end - - def test_select_nil_json_after_create - json = JsonDataType.create(payload: nil) - x = JsonDataType.where(payload: nil).first - assert_equal(json, x) - end - - def test_select_nil_json_after_update - json = JsonDataType.create(payload: "foo") - x = JsonDataType.where(payload: nil).first - assert_nil(x) - - json.update_attributes payload: nil - x = JsonDataType.where(payload: nil).first - assert_equal(json.reload, x) - end - - def test_select_array_json_value - @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| - x = JsonDataType.first - assert_equal(["v0", { "k1" => "v1" }], x.payload) - end - - def test_rewrite_array_json_value - @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')| - x = JsonDataType.first - x.payload = ["v1", { "k2" => "v2" }, "v3"] - assert x.save! - end - - def test_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution - - x.save! - x = JsonDataType.first - assert_equal "320×480", x.resolution - - x.resolution = "640×1136" - x.save! - - x = JsonDataType.first - assert_equal "640×1136", x.resolution - end - - def test_duplication_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution - - y = x.dup - assert_equal "320×480", y.resolution - end - - def test_yaml_round_trip_with_store_accessors - x = JsonDataType.new(resolution: "320×480") - assert_equal "320×480", x.resolution - - y = YAML.load(YAML.dump(x)) - assert_equal "320×480", y.resolution - end - - def test_changes_in_place - json = JsonDataType.new - assert_not json.changed? - - json.payload = { "one" => "two" } - assert json.changed? - assert json.payload_changed? - - json.save! - assert_not json.changed? - - json.payload["three"] = "four" - assert json.payload_changed? - - json.save! - json.reload - - assert_equal({ "one" => "two", "three" => "four" }, json.payload) - assert_not json.changed? - end - - def test_assigning_string_literal - json = JsonDataType.create(payload: "foo") - assert_equal "foo", json.payload - end - - def test_assigning_number - json = JsonDataType.create(payload: 1.234) - assert_equal 1.234, json.payload - end - - def test_assigning_boolean - json = JsonDataType.create(payload: true) - assert_equal true, json.payload - end end class PostgresqlJSONTest < ActiveRecord::PostgreSQLTestCase diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index bf570176f4..f86a76e08a 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -75,17 +75,6 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase end end - def test_schema_uniqueness - assert_nothing_raised do - set_session_auth - USERS.each do |u| - set_session_auth u - assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = 1") - set_session_auth - end - end - end - def test_sequence_schema_caching assert_nothing_raised do USERS.each do |u| diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 52e4a38cae..6ebe9d82a7 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -63,6 +63,16 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase UUIDType.reset_column_information end + def test_add_column_with_null_true_and_default_nil + assert_nothing_raised do + connection.add_column :uuid_data_type, :thingy, :uuid, null: true, default: nil + end + UUIDType.reset_column_information + column = UUIDType.columns_hash["thingy"] + assert column.null + assert_nil column.default + end + def test_data_type_of_uuid_types column = UUIDType.columns_hash["guid"] assert_equal :uuid, column.type diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index 7721bd5cd9..f9d1e44595 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -128,7 +128,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase assert ar.developers_log.empty? alice = Developer.new(name: "alice") ar.developers_with_callbacks << alice - assert_equal"after_adding#{alice.id}", ar.developers_log.last + assert_equal "after_adding#{alice.id}", ar.developers_log.last bob = ar.developers_with_callbacks.create(name: "bob") assert_equal "after_adding#{bob.id}", ar.developers_log.last diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 36238dd088..4271a09c9b 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1094,12 +1094,6 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal authors(:david), assert_no_queries { posts[0].author } posts = assert_queries(2) do - Post.all.merge!(select: "distinct posts.*", includes: :author, joins: [:comments], where: "comments.body like 'Thank you%'", order: "posts.id").to_a - end - assert_equal [posts(:welcome)], posts - assert_equal authors(:david), assert_no_queries { posts[0].author } - - posts = assert_queries(2) do Post.all.merge!(includes: :author, joins: { taggings: :tag }, where: "tags.name = 'General'", order: "posts.id").to_a end assert_equal posts(:welcome, :thinking), posts diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index 87d842f21d..f707a170f5 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -78,6 +78,12 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase assert_equal post.association(:comments), post.comments.where("1=1").the_association end + def test_association_with_default_scope + assert_raises OopsError do + posts(:welcome).comments.destroy_all + end + end + private def extend!(model) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 8060790594..f73005b3cb 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -367,19 +367,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated end - def test_create_by_new_record - devel = Developer.new(name: "Marcel", salary: 75000) - devel.projects.build(name: "Make bed") - proj2 = devel.projects.build(name: "Lie in it") - assert_equal devel.projects.last, proj2 - assert !proj2.persisted? - devel.save - assert devel.persisted? - assert proj2.persisted? - assert_equal devel.projects.last, proj2 - assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated - end - def test_creation_respects_hash_condition # in Oracle '' is saved as null therefore need to save ' ' in not null column post = categories(:general).post_with_conditions.build(body: " ") @@ -954,7 +941,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_not_nil Developer._reflections["shared_computers"] # Checking the fixture for named association is important here, because it's the only way # we've been able to reproduce this bug - assert_not_nil File.read(File.expand_path("../../../fixtures/developers.yml", __FILE__)).index("shared_computers") + assert_not_nil File.read(File.expand_path("../../fixtures/developers.yml", __dir__)).index("shared_computers") assert_equal developers(:david).shared_computers.first, computers(:laptop) end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 6a479a344c..590d3642bd 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1355,7 +1355,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase Client.create(client_of: firm.id, name: "SmallTime Inc.") # only one of two clients is included in the association due to the :conditions key assert_equal 2, Client.where(client_of: firm.id).size - assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size + assert_equal 1, firm.dependent_hash_conditional_clients_of_firm.size firm.destroy # only the correctly associated client should have been deleted assert_equal 1, Client.where(client_of: firm.id).size @@ -2251,7 +2251,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase test "association with extend option with multiple extensions" do post = posts(:welcome) assert_equal "lifo", post.comments_with_extend_2.author - assert_equal "hello", post.comments_with_extend_2.greeting + assert_equal "hullo", post.comments_with_extend_2.greeting + end + + test "extend option affects per association" do + post = posts(:welcome) + assert_equal "lifo", post.comments_with_extend.author + assert_equal "lifo", post.comments_with_extend_2.author + assert_equal "hello", post.comments_with_extend.greeting + assert_equal "hullo", post.comments_with_extend_2.greeting end test "delete record with complex joins" do 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 ea52fb5a67..9156f6d57a 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -64,10 +64,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase club1.members.sort_by(&:id) end - def make_model(name) - Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } - end - def test_ordered_has_many_through person_prime = Class.new(ActiveRecord::Base) do def self.name; "Person"; end @@ -152,20 +148,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert after_destroy_called, "after destroy should be called" end - def make_no_pk_hm_t - lesson = make_model "Lesson" - student = make_model "Student" - - lesson_student = make_model "LessonStudent" - lesson_student.table_name = "lessons_students" - - lesson_student.belongs_to :lesson, anonymous_class: lesson - lesson_student.belongs_to :student, anonymous_class: student - lesson.has_many :lesson_students, anonymous_class: lesson_student - lesson.has_many :students, through: :lesson_students, anonymous_class: student - [lesson, lesson_student, student] - end - def test_pk_is_not_required_for_join post = Post.includes(:scategories).first post2 = Post.includes(:categories).first @@ -1252,4 +1234,23 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase ) end end + + private + def make_model(name) + Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } + end + + def make_no_pk_hm_t + lesson = make_model "Lesson" + student = make_model "Student" + + lesson_student = make_model "LessonStudent" + lesson_student.table_name = "lessons_students" + + lesson_student.belongs_to :lesson, anonymous_class: lesson + lesson_student.belongs_to :student, anonymous_class: student + lesson.has_many :lesson_students, anonymous_class: lesson_student + lesson.has_many :students, through: :lesson_students, anonymous_class: student + [lesson, lesson_student, student] + end end diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 287b3e9ebc..467cc73ecd 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -651,20 +651,6 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance" end - def test_child_instance_should_be_shared_with_replaced_via_method_parent - face = faces(:confused) - new_man = Man.new - - assert_not_nil face.polymorphic_man - face.polymorphic_man = new_man - - assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same before changes to parent instance" - face.description = "Bongo" - assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to parent instance" - new_man.polymorphic_face.description = "Mungo" - assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance" - end - def test_inversed_instance_should_not_be_reloaded_after_stale_state_changed new_man = Man.new face = Face.new diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 4ab690bfc6..2eb31326a5 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -220,6 +220,18 @@ class AssociationProxyTest < ActiveRecord::TestCase assert_equal david.projects, david.projects.scope end + test "proxy object is cached" do + david = developers(:david) + assert_same david.projects, david.projects + end + + test "proxy object can be stubbed" do + david = developers(:david) + david.projects.define_singleton_method(:extra_method) { 42 } + + assert_equal 42, david.projects.extra_method + end + test "inverses get set of subsets of the association" do man = Man.create man.interests.create diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 15c253890b..dc32e995a4 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -907,80 +907,6 @@ class BasicsTest < ActiveRecord::TestCase end end - class NumericData < ActiveRecord::Base - self.table_name = "numeric_data" - - attribute :my_house_population, :integer - attribute :atoms_in_universe, :integer - end - - def test_big_decimal_conditions - m = NumericData.new( - bank_balance: 1586.43, - big_bank_balance: BigDecimal("1000234000567.95"), - world_population: 6000000000, - my_house_population: 3 - ) - assert m.save - assert_equal 0, NumericData.where("bank_balance > ?", 2000.0).count - end - - def test_numeric_fields - m = NumericData.new( - bank_balance: 1586.43, - big_bank_balance: BigDecimal("1000234000567.95"), - world_population: 6000000000, - my_house_population: 3 - ) - assert m.save - - m1 = NumericData.find(m.id) - assert_not_nil m1 - - # As with migration_test.rb, we should make world_population >= 2**62 - # to cover 64-bit platforms and test it is a Bignum, but the main thing - # is that it's an Integer. - assert_kind_of Integer, m1.world_population - assert_equal 6000000000, m1.world_population - - assert_kind_of Integer, m1.my_house_population - assert_equal 3, m1.my_house_population - - assert_kind_of BigDecimal, m1.bank_balance - assert_equal BigDecimal("1586.43"), m1.bank_balance - - assert_kind_of BigDecimal, m1.big_bank_balance - assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance - end - - def test_numeric_fields_with_scale - m = NumericData.new( - bank_balance: 1586.43122334, - big_bank_balance: BigDecimal("234000567.952344"), - world_population: 6000000000, - my_house_population: 3 - ) - assert m.save - - m1 = NumericData.find(m.id) - assert_not_nil m1 - - # As with migration_test.rb, we should make world_population >= 2**62 - # to cover 64-bit platforms and test it is a Bignum, but the main thing - # is that it's an Integer. - assert_kind_of Integer, m1.world_population - assert_equal 6000000000, m1.world_population - - assert_kind_of Integer, m1.my_house_population - assert_equal 3, m1.my_house_population - - assert_kind_of BigDecimal, m1.bank_balance - assert_equal BigDecimal("1586.43"), m1.bank_balance - - assert_kind_of BigDecimal, m1.big_bank_balance - assert_equal BigDecimal("234000567.95"), m1.big_bank_balance - end - def test_auto_id auto = AutoId.new auto.save diff --git a/activerecord/test/cases/cache_key_test.rb b/activerecord/test/cases/cache_key_test.rb index f74cb18244..7b8264e6e8 100644 --- a/activerecord/test/cases/cache_key_test.rb +++ b/activerecord/test/cases/cache_key_test.rb @@ -39,5 +39,13 @@ module ActiveRecord assert CacheMeWithVersion.create.cache_version.present? assert_not CacheMe.create.cache_version.present? end + + test "cache_key_with_version always has both key and version" do + r1 = CacheMeWithVersion.create + assert_equal "active_record/cache_key_test/cache_me_with_versions/#{r1.id}-#{r1.updated_at.to_s(:usec)}", r1.cache_key_with_version + + r2 = CacheMe.create + assert_equal "active_record/cache_key_test/cache_mes/#{r2.id}-#{r2.updated_at.to_s(:usec)}", r2.cache_key_with_version + end end end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 3214d778d4..21c5c0efee 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -8,6 +8,7 @@ require "models/organization" require "models/possession" require "models/topic" require "models/reply" +require "models/numeric_data" require "models/minivan" require "models/speedometer" require "models/ship_part" @@ -17,14 +18,6 @@ require "models/post" require "models/comment" require "models/rating" -class NumericData < ActiveRecord::Base - self.table_name = "numeric_data" - - attribute :world_population, :integer - attribute :my_house_population, :integer - attribute :atoms_in_universe, :integer -end - class CalculationsTest < ActiveRecord::TestCase fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books @@ -809,4 +802,16 @@ class CalculationsTest < ActiveRecord::TestCase def test_group_by_attribute_with_custom_type assert_equal({ "proposed" => 2, "published" => 2 }, Book.group(:status).count) end + + def test_deprecate_count_with_block_and_column_name + assert_deprecated do + assert_equal 6, Account.count(:firm_id) { true } + end + end + + def test_deprecate_sum_with_block_and_column_name + assert_deprecated do + assert_equal 6, Account.sum(:firm_id) { 1 } + end + end end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 681399c8bb..2a71f08d90 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -9,6 +9,17 @@ module ActiveRecord @pool = @handler.establish_connection(ActiveRecord::Base.configurations["arunit"]) end + def test_default_env_fall_back_to_default_env_when_rails_env_or_rack_env_is_empty_string + original_rails_env = ENV["RAILS_ENV"] + original_rack_env = ENV["RACK_ENV"] + ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "" + + assert_equal "default_env", ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + ensure + ENV["RAILS_ENV"] = original_rails_env + ENV["RACK_ENV"] = original_rack_env + end + def test_establish_connection_uses_spec_name config = { "readonly" => { "adapter" => "sqlite3" } } resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config) diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 721861975a..f72e0d2ead 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -4,10 +4,7 @@ require "models/pirate" # For timestamps require "models/parrot" require "models/person" # For optimistic locking require "models/aircraft" - -class NumericData < ActiveRecord::Base - self.table_name = "numeric_data" -end +require "models/numeric_data" class DirtyTest < ActiveRecord::TestCase include InTimeZone diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index 7f63bb2473..4ef9a125e6 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -6,7 +6,6 @@ class EnumTest < ActiveRecord::TestCase fixtures :books, :authors setup do - @author = authors(:david) @book = books(:awdr) end @@ -39,6 +38,8 @@ class EnumTest < ActiveRecord::TestCase assert_equal @book, Book.author_visibility_visible.first assert_equal @book, Book.illustrator_visibility_visible.first assert_equal @book, Book.medium_to_read.first + assert_equal books(:ddd), Book.forgotten.first + assert_equal books(:rfr), authors(:david).unpublished_books.first end test "find via where with values" do @@ -57,9 +58,9 @@ class EnumTest < ActiveRecord::TestCase assert_not_equal @book, Book.where(status: :written).first assert_equal @book, Book.where(status: [:published]).first assert_not_equal @book, Book.where(status: [:written]).first - assert_not @author.unpublished_books.include?(@book) assert_not_equal @book, Book.where.not(status: :published).first assert_equal @book, Book.where.not(status: :written).first + assert_equal books(:ddd), Book.where(read_status: :forgotten).first end test "find via where with strings" do @@ -69,6 +70,7 @@ class EnumTest < ActiveRecord::TestCase assert_not_equal @book, Book.where(status: ["written"]).first assert_not_equal @book, Book.where.not(status: "published").first assert_equal @book, Book.where.not(status: "written").first + assert_equal books(:ddd), Book.where(read_status: "forgotten").first end test "build from scope" do diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index a7b6333010..420f552ef6 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -743,7 +743,6 @@ class FinderTest < ActiveRecord::TestCase assert Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: false).find(1) assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) } assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "HHC", replies_count: 1, approved: false).find(1) } - assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) } end def test_condition_interpolation @@ -1024,16 +1023,6 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" } end - def test_find_all_with_join - developers_on_project_one = Developer. - joins("LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id"). - where("project_id=1").to_a - assert_equal 3, developers_on_project_one.length - developer_names = developers_on_project_one.map(&:name) - assert_includes developer_names, "David" - assert_includes developer_names, "Jamis" - end - def test_joins_dont_clobber_id first = Firm. joins("INNER JOIN companies clients ON clients.firm_id = companies.id"). diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index d70572d6eb..fb5a7bcc31 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -1,6 +1,7 @@ require "cases/helper" require "models/author" require "models/company" +require "models/membership" require "models/person" require "models/post" require "models/project" @@ -29,7 +30,7 @@ end class InheritanceTest < ActiveRecord::TestCase include InheritanceTestHelper - fixtures :companies, :projects, :subscribers, :accounts, :vegetables + fixtures :companies, :projects, :subscribers, :accounts, :vegetables, :memberships def test_class_with_store_full_sti_class_returns_full_name with_store_full_sti_class do @@ -316,7 +317,7 @@ class InheritanceTest < ActiveRecord::TestCase end def test_new_with_autoload_paths - path = File.expand_path("../../models/autoloadable", __FILE__) + path = File.expand_path("../models/autoloadable", __dir__) ActiveSupport::Dependencies.autoload_paths << path firm = Company.new(type: "ExtraFirm") @@ -435,6 +436,10 @@ class InheritanceTest < ActiveRecord::TestCase assert_nothing_raised { Company.of_first_firm } assert_nothing_raised { Client.of_first_firm } end + + def test_inheritance_with_default_scope + assert_equal 1, SelectedMembership.count(:all) + end end class InheritanceComputeTypeTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb index 7ffa86c42f..9104976126 100644 --- a/activerecord/test/cases/integration_test.rb +++ b/activerecord/test/cases/integration_test.rb @@ -168,14 +168,18 @@ class IntegrationTest < ActiveRecord::TestCase end def test_named_timestamps_for_cache_key - owner = owners(:blackbeard) - assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at) + assert_deprecated do + owner = owners(:blackbeard) + assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at) + end end def test_cache_key_when_named_timestamp_is_nil - owner = owners(:blackbeard) - owner.happy_at = nil - assert_equal "owners/#{owner.id}", owner.cache_key(:happy_at) + assert_deprecated do + owner = owners(:blackbeard) + owner.happy_at = nil + assert_equal "owners/#{owner.id}", owner.cache_key(:happy_at) + end end def test_cache_key_is_stable_with_versioning_on @@ -213,13 +217,13 @@ class IntegrationTest < ActiveRecord::TestCase Developer.cache_versioning = true developer = Developer.first - first_key = developer.cache_key(:updated_at) + first_key = developer.cache_key_with_version travel 10.seconds do developer.touch end - second_key = developer.cache_key(:updated_at) + second_key = developer.cache_key_with_version assert_not_equal first_key, second_key ensure diff --git a/activerecord/test/cases/json_attribute_test.rb b/activerecord/test/cases/json_attribute_test.rb new file mode 100644 index 0000000000..e5848b45f8 --- /dev/null +++ b/activerecord/test/cases/json_attribute_test.rb @@ -0,0 +1,33 @@ +require "cases/helper" +require "cases/json_shared_test_cases" + +class JsonAttributeTest < ActiveRecord::TestCase + include JSONSharedTestCases + self.use_transactional_tests = false + + class JsonDataTypeOnText < ActiveRecord::Base + self.table_name = "json_data_type" + + attribute :payload, :json + attribute :settings, :json + + store_accessor :settings, :resolution + end + + def setup + super + @connection.create_table("json_data_type") do |t| + t.text "payload" + t.text "settings" + end + end + + private + def column_type + :text + end + + def klass + JsonDataTypeOnText + end +end diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb new file mode 100644 index 0000000000..5aa41b9d6d --- /dev/null +++ b/activerecord/test/cases/json_shared_test_cases.rb @@ -0,0 +1,202 @@ +require "support/schema_dumping_helper" + +module JSONSharedTestCases + include SchemaDumpingHelper + + class JsonDataType < ActiveRecord::Base + self.table_name = "json_data_type" + + store_accessor :settings, :resolution + end + + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + @connection.drop_table :json_data_type, if_exists: true + klass.reset_column_information + end + + def test_column + column = klass.columns_hash["payload"] + assert_equal column_type, column.type + assert_equal column_type.to_s, column.sql_type + + type = klass.type_for_attribute("payload") + assert_not type.binary? + end + + def test_change_table_supports_json + @connection.change_table("json_data_type") do |t| + t.public_send column_type, "users" + end + klass.reset_column_information + column = klass.columns_hash["users"] + assert_equal column_type, column.type + assert_equal column_type.to_s, column.sql_type + end + + def test_schema_dumping + output = dump_table_schema("json_data_type") + assert_match(/t\.#{column_type}\s+"settings"/, output) + end + + def test_cast_value_on_write + x = klass.new(payload: { "string" => "foo", :symbol => :bar }) + assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast) + assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload) + x.save! + assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload) + end + + def test_type_cast_json + type = klass.type_for_attribute("payload") + + data = '{"a_key":"a_value"}' + hash = type.deserialize(data) + assert_equal({ "a_key" => "a_value" }, hash) + assert_equal({ "a_key" => "a_value" }, type.deserialize(data)) + + assert_equal({}, type.deserialize("{}")) + assert_equal({ "key" => nil }, type.deserialize('{"key": null}')) + assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"}))) + end + + def test_rewrite + @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|) + x = klass.first + x.payload = { '"a\'' => "b" } + assert x.save! + end + + def test_select + @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|) + x = klass.first + assert_equal({ "k" => "v" }, x.payload) + end + + def test_select_multikey + @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|) + x = klass.first + assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload) + end + + def test_null_json + @connection.execute("insert into json_data_type (payload) VALUES(null)") + x = klass.first + assert_nil(x.payload) + end + + def test_select_nil_json_after_create + json = klass.create!(payload: nil) + x = klass.where(payload: nil).first + assert_equal(json, x) + end + + def test_select_nil_json_after_update + json = klass.create!(payload: "foo") + x = klass.where(payload: nil).first + assert_nil(x) + + json.update_attributes(payload: nil) + x = klass.where(payload: nil).first + assert_equal(json.reload, x) + end + + def test_select_array_json_value + @connection.execute(%q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|) + x = klass.first + assert_equal(["v0", { "k1" => "v1" }], x.payload) + end + + def test_rewrite_array_json_value + @connection.execute(%q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|) + x = klass.first + x.payload = ["v1", { "k2" => "v2" }, "v3"] + assert x.save! + end + + def test_with_store_accessors + x = klass.new(resolution: "320×480") + assert_equal "320×480", x.resolution + + x.save! + x = klass.first + assert_equal "320×480", x.resolution + + x.resolution = "640×1136" + x.save! + + x = klass.first + assert_equal "640×1136", x.resolution + end + + def test_duplication_with_store_accessors + x = klass.new(resolution: "320×480") + assert_equal "320×480", x.resolution + + y = x.dup + assert_equal "320×480", y.resolution + end + + def test_yaml_round_trip_with_store_accessors + x = klass.new(resolution: "320×480") + assert_equal "320×480", x.resolution + + y = YAML.load(YAML.dump(x)) + assert_equal "320×480", y.resolution + end + + def test_changes_in_place + json = klass.new + assert_not json.changed? + + json.payload = { "one" => "two" } + assert json.changed? + assert json.payload_changed? + + json.save! + assert_not json.changed? + + json.payload["three"] = "four" + assert json.payload_changed? + + json.save! + json.reload + + assert_equal({ "one" => "two", "three" => "four" }, json.payload) + assert_not json.changed? + end + + def test_changes_in_place_with_ruby_object + time = Time.now.utc + json = klass.create!(payload: time) + + json.reload + assert_not json.changed? + + json.payload = time + assert_not json.changed? + end + + def test_assigning_string_literal + json = klass.create!(payload: "foo") + assert_equal "foo", json.payload + end + + def test_assigning_number + json = klass.create!(payload: 1.234) + assert_equal 1.234, json.payload + end + + def test_assigning_boolean + json = klass.create!(payload: true) + assert_equal true, json.payload + end + + private + def klass + JsonDataType + end +end diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 802a969cb7..007926f1b9 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -211,11 +211,6 @@ module ActiveRecord assert_equal [:remove_index, [:table, { name: "new_index" }]], remove end - def test_invert_add_index_with_no_options - remove = @recorder.inverse_of :add_index, [:table, [:one, :two]] - assert_equal [:remove_index, [:table, { column: [:one, :two] }]], remove - end - def test_invert_remove_index add = @recorder.inverse_of :remove_index, [:table, :one] assert_equal [:add_index, [:table, :one]], add diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb index 06c44c8c52..e9eb9968cb 100644 --- a/activerecord/test/cases/migration/references_statements_test.rb +++ b/activerecord/test/cases/migration/references_statements_test.rb @@ -50,6 +50,14 @@ module ActiveRecord assert column_exists?(table_name, :taggable_type, :string, default: "Photo") end + def test_creates_reference_type_column_with_not_null + connection.create_table table_name, force: true do |t| + t.references :taggable, null: false, polymorphic: true + end + assert column_exists?(table_name, :taggable_id, :integer, null: false) + assert column_exists?(table_name, :taggable_type, :string, null: false) + end + def test_does_not_share_options_with_reference_type_column add_reference table_name, :taggable, type: :integer, limit: 2, polymorphic: true assert column_exists?(table_name, :taggable_id, :integer, limit: 2) diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb index 7bcabd0cc6..5da3ad33a3 100644 --- a/activerecord/test/cases/migration/rename_table_test.rb +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -79,10 +79,33 @@ module ActiveRecord assert_equal ConnectionAdapters::PostgreSQL::Name.new("public", "octopi_#{pk}_seq"), seq end + def test_renaming_table_renames_primary_key + connection.create_table :cats, id: :uuid, default: "uuid_generate_v4()" + rename_table :cats, :felines + + assert connection.table_exists? :felines + refute connection.table_exists? :cats + + primary_key_name = connection.select_values(<<-SQL.strip_heredoc, "SCHEMA")[0] + SELECT c.relname + FROM pg_class c + JOIN pg_index i + ON c.oid = i.indexrelid + WHERE i.indisprimary + AND i.indrelid = 'felines'::regclass + SQL + + assert_equal "felines_pkey", primary_key_name + ensure + connection.drop_table :cats, if_exists: true + connection.drop_table :felines, if_exists: true + end + def test_renaming_table_doesnt_attempt_to_rename_non_existent_sequences connection.create_table :cats, id: :uuid, default: "uuid_generate_v4()" assert_nothing_raised { rename_table :cats, :felines } assert connection.table_exists? :felines + refute connection.table_exists? :cats ensure connection.drop_table :cats, if_exists: true connection.drop_table :felines, if_exists: true diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index da7875187a..57f94950f9 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -402,33 +402,6 @@ class MigrationTest < ActiveRecord::TestCase ActiveRecord::Migrator.up(migrations_path) end - def test_migration_sets_internal_metadata_even_when_fully_migrated - current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call - migrations_path = MIGRATIONS_ROOT + "/valid" - old_path = ActiveRecord::Migrator.migrations_paths - ActiveRecord::Migrator.migrations_paths = migrations_path - - ActiveRecord::Migrator.up(migrations_path) - assert_equal current_env, ActiveRecord::InternalMetadata[:environment] - - original_rails_env = ENV["RAILS_ENV"] - original_rack_env = ENV["RACK_ENV"] - ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "foofoo" - new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call - - refute_equal current_env, new_env - - sleep 1 # mysql by default does not store fractional seconds in the database - - ActiveRecord::Migrator.up(migrations_path) - assert_equal new_env, ActiveRecord::InternalMetadata[:environment] - ensure - ActiveRecord::Migrator.migrations_paths = old_path - ENV["RAILS_ENV"] = original_rails_env - ENV["RACK_ENV"] = original_rack_env - ActiveRecord::Migrator.up(migrations_path) - end - def test_internal_metadata_stores_environment_when_other_data_exists ActiveRecord::InternalMetadata.delete_all ActiveRecord::InternalMetadata[:foo] = "bar" diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index b87419d203..5a62cbd3a6 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -752,7 +752,7 @@ module NestedAttributesOnACollectionAssociationTests exception = assert_raise ArgumentError do @pirate.send(association_setter, "foo") end - assert_equal 'Hash or Array expected, got String ("foo")', exception.message + assert_equal %{Hash or Array expected for attribute `#{@association_name}`, got String ("foo")}, exception.message end def test_should_work_with_update_as_well diff --git a/activerecord/test/cases/numeric_data_test.rb b/activerecord/test/cases/numeric_data_test.rb new file mode 100644 index 0000000000..76b97033af --- /dev/null +++ b/activerecord/test/cases/numeric_data_test.rb @@ -0,0 +1,71 @@ +require "cases/helper" +require "models/numeric_data" + +class NumericDataTest < ActiveRecord::TestCase + def test_big_decimal_conditions + m = NumericData.new( + bank_balance: 1586.43, + big_bank_balance: BigDecimal("1000234000567.95"), + world_population: 6000000000, + my_house_population: 3 + ) + assert m.save + assert_equal 0, NumericData.where("bank_balance > ?", 2000.0).count + end + + def test_numeric_fields + m = NumericData.new( + bank_balance: 1586.43, + big_bank_balance: BigDecimal("1000234000567.95"), + world_population: 6000000000, + my_house_population: 3 + ) + assert m.save + + m1 = NumericData.find(m.id) + assert_not_nil m1 + + # As with migration_test.rb, we should make world_population >= 2**62 + # to cover 64-bit platforms and test it is a Bignum, but the main thing + # is that it's an Integer. + assert_kind_of Integer, m1.world_population + assert_equal 6000000000, m1.world_population + + assert_kind_of Integer, m1.my_house_population + assert_equal 3, m1.my_house_population + + assert_kind_of BigDecimal, m1.bank_balance + assert_equal BigDecimal("1586.43"), m1.bank_balance + + assert_kind_of BigDecimal, m1.big_bank_balance + assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance + end + + def test_numeric_fields_with_scale + m = NumericData.new( + bank_balance: 1586.43122334, + big_bank_balance: BigDecimal("234000567.952344"), + world_population: 6000000000, + my_house_population: 3 + ) + assert m.save + + m1 = NumericData.find(m.id) + assert_not_nil m1 + + # As with migration_test.rb, we should make world_population >= 2**62 + # to cover 64-bit platforms and test it is a Bignum, but the main thing + # is that it's an Integer. + assert_kind_of Integer, m1.world_population + assert_equal 6000000000, m1.world_population + + assert_kind_of Integer, m1.my_house_population + assert_equal 3, m1.my_house_population + + assert_kind_of BigDecimal, m1.bank_balance + assert_equal BigDecimal("1586.43"), m1.bank_balance + + assert_kind_of BigDecimal, m1.big_bank_balance + assert_equal BigDecimal("234000567.95"), m1.big_bank_balance + end +end diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index 64866eaf2d..c3b39a9295 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -56,7 +56,7 @@ class RelationMergingTest < ActiveRecord::TestCase def test_relation_merging_with_locks devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2)) - assert devs.locked.present? + assert devs.locked? end def test_relation_merging_with_preload diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index 11ef0d8743..dea787c07f 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -36,7 +36,7 @@ module ActiveRecord @relation ||= Relation.new FakeKlass.new("posts"), Post.arel_table, Post.predicate_builder end - (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select, :left_joins]).each do |method| + (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method| test "##{method}!" do assert relation.public_send("#{method}!", :foo).equal?(relation) assert_equal [:foo], relation.public_send("#{method}_values") diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index cbc466d6b8..42dae4d569 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -15,7 +15,7 @@ require "models/vertex" module ActiveRecord class WhereTest < ActiveRecord::TestCase - fixtures :posts, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates + fixtures :posts, :edges, :authors, :author_addresses, :binaries, :essays, :cars, :treasures, :price_estimates, :topics def test_where_copies_bind_params author = authors(:david) @@ -48,6 +48,10 @@ module ActiveRecord assert_equal [chef], chefs.to_a end + def test_where_with_casted_value_is_nil + assert_equal 4, Topic.where(last_read: "").count + end + def test_rewhere_on_root assert_equal posts(:welcome), Post.rewhere(title: "Welcome to the weblog").first end diff --git a/activerecord/test/cases/reload_models_test.rb b/activerecord/test/cases/reload_models_test.rb index 5dc9d6d8b7..3f4c0c03e3 100644 --- a/activerecord/test/cases/reload_models_test.rb +++ b/activerecord/test/cases/reload_models_test.rb @@ -13,7 +13,7 @@ class ReloadModelsTest < ActiveRecord::TestCase # development environment. Note that meanwhile the class Pet is not # reloaded, simulating a class that is present in a plugin. Object.class_eval { remove_const :Owner } - Kernel.load(File.expand_path(File.join(File.dirname(__FILE__), "../models/owner.rb"))) + Kernel.load(File.expand_path("../models/owner.rb", __dir__)) pet = Pet.find_by_name("parrot") pet.owner = Owner.find_by_name("ashley") diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index cb8d449ba9..417c6f4832 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -116,32 +116,22 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dump_includes_limit_constraint_for_integer_columns output = dump_all_table_schema([/^(?!integer_limits)/]) - assert_match %r{c_int_without_limit}, output + assert_match %r{"c_int_without_limit"(?!.*limit)}, output if current_adapter?(:PostgreSQLAdapter) - assert_no_match %r{c_int_without_limit.*limit:}, output - assert_match %r{c_int_1.*limit: 2}, output assert_match %r{c_int_2.*limit: 2}, output # int 3 is 4 bytes in postgresql - assert_match %r{c_int_3.*}, output - assert_no_match %r{c_int_3.*limit:}, output - - assert_match %r{c_int_4.*}, output - assert_no_match %r{c_int_4.*limit:}, output + assert_match %r{"c_int_3"(?!.*limit)}, output + assert_match %r{"c_int_4"(?!.*limit)}, output elsif current_adapter?(:Mysql2Adapter) - assert_match %r{c_int_without_limit"$}, output - assert_match %r{c_int_1.*limit: 1}, output assert_match %r{c_int_2.*limit: 2}, output assert_match %r{c_int_3.*limit: 3}, output - assert_match %r{c_int_4.*}, output - assert_no_match %r{c_int_4.*:limit}, output + assert_match %r{"c_int_4"(?!.*limit)}, output elsif current_adapter?(:SQLite3Adapter) - assert_no_match %r{c_int_without_limit.*limit:}, output - assert_match %r{c_int_1.*limit: 1}, output assert_match %r{c_int_2.*limit: 2}, output assert_match %r{c_int_3.*limit: 3}, output diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 0c2cffe0d3..483ea7128d 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -551,6 +551,12 @@ class NamedScopingTest < ActiveRecord::TestCase assert_equal 1, SpecialComment.where(body: "go crazy").created.count end + def test_model_class_should_respond_to_extending + assert_raises OopsError do + Comment.unscoped.oops_comments.destroy_all + end + end + def test_model_class_should_respond_to_none assert !Topic.none? Topic.delete_all diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index 3fbff7664b..8535be8402 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -229,12 +229,19 @@ class RelationScopingTest < ActiveRecord::TestCase end end - def test_circular_joins_with_current_scope_does_not_crash + def test_circular_joins_with_scoping_does_not_crash posts = Post.joins(comments: :post).scoping do - Post.current_scope.first(10) + Post.first(10) end assert_equal posts, Post.joins(comments: :post).first(10) end + + def test_circular_left_joins_with_scoping_does_not_crash + posts = Post.left_joins(comments: :post).scoping do + Post.first(10) + end + assert_equal posts, Post.left_joins(comments: :post).first(10) + end end class NestedRelationScopingTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 673392b4c4..e1bdaab5cf 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -349,4 +349,32 @@ class SerializedAttributeTest < ActiveRecord::TestCase topic.foo refute topic.changed? end + + def test_serialized_attribute_works_under_concurrent_initial_access + model = Topic.dup + + topic = model.last + topic.update group: "1" + + model.serialize :group, JSON + model.reset_column_information + + # This isn't strictly necessary for the test, but a little bit of + # knowledge of internals allows us to make failures far more likely. + model.define_singleton_method(:define_attribute) do |*args| + Thread.pass + super(*args) + end + + threads = 4.times.map do + Thread.new do + topic.reload.group + end + end + + # All the threads should retrieve the value knowing it is JSON, and + # thus decode it. If this fails, some threads will instead see the + # raw string ("1"), or raise an exception. + assert_equal [1] * threads.size, threads.map(&:value) + end end diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index 33da3d11fc..c22d974536 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -294,13 +294,6 @@ if current_adapter?(:Mysql2Adapter) ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) end - def test_structure_dump - filename = "awesome-file.sql" - Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db").returns(true) - - ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) - end - def test_structure_dump_with_extra_flags filename = "awesome-file.sql" expected_command = ["mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "--noop", "test-db"] diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 5d9aa99497..a305aa295a 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -167,6 +167,20 @@ class ValidationsTest < ActiveRecord::TestCase assert topic.valid? end + def test_numericality_validation_checks_against_raw_value + klass = Class.new(Topic) do + def self.model_name + ActiveModel::Name.new(self, nil, "Topic") + end + attribute :wibble, :decimal, scale: 2, precision: 9 + validates_numericality_of :wibble, greater_than_or_equal_to: BigDecimal.new("97.18") + end + + assert_not klass.new(wibble: "97.179").valid? + assert_not klass.new(wibble: 97.179).valid? + assert_not klass.new(wibble: BigDecimal.new("97.179")).valid? + end + def test_acceptance_validator_doesnt_require_db_connection klass = Class.new(ActiveRecord::Base) do self.table_name = "posts" diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb index ab0e67cd9d..bfc13d683d 100644 --- a/activerecord/test/cases/yaml_serialization_test.rb +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -123,8 +123,8 @@ class YamlSerializationTest < ActiveRecord::TestCase def yaml_fixture(file_name) path = File.expand_path( - "../../support/yaml_compatibility_fixtures/#{file_name}.yml", - __FILE__ + "../support/yaml_compatibility_fixtures/#{file_name}.yml", + __dir__ ) File.read(path) end diff --git a/activerecord/test/config.rb b/activerecord/test/config.rb index 6e2e8b2145..a65e6ff776 100644 --- a/activerecord/test/config.rb +++ b/activerecord/test/config.rb @@ -1,4 +1,4 @@ -TEST_ROOT = File.expand_path(File.dirname(__FILE__)) +TEST_ROOT = __dir__ ASSETS_ROOT = TEST_ROOT + "/assets" FIXTURES_ROOT = TEST_ROOT + "/fixtures" MIGRATIONS_ROOT = TEST_ROOT + "/migrations" diff --git a/activerecord/test/fixtures/books.yml b/activerecord/test/fixtures/books.yml index b3625ee72e..699623a6f9 100644 --- a/activerecord/test/fixtures/books.yml +++ b/activerecord/test/fixtures/books.yml @@ -25,6 +25,7 @@ ddd: name: "Domain-Driven Design" format: "hardcover" status: 2 + read_status: "forgotten" tlg: author_id: 1 diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb index 5f8a8a96dd..6466e1b341 100644 --- a/activerecord/test/models/book.rb +++ b/activerecord/test/models/book.rb @@ -8,7 +8,7 @@ class Book < ActiveRecord::Base has_many :subscribers, through: :subscriptions enum status: [:proposed, :written, :published] - enum read_status: { unread: 0, reading: 2, read: 3 } + enum read_status: { unread: 0, reading: 2, read: 3, forgotten: nil } enum nullable_status: [:single, :married] enum language: [:english, :spanish, :french], _prefix: :in enum author_visibility: [:visible, :invisible], _prefix: true diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb index e8654dca01..4b2840c653 100644 --- a/activerecord/test/models/category.rb +++ b/activerecord/test/models/category.rb @@ -29,6 +29,15 @@ class Category < ActiveRecord::Base has_many :authors_with_select, -> { select "authors.*, categorizations.post_id" }, through: :categorizations, source: :author scope :general, -> { where(name: "General") } + + # Should be delegated `ast` and `locked` to `arel`. + def self.ast + raise + end + + def self.locked + raise + end end class SpecialCategory < Category diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 76b484e616..d227f6fe86 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -19,6 +19,18 @@ class Comment < ActiveRecord::Base has_many :children, class_name: "Comment", foreign_key: :parent_id belongs_to :parent, class_name: "Comment", counter_cache: :children_count + class ::OopsError < RuntimeError; end + + module OopsExtension + def destroy_all(*) + raise OopsError + end + end + + default_scope { extending OopsExtension } + + scope :oops_comments, -> { extending OopsExtension } + # Should not be called if extending modules that having the method exists on an association. def self.greeting raise diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index d269a95e8c..c6a5bf1c92 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -175,6 +175,7 @@ end class ExclusivelyDependentFirm < Company has_one :account, foreign_key: "firm_id", dependent: :delete has_many :dependent_sanitized_conditional_clients_of_firm, -> { order("id").where("name = 'BigShot Inc.'") }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all + has_many :dependent_hash_conditional_clients_of_firm, -> { order("id").where(name: "BigShot Inc.") }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all has_many :dependent_conditional_clients_of_firm, -> { order("id").where("name = ?", "BigShot Inc.") }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all end diff --git a/activerecord/test/models/numeric_data.rb b/activerecord/test/models/numeric_data.rb new file mode 100644 index 0000000000..c6e025a9ce --- /dev/null +++ b/activerecord/test/models/numeric_data.rb @@ -0,0 +1,8 @@ +class NumericData < ActiveRecord::Base + self.table_name = "numeric_data" + # Decimal columns with 0 scale being automatically treated as integers + # is deprecated, and will be removed in a future version of Rails. + attribute :world_population, :big_integer + attribute :my_house_population, :big_integer + attribute :atoms_in_universe, :big_integer +end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index a2028b3eb9..4c913b3b72 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -13,7 +13,7 @@ class Post < ActiveRecord::Base module NamedExtension2 def greeting - "hello" + "hullo" end end diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 0420e2d15c..d9381ac9cf 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -14,7 +14,7 @@ class Topic < ActiveRecord::Base scope :replied, -> { where "replies_count > 0" } scope "approved_as_string", -> { where(approved: true) } - scope :anonymous_extension, -> { all } do + scope :anonymous_extension, -> {} do def one 1 end |