diff options
Diffstat (limited to 'activerecord')
56 files changed, 486 insertions, 480 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 1f8163db12..f75f1a9108 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* 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* 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/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_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 4a8d231503..1f1efe8812 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -54,8 +54,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - mattr_accessor :time_zone_aware_attributes, instance_writer: false - self.time_zone_aware_attributes = false + mattr_accessor :time_zone_aware_attributes, instance_writer: false, default: false class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: [] class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ] diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 829a4f6e86..70f0e2af8e 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -140,8 +140,7 @@ module ActiveRecord included do Associations::Builder::Association.extensions << AssociationBuilderExtension - mattr_accessor :index_nested_attribute_errors, instance_writer: false - self.index_nested_attribute_errors = false + mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false end module ClassMethods # :nodoc: 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 16a398f631..a1031ccbf5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -188,6 +188,8 @@ module ActiveRecord # The name of the primary key, if one is to be added automatically. # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored. # + # If an array is passed, a composite primary key will be created. + # # Note that Active Record models will automatically detect their # primary key. This can be avoided by using # {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model @@ -241,6 +243,23 @@ module ActiveRecord # label varchar # ) # + # ====== Create a composite primary key + # + # create_table(:orders, primary_key: [:product_id, :client_id]) do |t| + # t.belongs_to :product + # t.belongs_to :client + # end + # + # generates: + # + # CREATE TABLE order ( + # product_id integer NOT NULL, + # client_id integer NOT NULL + # ); + # + # ALTER TABLE ONLY "orders" + # ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id); + # # ====== Do not add a primary key column # # create_table(:categories_suppliers, id: false) do |t| @@ -493,8 +512,7 @@ module ActiveRecord # * <tt>:default</tt> - # The column's default value. Use +nil+ for +NULL+. # * <tt>:null</tt> - - # Allows or disallows +NULL+ values in the column. This option could - # have been named <tt>:null_allowed</tt>. + # Allows or disallows +NULL+ values in the column. # * <tt>:precision</tt> - # Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns. # * <tt>:scale</tt> - 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 01599985ca..648c869915 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -543,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 @@ -837,7 +837,7 @@ module ActiveRecord end end - class MysqlJson < Type::Internal::AbstractJson # :nodoc: + class MysqlJson < Type::Json # :nodoc: end class MysqlString < Type::String # :nodoc: @@ -860,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 705cb7f0b3..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,7 +2,7 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: - class Jsonb < Json # :nodoc: + class Jsonb < Type::Json # :nodoc: def type :jsonb end 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 55566f1e34..cb45d7ba6b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -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 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/core.rb b/activerecord/lib/active_record/core.rb index 8f78330d4a..198c712abc 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -56,8 +56,7 @@ module ActiveRecord # :singleton-method: # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling # dates and times from the database. This is set to :utc by default. - mattr_accessor :default_timezone, instance_writer: false - self.default_timezone = :utc + mattr_accessor :default_timezone, instance_writer: false, default: :utc ## # :singleton-method: @@ -67,16 +66,14 @@ module ActiveRecord # ActiveRecord::Schema file which can be loaded into any database that # supports migrations. Use :ruby if you want to have different database # adapters for, e.g., your development and test environments. - mattr_accessor :schema_format, instance_writer: false - self.schema_format = :ruby + mattr_accessor :schema_format, instance_writer: false, default: :ruby ## # :singleton-method: # Specifies if an error should be raised if the query has an order being # ignored when doing batch queries. Useful in applications where the # scope being ignored is error-worthy, rather than a warning. - mattr_accessor :error_on_ignored_order, instance_writer: false - self.error_on_ignored_order = false + mattr_accessor :error_on_ignored_order, instance_writer: false, default: false def self.error_on_ignored_order_or_limit ActiveSupport::Deprecation.warn(<<-MSG.squish) @@ -101,8 +98,7 @@ module ActiveRecord ## # :singleton-method: # Specify whether or not to use timestamps for migration versions - mattr_accessor :timestamped_migrations, instance_writer: false - self.timestamped_migrations = true + mattr_accessor :timestamped_migrations, instance_writer: false, default: true ## # :singleton-method: @@ -110,8 +106,7 @@ module ActiveRecord # db:migrate rake task. This is true by default, which is useful for the # development environment. This should ideally be false in the production # environment where dumping schema is rarely needed. - mattr_accessor :dump_schema_after_migration, instance_writer: false - self.dump_schema_after_migration = true + mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true ## # :singleton-method: @@ -120,8 +115,7 @@ module ActiveRecord # schema_search_path are dumped. Use :all to dump all schemas regardless # of schema_search_path, or a string of comma separated schemas for a # custom list. - mattr_accessor :dump_schemas, instance_writer: false - self.dump_schemas = :schema_search_path + mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path ## # :singleton-method: @@ -130,7 +124,6 @@ module ActiveRecord # be used to identify queries which load thousands of records and # potentially cause memory bloat. mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false - self.warn_on_records_fetched_greater_than = nil mattr_accessor :maintain_test_schema, instance_accessor: false diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 18fac5af1b..60d4fb70e0 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -105,7 +105,7 @@ module ActiveRecord class WrappedDatabaseException < StatementInvalid end - # Raised when a record cannot be inserted because it would violate a uniqueness constraint. + # Raised when a record cannot be inserted or updated because it would violate a uniqueness constraint. class RecordNotUnique < WrappedDatabaseException end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index bad6542be2..a6b66c91e3 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -492,8 +492,7 @@ module ActiveRecord end end - cattr_accessor :all_loaded_fixtures - self.all_loaded_fixtures = {} + cattr_accessor :all_loaded_fixtures, default: {} class ClassCache def initialize(class_names, config) diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 1179a60e9b..14e0f5bff7 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -446,7 +446,11 @@ module ActiveRecord def load_schema return if schema_loaded? @load_schema_monitor.synchronize do - load_schema! unless defined?(@columns_hash) && @columns_hash + return if defined?(@columns_hash) && @columns_hash + + load_schema! + + @schema_loaded = true end end @@ -460,8 +464,6 @@ module ActiveRecord user_provided_default: false ) end - - @schema_loaded = true end def reload_schema_from_cache 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/reflection.rb b/activerecord/lib/active_record/reflection.rb index 429f9a257a..e8ee8279fd 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -287,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(_) @@ -583,12 +588,6 @@ module ActiveRecord Array(options[:extend]) end - protected - - def actual_source_reflection # FIXME: this is a horrible name - self - end - private def calculate_constructable(macro, options) @@ -761,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 @@ -987,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/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index a6309e0b5c..7dea5deec5 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -107,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 @@ -164,7 +169,7 @@ 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 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/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/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 24b81aabc8..66a2846f3a 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -13,8 +13,7 @@ module ActiveRecord # A list of tables which should not be dumped to the schema. # Acceptable values are strings as well as regexp if ActiveRecord::Base.schema_format == :ruby. # Only strings are accepted if ActiveRecord::Base.schema_format == :sql. - cattr_accessor :ignore_tables - @@ignore_tables = [] + cattr_accessor :ignore_tables, default: [] class << self def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base) diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index ba5cc29ac1..70b2693b28 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -107,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/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/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 a8d6a63465..0000000000 --- a/activerecord/lib/active_record/type/internal/abstract_json.rb +++ /dev/null @@ -1,37 +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 changed_in_place?(raw_old_value, new_value) - deserialize(raw_old_value) != new_value - 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/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb index d311ffb703..26c69edc7b 100644 --- a/activerecord/test/cases/adapters/mysql2/json_test.rb +++ b/activerecord/test/cases/adapters/mysql2/json_test.rb @@ -7,20 +7,13 @@ if ActiveRecord::Base.connection.supports_json? self.use_transactional_tests = false 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 - private def column_type :json diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb deleted file mode 100644 index 2c778b1150..0000000000 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ /dev/null @@ -1,151 +0,0 @@ -require "cases/helper" - -# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with -# reserved word names (ie: group, order, values, etc...) -class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase - class Group < ActiveRecord::Base - Group.table_name = "group" - belongs_to :select - has_one :values - end - - class Select < ActiveRecord::Base - Select.table_name = "select" - has_many :groups - end - - class Values < ActiveRecord::Base - Values.table_name = "values" - end - - class Distinct < ActiveRecord::Base - Distinct.table_name = "distinct" - has_and_belongs_to_many :selects - has_many :values, through: :groups - end - - def setup - @connection = ActiveRecord::Base.connection - - # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table() - # will fail with these table names if these test cases fail - - create_tables_directly "group" => "id int auto_increment primary key, `order` varchar(255), select_id int", - "select" => "id int auto_increment primary key", - "values" => "id int auto_increment primary key, group_id int", - "distinct" => "id int auto_increment primary key", - "distinct_select" => "distinct_id int, select_id int" - end - - teardown do - drop_tables_directly ["group", "select", "values", "distinct", "distinct_select", "order"] - end - - # create tables with reserved-word names and columns - def test_create_tables - assert_nothing_raised { - @connection.create_table :order do |t| - t.column :group, :string - end - } - end - - # rename tables with reserved-word names - def test_rename_tables - assert_nothing_raised { @connection.rename_table(:group, :order) } - end - - # alter column with a reserved-word name in a table with a reserved-word name - def test_change_columns - assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") } - #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter - assert_nothing_raised { @connection.change_column("group", "order", :Int, default: 0) } - assert_nothing_raised { @connection.rename_column(:group, :order, :values) } - end - - # introspect table with reserved word name - def test_introspect - assert_nothing_raised { @connection.columns(:group) } - assert_nothing_raised { @connection.indexes(:group) } - end - - #fixtures - self.use_instantiated_fixtures = true - self.use_transactional_tests = false - - #activerecord model class with reserved-word table name - def test_activerecord_model - create_test_fixtures :select, :distinct, :group, :values, :distinct_select - x = nil - assert_nothing_raised { x = Group.new } - x.order = "x" - assert_nothing_raised { x.save } - x.order = "y" - assert_nothing_raised { x.save } - assert_nothing_raised { Group.find_by_order("y") } - assert_nothing_raised { Group.find(1) } - end - - # has_one association with reserved-word table name - def test_has_one_associations - create_test_fixtures :select, :distinct, :group, :values, :distinct_select - v = nil - assert_nothing_raised { v = Group.find(1).values } - assert_equal 2, v.id - end - - # belongs_to association with reserved-word table name - def test_belongs_to_associations - create_test_fixtures :select, :distinct, :group, :values, :distinct_select - gs = nil - assert_nothing_raised { gs = Select.find(2).groups } - assert_equal gs.length, 2 - assert(gs.collect(&:id).sort == [2, 3]) - end - - # has_and_belongs_to_many with reserved-word table name - def test_has_and_belongs_to_many - create_test_fixtures :select, :distinct, :group, :values, :distinct_select - s = nil - assert_nothing_raised { s = Distinct.find(1).selects } - assert_equal s.length, 2 - assert(s.collect(&:id).sort == [1, 2]) - end - - # activerecord model introspection with reserved-word table and column names - def test_activerecord_introspection - assert_nothing_raised { Group.table_exists? } - assert_nothing_raised { Group.columns } - end - - # Calculations - def test_calculations_work_with_reserved_words - assert_nothing_raised { Group.count } - end - - def test_associations_work_with_reserved_words - assert_nothing_raised { Select.all.merge!(includes: [:groups]).to_a } - end - - #the following functions were added to DRY test cases - - private - # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path - def create_test_fixtures(*fixture_names) - ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) - end - - # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name - def drop_tables_directly(table_names, connection = @connection) - table_names.each do |name| - connection.drop_table name, if_exists: true - end - end - - # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns - def create_tables_directly(tables, connection = @connection) - tables.each do |table_name, column_properties| - connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )") - end - end -end diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb index c1f3a4ae2c..3b6840a1c9 100644 --- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb +++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb @@ -93,8 +93,6 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase end def test_empty_string_assignment - assert_nothing_raised { PostgresqlPoint.new(x: "") } - p = PostgresqlPoint.new(x: "") assert_nil p.x end diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 4eeb563781..6aa60630c2 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -5,35 +5,26 @@ module PostgresqlJSONSharedTestCases 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 + 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_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 diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index bfc763e1ef..76e0ad60fe 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -324,13 +324,13 @@ module ActiveRecord reset_connection end - def test_only_reload_type_map_once_for_every_unknown_type + def test_only_reload_type_map_once_for_every_unrecognized_type silence_warnings do assert_queries 2, ignore_none: true do - @connection.select_all "SELECT NULL::anyelement" + @connection.select_all "select 'pg_catalog.pg_class'::regclass" end assert_queries 1, ignore_none: true do - @connection.select_all "SELECT NULL::anyelement" + @connection.select_all "select 'pg_catalog.pg_class'::regclass" end assert_queries 2, ignore_none: true do @connection.select_all "SELECT NULL::anyarray" @@ -340,13 +340,13 @@ module ActiveRecord reset_connection end - def test_only_warn_on_first_encounter_of_unknown_oid + def test_only_warn_on_first_encounter_of_unrecognized_oid warning = capture(:stderr) { - @connection.select_all "SELECT NULL::anyelement" - @connection.select_all "SELECT NULL::anyelement" - @connection.select_all "SELECT NULL::anyelement" + @connection.select_all "select 'pg_catalog.pg_class'::regclass" + @connection.select_all "select 'pg_catalog.pg_class'::regclass" + @connection.select_all "select 'pg_catalog.pg_class'::regclass" } - assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'\. It will be treated as String\.\n\z/, warning) + assert_match(/\Aunknown OID \d+: failed to recognize type of 'regclass'\. It will be treated as String\.\n\z/, warning) ensure reset_connection end diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 6ebe9d82a7..d124b64861 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -64,11 +64,11 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase 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 + connection.add_column :uuid_data_type, :thingy, :uuid, null: true, default: nil + UUIDType.reset_column_information column = UUIDType.columns_hash["thingy"] + assert column.null assert_nil column.default end diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 3638c87968..7b0445025c 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -34,18 +34,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_hmt_does_not_table_name_collide_when_joining_associations - assert_nothing_raised do - Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a - end authors = Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a assert_equal 1, assert_no_queries { authors.size } assert_equal 10, assert_no_queries { authors[0].comments.size } end def test_eager_association_loading_grafts_stashed_associations_to_correct_parent - assert_nothing_raised do - Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").to_a - end assert_equal people(:michael), Person.eager_load(primary_contact: :primary_contact).where("primary_contacts_people_2.first_name = ?", "Susan").order("people.id").first end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 4271a09c9b..55b294cfaa 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -271,9 +271,6 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_loading_from_an_association_that_has_a_hash_of_conditions - assert_nothing_raised do - Author.all.merge!(includes: :hello_posts_with_hash_conditions).to_a - end assert !Author.all.merge!(includes: :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty? end 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 4bf1b5bcd5..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: " ") diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index a794eba691..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 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/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 93f8ab18c2..80baaac30a 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 @@ -587,8 +580,11 @@ class CalculationsTest < ActiveRecord::TestCase end def test_pluck_without_column_names - assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]], - Company.order(:id).limit(1).pluck + if current_adapter?(:OracleAdapter) + assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, nil]], Company.order(:id).limit(1).pluck + else + assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]], Company.order(:id).limit(1).pluck + end end def test_pluck_type_cast 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 db3da53487..4ef9a125e6 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -60,6 +60,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 "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 4837a169fa..420f552ef6 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1023,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 b4bbdc6dad..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 @@ -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/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 index ef5ca86874..5aa41b9d6d 100644 --- a/activerecord/test/cases/json_shared_test_cases.rb +++ b/activerecord/test/cases/json_shared_test_cases.rb @@ -9,12 +9,21 @@ module JSONSharedTestCases 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 = JsonDataType.columns_hash["payload"] + column = klass.columns_hash["payload"] assert_equal column_type, column.type assert_equal column_type.to_s, column.sql_type - type = JsonDataType.type_for_attribute("payload") + type = klass.type_for_attribute("payload") assert_not type.binary? end @@ -22,8 +31,8 @@ module JSONSharedTestCases @connection.change_table("json_data_type") do |t| t.public_send column_type, "users" end - JsonDataType.reset_column_information - column = JsonDataType.columns_hash["users"] + 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 @@ -34,7 +43,7 @@ module JSONSharedTestCases end def test_cast_value_on_write - x = JsonDataType.new(payload: { "string" => "foo", :symbol => :bar }) + 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! @@ -42,7 +51,7 @@ module JSONSharedTestCases end def test_type_cast_json - type = JsonDataType.type_for_attribute("payload") + type = klass.type_for_attribute("payload") data = '{"a_key":"a_value"}' hash = type.deserialize(data) @@ -56,75 +65,75 @@ module JSONSharedTestCases def test_rewrite @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|) - x = JsonDataType.first + 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 = JsonDataType.first + 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 = JsonDataType.first + 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 = JsonDataType.first + x = klass.first assert_nil(x.payload) end def test_select_nil_json_after_create - json = JsonDataType.create!(payload: nil) - x = JsonDataType.where(payload: nil).first + json = klass.create!(payload: nil) + x = klass.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 + json = klass.create!(payload: "foo") + x = klass.where(payload: nil).first assert_nil(x) json.update_attributes(payload: nil) - x = JsonDataType.where(payload: nil).first + 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 = JsonDataType.first + 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 = JsonDataType.first + x = klass.first x.payload = ["v1", { "k2" => "v2" }, "v3"] assert x.save! end def test_with_store_accessors - x = JsonDataType.new(resolution: "320×480") + x = klass.new(resolution: "320×480") assert_equal "320×480", x.resolution x.save! - x = JsonDataType.first + x = klass.first assert_equal "320×480", x.resolution x.resolution = "640×1136" x.save! - x = JsonDataType.first + x = klass.first assert_equal "640×1136", x.resolution end def test_duplication_with_store_accessors - x = JsonDataType.new(resolution: "320×480") + x = klass.new(resolution: "320×480") assert_equal "320×480", x.resolution y = x.dup @@ -132,7 +141,7 @@ module JSONSharedTestCases end def test_yaml_round_trip_with_store_accessors - x = JsonDataType.new(resolution: "320×480") + x = klass.new(resolution: "320×480") assert_equal "320×480", x.resolution y = YAML.load(YAML.dump(x)) @@ -140,7 +149,7 @@ module JSONSharedTestCases end def test_changes_in_place - json = JsonDataType.new + json = klass.new assert_not json.changed? json.payload = { "one" => "two" } @@ -162,7 +171,7 @@ module JSONSharedTestCases def test_changes_in_place_with_ruby_object time = Time.now.utc - json = JsonDataType.create!(payload: time) + json = klass.create!(payload: time) json.reload assert_not json.changed? @@ -172,17 +181,22 @@ module JSONSharedTestCases end def test_assigning_string_literal - json = JsonDataType.create!(payload: "foo") + json = klass.create!(payload: "foo") assert_equal "foo", json.payload end def test_assigning_number - json = JsonDataType.create!(payload: 1.234) + json = klass.create!(payload: 1.234) assert_equal 1.234, json.payload end def test_assigning_boolean - json = JsonDataType.create!(payload: true) + json = klass.create!(payload: true) assert_equal true, json.payload end + + private + def klass + JsonDataType + end end 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/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/relations_test.rb b/activerecord/test/cases/relations_test.rb index 81173945a3..86f3b0b962 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -695,16 +695,6 @@ class RelationTest < ActiveRecord::TestCase end end - def test_default_scope_with_conditions_string - assert_equal Developer.where(name: "David").map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort - assert_nil DeveloperCalledDavid.create!.name - end - - def test_default_scope_with_conditions_hash - assert_equal Developer.where(name: "Jamis").map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort - assert_equal "Jamis", DeveloperCalledJamis.create!.name - end - def test_default_scoping_finder_methods developers = DeveloperCalledDavid.order("id").map(&:id).sort assert_equal Developer.where(name: "David").map(&:id).sort, developers diff --git a/activerecord/test/cases/reserved_word_test.rb b/activerecord/test/cases/reserved_word_test.rb new file mode 100644 index 0000000000..f3019a5326 --- /dev/null +++ b/activerecord/test/cases/reserved_word_test.rb @@ -0,0 +1,132 @@ +require "cases/helper" + +class ReservedWordTest < ActiveRecord::TestCase + self.use_instantiated_fixtures = true + self.use_transactional_tests = false + + class Group < ActiveRecord::Base + Group.table_name = "group" + belongs_to :select + has_one :values + end + + class Select < ActiveRecord::Base + Select.table_name = "select" + has_many :groups + end + + class Values < ActiveRecord::Base + Values.table_name = "values" + end + + class Distinct < ActiveRecord::Base + Distinct.table_name = "distinct" + has_and_belongs_to_many :selects + has_many :values, through: :groups + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.create_table :select, force: true + @connection.create_table :distinct, force: true + @connection.create_table :distinct_select, id: false, force: true do |t| + t.belongs_to :distinct + t.belongs_to :select + end + @connection.create_table :group, force: true do |t| + t.string :order + t.belongs_to :select + end + @connection.create_table :values, force: true do |t| + t.belongs_to :group + end + end + + def teardown + @connection.drop_table :select, if_exists: true + @connection.drop_table :distinct, if_exists: true + @connection.drop_table :distinct_select, if_exists: true + @connection.drop_table :group, if_exists: true + @connection.drop_table :values, if_exists: true + @connection.drop_table :order, if_exists: true + end + + def test_create_tables + assert_not @connection.table_exists?(:order) + + @connection.create_table :order do |t| + t.string :group + end + + assert @connection.table_exists?(:order) + end + + def test_rename_tables + assert_nothing_raised { @connection.rename_table(:group, :order) } + end + + def test_change_columns + assert_nothing_raised { @connection.change_column_default(:group, :order, "whatever") } + assert_nothing_raised { @connection.change_column("group", "order", :text, default: nil) } + assert_nothing_raised { @connection.rename_column(:group, :order, :values) } + end + + def test_introspect + assert_equal ["id", "order", "select_id"], @connection.columns(:group).map(&:name).sort + assert_equal ["index_group_on_select_id"], @connection.indexes(:group).map(&:name).sort + end + + def test_activerecord_model + x = Group.new + x.order = "x" + x.save! + x.order = "y" + x.save! + assert_equal x, Group.find_by_order("y") + assert_equal x, Group.find(x.id) + end + + def test_has_one_associations + create_test_fixtures :group, :values + v = Group.find(1).values + assert_equal 2, v.id + end + + def test_belongs_to_associations + create_test_fixtures :select, :group + gs = Select.find(2).groups + assert_equal 2, gs.length + assert_equal [2, 3], gs.collect(&:id).sort + end + + def test_has_and_belongs_to_many + create_test_fixtures :select, :distinct, :distinct_select + s = Distinct.find(1).selects + assert_equal 2, s.length + assert_equal [1, 2], s.collect(&:id).sort + end + + def test_activerecord_introspection + assert Group.table_exists? + assert_equal ["id", "order", "select_id"], Group.columns.map(&:name).sort + end + + def test_calculations_work_with_reserved_words + create_test_fixtures :group + assert_equal 3, Group.count + end + + def test_associations_work_with_reserved_words + create_test_fixtures :select, :group + selects = Select.all.merge!(includes: [:groups]).to_a + assert_no_queries do + selects.each { |select| select.groups } + end + end + + private + # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path + def create_test_fixtures(*fixture_names) + ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) + end +end 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/models/comment.rb b/activerecord/test/models/comment.rb index 8cba788598..d227f6fe86 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -29,6 +29,8 @@ class Comment < ActiveRecord::Base 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 |