diff options
Diffstat (limited to 'activerecord')
137 files changed, 2067 insertions, 1644 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index b2d13e86f3..abc415ea14 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,6 +1,128 @@ -* Fix `association_primary_key_type` for reflections with symbol primary key +* Fix `rake db:schema:load` with subdirectories. - Fixes #27864 + *Ryuta Kamizono* + +* Fix `rake db:migrate:status` with subdirectories. + + *Ryuta Kamizono* + +* Don't share options between reference id and type columns + + When using a polymorphic reference column in a migration, sharing options + between the two columns doesn't make sense since they are different types. + The `reference_id` column is usually an integer and the `reference_type` + column a string so options like `unsigned: true` will result in an invalid + table definition. + + *Ryuta Kamizono* + +* Use `max_identifier_length` for `index_name_length` in PostgreSQL adapter. + + *Ryuta Kamizono* + +* Deprecate `supports_migrations?` on connection adapters. + + *Ryuta Kamizono* + +* Fix regression of #1969 with SELECT aliases in HAVING clause. + + *Eugene Kenny* + +* Deprecate using `#quoted_id` in quoting. + + *Ryuta Kamizono* + +* Fix `wait_timeout` to configurable for mysql2 adapter. + + Fixes #26556. + + *Ryuta Kamizono* + + +## Rails 5.1.0.beta1 (February 23, 2017) ## + +* Correctly dump native timestamp types for MySQL. + + The native timestamp type in MySQL is different from datetime type. + Internal representation of the timestamp type is UNIX time, This means + that timestamp columns are affected by time zone. + + > SET time_zone = '+00:00'; + Query OK, 0 rows affected (0.00 sec) + + > INSERT INTO time_with_zone(ts,dt) VALUES (NOW(),NOW()); + Query OK, 1 row affected (0.02 sec) + + > SELECT * FROM time_with_zone; + +---------------------+---------------------+ + | ts | dt | + +---------------------+---------------------+ + | 2016-02-07 22:11:44 | 2016-02-07 22:11:44 | + +---------------------+---------------------+ + 1 row in set (0.00 sec) + + > SET time_zone = '-08:00'; + Query OK, 0 rows affected (0.00 sec) + + > SELECT * FROM time_with_zone; + +---------------------+---------------------+ + | ts | dt | + +---------------------+---------------------+ + | 2016-02-07 14:11:44 | 2016-02-07 22:11:44 | + +---------------------+---------------------+ + 1 row in set (0.00 sec) + + *Ryuta Kamizono* + +* All integer-like PKs are autoincrement unless they have an explicit default. + + *Matthew Draper* + +* Omit redundant `using: :btree` for schema dumping. + + *Ryuta Kamizono* + +* Deprecate passing `default` to `index_name_exists?`. + + *Ryuta Kamizono* + +* PostgreSQL: schema dumping support for interval and OID columns. + + *Ryuta Kamizono* + +* Deprecate `supports_primary_key?` on connection adapters since it's + been long unused and unsupported. + + *Ryuta Kamizono* + +* Make `table_name=` reset current statement cache, + so queries are not run against the previous table name. + + *namusyaka* + +* Allow `ActiveRecord::Base#as_json` to be passed a frozen Hash. + + *Isaac Betesh* + +* Fix inspection behavior when the :id column is not primary key. + + *namusyaka* + +* Deprecate locking records with unpersisted changes. + + *Marc Schütz* + +* Remove deprecated behavior that halts callbacks when the return is false. + + *Rafael Mendonça França* + +* Deprecate `ColumnDumper#migration_keys`. + + *Ryuta Kamizono* + +* Fix `association_primary_key_type` for reflections with symbol primary key. + + Fixes #27864. *Daniel Colson* @@ -39,7 +161,7 @@ *Ryuta Kamizono* -* Add the touch option to ActiveRecord#increment! and decrement!. +* Add the touch option to `#increment!` and `#decrement!`. *Hiroaki Izu* @@ -171,7 +293,7 @@ *Rafael Mendonça França* * Set `:time` as a timezone aware type and remove deprecation when - `config.active_record.time_zone_aware_types` is not explictly set. + `config.active_record.time_zone_aware_types` is not explicitly set. *Rafael Mendonça França* diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 84d0493a60..1cb2b2d7c6 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -83,7 +83,7 @@ module ActiveRecord end def scope - target_scope.merge(association_scope) + target_scope.merge!(association_scope) end # The scope for this association. diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index badde9973f..120d75416c 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -25,7 +25,7 @@ module ActiveRecord chain_head, chain_tail = get_chain(reflection, association, alias_tracker) scope.extending! Array(reflection.options[:extend]) - add_constraints(scope, owner, klass, reflection, chain_head, chain_tail) + add_constraints(scope, owner, reflection, chain_head, chain_tail) end def join_type @@ -60,8 +60,8 @@ module ActiveRecord table.create_join(table, table.create_on(constraint), join_type) end - def last_chain_scope(scope, table, reflection, owner, association_klass) - join_keys = reflection.join_keys(association_klass) + def last_chain_scope(scope, table, reflection, owner) + join_keys = reflection.join_keys key = join_keys.key foreign_key = join_keys.foreign_key @@ -80,8 +80,8 @@ module ActiveRecord value_transformation.call(value) end - def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) - join_keys = reflection.join_keys(association_klass) + def next_chain_scope(scope, table, reflection, foreign_table, next_reflection) + join_keys = reflection.join_keys key = join_keys.key foreign_key = join_keys.foreign_key @@ -120,10 +120,10 @@ module ActiveRecord [runtime_reflection, previous_reflection] end - def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail) + def add_constraints(scope, owner, refl, chain_head, chain_tail) owner_reflection = chain_tail table = owner_reflection.alias_name - scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass) + scope = last_chain_scope(scope, table, owner_reflection, owner) reflection = chain_head while reflection @@ -132,7 +132,7 @@ module ActiveRecord unless reflection == chain_tail foreign_table = next_reflection.alias_name - scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) + scope = next_chain_scope(scope, table, reflection, foreign_table, next_reflection) end # Exclude the scope of the association itself, because that diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 0437a79b84..77282e6463 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -30,13 +30,7 @@ module ActiveRecord reload end - if null_scope? - # Cache the proxy separately before the owner has an id - # or else a post-save proxy will still lack the id - @null_proxy ||= CollectionProxy.create(klass, self) - else - @proxy ||= CollectionProxy.create(klass, self) - end + CollectionProxy.create(klass, self) end # Implements the writer method, e.g. foo.items= for Foo.has_many :items @@ -315,9 +309,9 @@ module ActiveRecord record end - def scope(opts = {}) - scope = super() - scope.none! if opts.fetch(:nullify, true) && null_scope? + def scope + scope = super + scope.none! if null_scope? scope end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 0d84805b4d..55bf2e0ff0 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -28,12 +28,9 @@ module ActiveRecord # is computed directly through SQL and does not trigger by itself the # instantiation of the actual post records. class CollectionProxy < Relation - delegate :exists?, :update_all, :arel, to: :scope - def initialize(klass, association) #:nodoc: @association = association super klass, klass.arel_table, klass.predicate_builder - merge! association.scope(nullify: false) end def target @@ -956,19 +953,10 @@ module ActiveRecord @association end - # We don't want this object to be put on the scoping stack, because - # that could create an infinite loop where we call an @association - # method, which gets the current scope, which is this object, which - # delegates to @association, and so on. - def scoping - @association.scope.scoping { yield } - end - # Returns a <tt>Relation</tt> object for the records in this association def scope - @association.scope + @scope ||= @association.scope end - alias spawn scope # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays # contain the same number of elements and if each element is equal @@ -1100,6 +1088,7 @@ 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 end @@ -1121,11 +1110,21 @@ 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 self end + delegate_methods = [ + QueryMethods, + SpawnMethods, + ].flat_map { |klass| + klass.public_instance_methods(false) + } - self.public_instance_methods(false) + [:scoping] + + delegate(*delegate_methods, to: :scope) + private def find_nth_with_limit(index, limit) @@ -1149,6 +1148,18 @@ module ActiveRecord def exec_queries load_target end + + def respond_to_missing?(method, _) + scope.respond_to?(method) || super + end + + def method_missing(method, *args, &block) + if scope.respond_to?(method) + scope.public_send(method, *args, &block) + else + super + end + 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 87e0847ec1..8995b1e352 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -171,7 +171,7 @@ module ActiveRecord chain = child.reflection.chain foreign_table = parent.table foreign_klass = parent.base_klass - child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, chain) + child.join_constraints(foreign_table, foreign_klass, join_type, tables, chain) end def make_outer_joins(parent, child) diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index f5fcba1236..97cfec0302 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -23,7 +23,7 @@ module ActiveRecord JoinInformation = Struct.new :joins, :binds - def join_constraints(foreign_table, foreign_klass, node, join_type, tables, chain) + def join_constraints(foreign_table, foreign_klass, join_type, tables, chain) joins = [] binds = [] tables = tables.reverse @@ -34,35 +34,16 @@ module ActiveRecord table = tables.shift klass = reflection.klass - join_keys = reflection.join_keys(klass) + join_keys = reflection.join_keys key = join_keys.key foreign_key = join_keys.foreign_key constraint = build_constraint(klass, table, key, foreign_table, foreign_key) predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table)) - scope_chain_items = reflection.scopes.map do |item| - if item.is_a?(Relation) - item - else - ActiveRecord::Relation.create(klass, table, predicate_builder) - .instance_exec(node, &item) - end - end + scope_chain_items = reflection.join_scopes(table, predicate_builder) + klass_scope = reflection.klass_join_scope(table, predicate_builder) - klass_scope = - if klass.current_scope - klass.current_scope.clone.tap { |scope| - scope.joins_values = [] - } - else - relation = ActiveRecord::Relation.create( - klass, - table, - predicate_builder, - ) - klass.send(:build_default_scope, relation) - end scope_chain_items.concat [klass_scope].compact rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right| diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index b0e1391cb9..6aa414ba6b 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -50,7 +50,7 @@ module ActiveRecord super.tap do @previous_mutation_tracker = nil clear_mutation_trackers - @changed_attributes = HashWithIndifferentAccess.new + @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end end @@ -70,13 +70,13 @@ module ActiveRecord def changes_applied @previous_mutation_tracker = mutation_tracker - @changed_attributes = HashWithIndifferentAccess.new + @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new clear_mutation_trackers end def clear_changes_information @previous_mutation_tracker = nil - @changed_attributes = HashWithIndifferentAccess.new + @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new forget_attribute_assignments clear_mutation_trackers end @@ -275,7 +275,17 @@ module ActiveRecord def attribute_will_change!(attr_name) super - mutations_from_database.force_change(attr_name) + if self.class.has_attribute?(attr_name) + mutations_from_database.force_change(attr_name) + else + ActiveSupport::Deprecation.warn(<<-EOW.squish) + #{attr_name} is not an attribute known to Active Record. + This behavior is deprecated and will be removed in the next + version of Rails. If you'd like #{attr_name} to be managed + by Active Record, add `attribute :#{attr_name} to your class. + EOW + mutations_from_database.deprecated_force_change(attr_name) + end end def _update_record(*) diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 369a6e35aa..fdc4bf6621 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -54,7 +54,7 @@ module ActiveRecord attr_name.to_s end - name = self.class.primary_key if name == "id".freeze + name = self.class.primary_key if name == "id".freeze && self.class.primary_key _read_attribute(name, &block) end 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 df1231ad47..321d039ed4 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -1,5 +1,3 @@ -require "active_support/core_ext/string/strip" - module ActiveRecord module AttributeMethods module TimeZoneConversion diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb index 3417090830..4de993e169 100644 --- a/activerecord/lib/active_record/attribute_mutation_tracker.rb +++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb @@ -5,6 +5,7 @@ module ActiveRecord def initialize(attributes) @attributes = attributes @forced_changes = Set.new + @deprecated_forced_changes = Set.new end def changed_values @@ -31,7 +32,7 @@ module ActiveRecord end def any_changes? - attr_names.any? { |attr| changed?(attr) } + attr_names.any? { |attr| changed?(attr) } || deprecated_forced_changes.any? end def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) @@ -60,11 +61,15 @@ module ActiveRecord forced_changes << attr_name.to_s end + def deprecated_force_change(attr_name) + deprecated_forced_changes << attr_name.to_s + end + # TODO Change this to private once we've dropped Ruby 2.2 support. # Workaround for Ruby 2.2 "private attribute?" warning. protected - attr_reader :attributes, :forced_changes + attr_reader :attributes, :forced_changes, :deprecated_forced_changes private diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb index 2136da43fe..9c52a31b95 100644 --- a/activerecord/lib/active_record/coders/yaml_column.rb +++ b/activerecord/lib/active_record/coders/yaml_column.rb @@ -14,7 +14,7 @@ module ActiveRecord def dump(obj) return if obj.nil? - assert_valid_value(obj) + assert_valid_value(obj, action: "dump") YAML.dump obj end @@ -23,16 +23,16 @@ module ActiveRecord return yaml unless yaml.is_a?(String) && /^---/.match?(yaml) obj = YAML.load(yaml) - assert_valid_value(obj) + assert_valid_value(obj, action: "load") obj ||= object_class.new if object_class != Object obj end - def assert_valid_value(obj) + def assert_valid_value(obj, action:) unless obj.nil? || obj.is_a?(object_class) raise SerializationTypeMismatch, - "Attribute `#{@attr_name}` was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}" + "can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}" end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index ce4721c99d..3f2e86a98d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -353,6 +353,16 @@ module ActiveRecord @threads_blocking_new_connections = 0 @available = ConnectionLeasingQueue.new self + + @lock_thread = false + end + + def lock_thread=(lock_thread) + if lock_thread + @lock_thread = Thread.current + else + @lock_thread = nil + end end # Retrieve the connection associated with the current thread, or call @@ -361,7 +371,7 @@ module ActiveRecord # #connection can be called any number of times; the connection is # held in a cache keyed by a thread. def connection - @thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout + @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout end # Returns true if there is an open connection being used for the current thread. diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 7eab7de5d3..e53ba4e666 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -83,7 +83,9 @@ module ActiveRecord # the same SQL query and repeatedly return the same result each time, silently # undermining the randomness you were expecting. def clear_query_cache - @query_cache.clear + @lock.synchronize do + @query_cache.clear + end end def select_all(arel, name = nil, binds = [], preparable: nil) @@ -99,21 +101,23 @@ module ActiveRecord private def cache_sql(sql, name, binds) - result = - if @query_cache[sql].key?(binds) - ActiveSupport::Notifications.instrument( - "sql.active_record", - sql: sql, - binds: binds, - name: name, - connection_id: object_id, - cached: true, - ) - @query_cache[sql][binds] - else - @query_cache[sql][binds] = yield - end - result.dup + @lock.synchronize do + result = + if @query_cache[sql].key?(binds) + ActiveSupport::Notifications.instrument( + "sql.active_record", + sql: sql, + binds: binds, + name: name, + connection_id: object_id, + cached: true, + ) + @query_cache[sql][binds] + else + @query_cache[sql][binds] = yield + end + result.dup + end end # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 437e7c6510..e5a24b2aca 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -7,8 +7,13 @@ module ActiveRecord # Quotes the column value to help prevent # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection]. def quote(value) - # records are quoted as their primary key - return value.quoted_id if value.respond_to?(:quoted_id) + value = id_value_for_database(value) if value.is_a?(Base) + + if value.respond_to?(:quoted_id) + ActiveSupport::Deprecation.warn \ + "Using #quoted_id is deprecated and will be removed in Rails 5.2." + return value.quoted_id + end _quote(value) end @@ -17,6 +22,8 @@ module ActiveRecord # SQLite does not understand dates, so this method will convert a Date # to a String. def type_cast(value, column = nil) + value = id_value_for_database(value) if value.is_a?(Base) + if value.respond_to?(:quoted_id) && value.respond_to?(:id) return value.id end @@ -141,19 +148,29 @@ module ActiveRecord quoted_date(value).sub(/\A2000-01-01 /, "") end + def quoted_binary(value) # :nodoc: + "'#{quote_string(value.to_s)}'" + end + private def type_casted_binds(binds) binds.map { |attr| type_cast(attr.value_for_database) } end + def id_value_for_database(value) + if primary_key = value.class.primary_key + value.instance_variable_get(:@attributes)[primary_key].value_for_database + end + end + def types_which_need_no_typecasting [nil, Numeric, String] end def _quote(value) case value - when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data + when String, ActiveSupport::Multibyte::Chars "'#{quote_string(value.to_s)}'" when true then quoted_true when false then quoted_false @@ -161,6 +178,7 @@ module ActiveRecord # BigDecimals need to be put in a non-normalized form and quoted. when BigDecimal then value.to_s("F") when Numeric, ActiveSupport::Duration then value.to_s + when Type::Binary::Data then quoted_binary(value) when Type::Time::Value then "'#{quoted_time(value)}'" when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index 81dec97bf7..c48a4acff8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -29,7 +29,7 @@ module ActiveRecord end def visit_ColumnDefinition(o) - o.sql_type ||= type_to_sql(o.type, o.limit, o.precision, o.scale) + o.sql_type = type_to_sql(o.type, o.options) column_sql = "#{quote_column_name(o.name)} #{o.sql_type}" add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key column_sql @@ -96,18 +96,7 @@ module ActiveRecord end def column_options(o) - column_options = {} - column_options[:null] = o.null unless o.null.nil? - column_options[:default] = o.default unless o.default.nil? - column_options[:column] = o - column_options[:first] = o.first - column_options[:after] = o.after - column_options[:auto_increment] = o.auto_increment - column_options[:primary_key] = o.primary_key - column_options[:collation] = o.collation - column_options[:comment] = o.comment - column_options[:as] = o.as - column_options + o.options.merge(column: o) end def add_column_options!(sql, options) 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 ecc6caa8f2..4682afc188 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -9,9 +9,21 @@ module ActiveRecord # are typically created by methods in TableDefinition, and added to the # +columns+ attribute of said TableDefinition object, in order to be used # for generating a number of table creation or table changing SQL statements. - ColumnDefinition = Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment, :as) do # :nodoc: + ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc: def primary_key? - primary_key || type.to_sym == :primary_key + options[:primary_key] + end + + [:limit, :precision, :scale, :default, :null, :collation, :comment].each do |option_name| + module_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{option_name} + options[:#{option_name}] + end + + def #{option_name}=(value) + options[:#{option_name}] = value + end + CODE end end @@ -104,16 +116,12 @@ module ActiveRecord private - def as_options(value, default = {}) - if value.is_a?(Hash) - value - else - default - end + def as_options(value) + value.is_a?(Hash) ? value : {} end def polymorphic_options - as_options(polymorphic, options) + as_options(polymorphic) end def index_options @@ -354,34 +362,22 @@ module ActiveRecord # # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use. def references(*args, **options) - args.each do |col| - ReferenceDefinition.new(col, **options).add_to(self) + args.each do |ref_name| + ReferenceDefinition.new(ref_name, options).add_to(self) end end alias :belongs_to :references - def new_column_definition(name, type, options) # :nodoc: + def new_column_definition(name, type, **options) # :nodoc: type = aliased_types(type.to_s, type) - column = create_column_definition name, type - - column.limit = options[:limit] - column.precision = options[:precision] - column.scale = options[:scale] - column.default = options[:default] - column.null = options[:null] - column.first = options[:first] - column.after = options[:after] - column.auto_increment = options[:auto_increment] - column.primary_key = type == :primary_key || options[:primary_key] - column.collation = options[:collation] - column.comment = options[:comment] - column.as = options[:as] - column + options[:primary_key] ||= type == :primary_key + options[:null] = false if options[:primary_key] + create_column_definition(name, type, options) end private - def create_column_definition(name, type) - ColumnDefinition.new name, type + def create_column_definition(name, type, options) + ColumnDefinition.new(name, type, options) end def aliased_types(name, fallback) @@ -589,8 +585,7 @@ module ActiveRecord # t.belongs_to(:supplier, foreign_key: true) # # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use. - def references(*args) - options = args.extract_options! + def references(*args, **options) args.each do |ref_name| @base.add_reference(name, ref_name, options) end @@ -603,8 +598,7 @@ module ActiveRecord # t.remove_belongs_to(:supplier, polymorphic: true) # # See {connection.remove_reference}[rdoc-ref:SchemaStatements#remove_reference] - def remove_references(*args) - options = args.extract_options! + def remove_references(*args, **options) args.each do |ref_name| @base.remove_reference(name, ref_name, options) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index d6c912a69e..34036d8a1d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -14,6 +14,8 @@ module ActiveRecord return {} if default_primary_key?(column) spec = { id: schema_type(column).inspect } spec.merge!(prepare_column_options(column).except!(:null)) + spec[:default] ||= "nil" if explicit_primary_key_default?(column) + spec end # This can be overridden on an Adapter level basis to support other @@ -49,9 +51,10 @@ module ActiveRecord end # Lists the valid migration options - def migration_keys - [:limit, :precision, :scale, :default, :null, :collation, :comment] + def migration_keys # :nodoc: + column_options_keys end + deprecate :migration_keys private @@ -59,6 +62,10 @@ module ActiveRecord schema_type(column) == :bigint end + def explicit_primary_key_default?(column) + false + end + def schema_type_with_virtual(column) if supports_virtual_columns? && column.virtual? :virtual 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 d7e2818e35..c9dd915e98 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -122,7 +122,7 @@ module ActiveRecord checks = [] checks << lambda { |c| c.name == column_name } checks << lambda { |c| c.type == type } if type - migration_keys.each do |attr| + column_options_keys.each do |attr| checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr) end @@ -273,8 +273,8 @@ module ActiveRecord yield td if block_given? - if options[:force] && data_source_exists?(table_name) - drop_table(table_name, options) + if options[:force] + drop_table(table_name, **options, if_exists: true) end result = execute schema_creation.accept td @@ -334,18 +334,16 @@ module ActiveRecord # part_id int NOT NULL, # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 # - def create_join_table(table_1, table_2, options = {}) + def create_join_table(table_1, table_2, column_options: {}, **options) join_table_name = find_join_table_name(table_1, table_2, options) - column_options = options.delete(:column_options) || {} - column_options.reverse_merge!(null: false) - type = column_options.delete(:type) || :integer + column_options.reverse_merge!(null: false, index: false) - t1_column, t2_column = [table_1, table_2].map { |t| t.to_s.singularize.foreign_key } + t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize } create_table(join_table_name, options.merge!(id: false)) do |td| - td.send type, t1_column, column_options - td.send type, t2_column, column_options + td.references t1_ref, column_options + td.references t2_ref, column_options yield td if block_given? end end @@ -768,16 +766,17 @@ module ActiveRecord raise ArgumentError, "You must specify the index name" end else - index_name(table_name, column: options) + index_name(table_name, index_name_options(options)) end end # Verifies the existence of an index with a given name. - # - # The default argument is returned if the underlying implementation does not define the indexes method, - # as there's no way to determine the correct answer in that case. - def index_name_exists?(table_name, index_name, default) - return default unless respond_to?(:indexes) + def index_name_exists?(table_name, index_name, default = nil) + unless default.nil? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing default to #index_name_exists? is deprecated without replacement. + MSG + end index_name = index_name.to_s indexes(table_name).detect { |i| i.name == index_name } end @@ -828,8 +827,8 @@ module ActiveRecord # # add_reference(:products, :supplier, foreign_key: {to_table: :firms}) # - def add_reference(table_name, *args) - ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self)) + def add_reference(table_name, ref_name, **options) + ReferenceDefinition.new(ref_name, options).add_to(update_table_definition(table_name, self)) end alias :add_belongs_to :add_reference @@ -856,6 +855,7 @@ module ActiveRecord else foreign_key_options = { to_table: reference_name } end + foreign_key_options[:column] ||= "#{ref_name}_id" remove_foreign_key(table_name, **foreign_key_options) end @@ -1028,9 +1028,8 @@ module ActiveRecord sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name) migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i) - paths = migrations_paths.map { |p| "#{p}/[0-9]*_*.rb" } - versions = Dir[*paths].map do |filename| - filename.split("/").last.split("_").first.to_i + versions = ActiveRecord::Migrator.migration_files(migrations_paths).map do |file| + ActiveRecord::Migrator.parse_migration_filename(file).first.to_i end unless migrated.include?(version) @@ -1052,7 +1051,7 @@ module ActiveRecord end end - def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: + def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc: type = type.to_sym if type if native = native_database_types[type] column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup @@ -1070,7 +1069,7 @@ module ActiveRecord raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified" end - elsif [:datetime, :time].include?(type) && precision ||= native[:precision] + elsif [:datetime, :timestamp, :time, :interval].include?(type) && precision ||= native[:precision] if (0..6) === precision column_type_sql << "(#{precision})" else @@ -1122,18 +1121,14 @@ module ActiveRecord end def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc: - if column_name.is_a?(String) && /\W/.match?(column_name) - column_names = column_name - else - column_names = Array(column_name) - end + column_names = index_column_names(column_name) options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type) index_type = options[:type].to_s if options.key?(:type) index_type ||= options[:unique] ? "UNIQUE" : "" index_name = options[:name].to_s if options.key?(:name) - index_name ||= index_name(table_name, index_name_options(column_names)) + index_name ||= index_name(table_name, column_names) if options.key?(:algorithm) algorithm = index_algorithms.fetch(options[:algorithm]) { @@ -1149,7 +1144,7 @@ module ActiveRecord validate_index_length!(table_name, index_name, options.fetch(:internal, false)) - if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false) + if data_source_exists?(table_name) && index_name_exists?(table_name, index_name) raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" end index_columns = quoted_columns_for_index(column_names, options).join(", ") @@ -1172,6 +1167,9 @@ module ActiveRecord end private + def column_options_keys + [:limit, :precision, :scale, :default, :null, :collation, :comment] + end def add_index_sort_order(quoted_columns, **options) if order = options[:order] @@ -1210,13 +1208,13 @@ module ActiveRecord if options.is_a?(Hash) checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name) - column_names = Array(options[:column]).map(&:to_s) + column_names = index_column_names(options[:column]) else - column_names = Array(options).map(&:to_s) + column_names = index_column_names(options) end - if column_names.any? - checks << lambda { |i| i.columns.join("_and_") == column_names.join("_and_") } + if column_names.present? + checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) } end raise ArgumentError, "No name or columns specified" if checks.none? @@ -1263,8 +1261,16 @@ module ActiveRecord AlterTable.new create_table_definition(name) end + def index_column_names(column_names) + if column_names.is_a?(String) && /\W/.match?(column_names) + column_names + else + Array(column_names) + end + end + def index_name_options(column_names) - if column_names.is_a?(String) + if column_names.is_a?(String) && /\W/.match?(column_names) column_names = column_names.scan(/\w+/).join("_") end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index fd11cab5c0..ef1d9f81a9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -107,6 +107,7 @@ module ActiveRecord @schema_cache = SchemaCache.new self @quoted_column_names, @quoted_table_names = {}, {} @visitor = arel_visitor + @lock = Monitor.new if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @prepared_statements = true @@ -153,8 +154,8 @@ module ActiveRecord Arel::Visitors::ToSql.new(self) end - def valid_type?(type) - false + def valid_type?(type) # :nodoc: + !native_database_types[type].nil? end def schema_creation @@ -231,16 +232,15 @@ module ActiveRecord self.class::ADAPTER_NAME end - # Does this adapter support migrations? - def supports_migrations? - false + def supports_migrations? # :nodoc: + true end + deprecate :supports_migrations? - # Can this adapter determine the primary key for tables not attached - # to an Active Record class, such as join tables? - def supports_primary_key? - false + def supports_primary_key? # :nodoc: + true end + deprecate :supports_primary_key? # Does this adapter support DDL rollbacks in transactions? That is, would # CREATE TABLE or ALTER TABLE get rolled back by a transaction? @@ -439,6 +439,9 @@ module ActiveRecord # This is done under the hood by calling #active?. If the connection # is no longer active, then this method will reconnect to the database. def verify!(*ignored) + if ignored.size > 0 + ActiveSupport::Deprecation.warn("Passing arguments to #verify method of the connection has no effect and has been deprecated. Please remove all arguments from the #verify method call.") + end reconnect! unless active? end @@ -452,15 +455,15 @@ module ActiveRecord @connection end - def case_sensitive_comparison(table, attribute, column, value) - table[attribute].eq(Arel::Nodes::BindParam.new) + def case_sensitive_comparison(table, attribute, column, value) # :nodoc: + table[attribute].eq(value) end - def case_insensitive_comparison(table, attribute, column, value) + def case_insensitive_comparison(table, attribute, column, value) # :nodoc: if can_perform_case_insensitive_comparison_for?(column) - table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new)) + table[attribute].lower.eq(table.lower(value)) else - table[attribute].eq(Arel::Nodes::BindParam.new) + table[attribute].eq(value) end end @@ -510,6 +513,10 @@ module ActiveRecord result end + def default_index_type?(index) # :nodoc: + index.using.nil? + end + private def initialize_type_map(m) @@ -602,7 +609,11 @@ module ActiveRecord binds: binds, type_casted_binds: type_casted_binds, statement_name: statement_name, - connection_id: object_id) { yield } + connection_id: object_id) do + @lock.synchronize do + yield + end + end rescue => e raise translate_exception_class(e, sql) 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 89b4ec9473..e3b6327dd8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -46,6 +46,7 @@ module ActiveRecord float: { name: "float" }, decimal: { name: "decimal" }, datetime: { name: "datetime" }, + timestamp: { name: "timestamp" }, time: { name: "time" }, date: { name: "date" }, binary: { name: "blob", limit: 65535 }, @@ -88,15 +89,6 @@ module ActiveRecord /mariadb/i.match?(full_version) end - # Returns true, since this connection adapter supports migrations. - def supports_migrations? - true - end - - def supports_primary_key? - true - end - def supports_bulk_alter? #:nodoc: true end @@ -574,7 +566,7 @@ module ActiveRecord end # Maps logical Rails types to MySQL-specific data types. - def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil) + def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc: sql = \ case type.to_s when "integer" @@ -590,7 +582,7 @@ module ActiveRecord binary_to_sql(limit) end else - super(type, limit, precision, scale) + super end sql << " unsigned" if unsigned && type != :primary_key @@ -619,9 +611,9 @@ module ActiveRecord SQL end - def case_sensitive_comparison(table, attribute, column, value) + def case_sensitive_comparison(table, attribute, column, value) # :nodoc: if column.collation && !column.case_sensitive? - table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new)) + table[attribute].eq(Arel::Nodes::Bin.new(value)) else super end @@ -651,8 +643,8 @@ module ActiveRecord self.class.type_cast_config_to_boolean(@config.fetch(:strict, true)) end - def valid_type?(type) - !native_database_types[type].nil? + def default_index_type?(index) # :nodoc: + index.using == :btree || super end private @@ -708,7 +700,7 @@ module ActiveRecord end def extract_precision(sql_type) - if /time/.match?(sql_type) + if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type) super || 0 else super @@ -785,11 +777,11 @@ module ActiveRecord def change_column_sql(table_name, column_name, type, options = {}) column = column_for(table_name, column_name) - unless options_include_default?(options) + unless options.key?(:default) options[:default] = column.default end - unless options.has_key?(:null) + unless options.key?(:null) options[:null] = column.null end @@ -869,9 +861,9 @@ module ActiveRecord variables["sql_auto_is_null"] = 0 # Increase timeout so the server doesn't disconnect us. - wait_timeout = @config[:wait_timeout] + wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout]) wait_timeout = 2147483 unless wait_timeout.is_a?(Integer) - variables["wait_timeout"] = self.class.type_cast_config_to_integer(wait_timeout) + variables["wait_timeout"] = wait_timeout defaults = [":default", :default].to_set diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index dcf56997db..3e4ea28f63 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -149,9 +149,18 @@ module ActiveRecord # Expands each key in @configurations hash into fully resolved hash def resolve_all config = configurations.dup + + if env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + env_config = config[env] if config[env].is_a?(Hash) && !(config[env].key?("adapter") || config[env].key?("url")) + end + + config.reject! { |k, v| v.is_a?(Hash) && !(v.key?("adapter") || v.key?("url")) } + config.merge! env_config if env_config + config.each do |key, value| config[key] = resolve(value) if value end + config end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb index 9d11ad28d4..d4f5906b33 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb @@ -36,15 +36,9 @@ module ActiveRecord end end - private - - def _quote(value) - if value.is_a?(Type::Binary::Data) - "x'#{value.hex}'" - else - super - end - end + def quoted_binary(value) + "x'#{value.hex}'" + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb index 39c2acbca9..083cd6340f 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb @@ -1,7 +1,7 @@ module ActiveRecord module ConnectionAdapters module MySQL - class SchemaCreation < AbstractAdapter::SchemaCreation + class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc: delegate :add_sql_comment!, :mariadb?, to: :@conn private :add_sql_comment!, :mariadb? @@ -11,11 +11,6 @@ module ActiveRecord "DROP FOREIGN KEY #{name}" end - def visit_ColumnDefinition(o) - o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.unsigned) - super - end - def visit_AddColumnDefinition(o) add_column_position!(super, column_options(o.column)) end @@ -29,14 +24,15 @@ module ActiveRecord add_sql_comment!(super, options[:comment]) end - def column_options(o) - column_options = super - column_options[:charset] = o.charset - column_options[:stored] = o.stored - column_options - end - def add_column_options!(sql, options) + # By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values, + # and assigning NULL assigns the current timestamp. To permit a TIMESTAMP + # column to contain NULL, explicitly declare it with the NULL attribute. + # See http://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html + if /\Atimestamp\b/.match?(options[:column].sql_type) && !options[:primary_key] + sql << " NULL" unless options[:null] == false || options_include_default?(options) + end + if charset = options[:charset] sql << " CHARACTER SET #{charset}" end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb index 76ebd0bf6c..6d88c14d50 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -56,14 +56,10 @@ module ActiveRecord end end - class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition - attr_accessor :charset, :unsigned, :stored - end - class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods - def new_column_definition(name, type, options) # :nodoc: + def new_column_definition(name, type, **options) # :nodoc: case type when :virtual type = options[:type] @@ -76,17 +72,13 @@ module ActiveRecord type = $~[:type].to_sym options[:unsigned] = true end - column = super - column.unsigned = options[:unsigned] - column.charset = options[:charset] - column.stored = options[:stored] - column + + super end private - - def create_column_definition(name, type) - MySQL::ColumnDefinition.new(name, type) + def aliased_types(name, fallback) + fallback end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb index 7a277b8cfd..3e0afd9761 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -1,16 +1,7 @@ module ActiveRecord module ConnectionAdapters module MySQL - module ColumnDumper - def column_spec_for_primary_key(column) - spec = super - if [:integer, :bigint].include?(schema_type(column)) && !column.auto_increment? - spec[:default] ||= schema_default(column) || "nil" - end - spec[:unsigned] = "true" if column.unsigned? - spec - end - + module ColumnDumper # :nodoc: def prepare_column_options(column) spec = super spec[:unsigned] = "true" if column.unsigned? @@ -31,11 +22,18 @@ module ActiveRecord private def default_primary_key?(column) - super && column.auto_increment? + super && column.auto_increment? && !column.unsigned? + end + + def explicit_primary_key_default?(column) + column.type == :integer && !column.auto_increment? end def schema_type(column) - if column.sql_type == "tinyblob" + case column.sql_type + when /\Atimestamp\b/ + :timestamp + when "tinyblob" :blob else super @@ -43,7 +41,7 @@ module ActiveRecord end def schema_precision(column) - super unless /time/.match?(column.sql_type) && column.precision == 0 + super unless /\A(?:date)?time(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0 end def schema_collation(column) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 0e526f6201..4098250f3e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -11,6 +11,7 @@ require "active_record/connection_adapters/postgresql/oid/inet" require "active_record/connection_adapters/postgresql/oid/json" require "active_record/connection_adapters/postgresql/oid/jsonb" require "active_record/connection_adapters/postgresql/oid/money" +require "active_record/connection_adapters/postgresql/oid/oid" require "active_record/connection_adapters/postgresql/oid/point" require "active_record/connection_adapters/postgresql/oid/legacy_point" require "active_record/connection_adapters/postgresql/oid/range" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb new file mode 100644 index 0000000000..9c2ac08b30 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb @@ -0,0 +1,13 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Oid < Type::Integer # :nodoc: + def type + :oid + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb index 2d2fede4e8..564e82a4ac 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb @@ -5,8 +5,9 @@ module ActiveRecord class SpecializedString < Type::String # :nodoc: attr_reader :type - def initialize(type) + def initialize(type, **options) @type = type + super(options) 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 3783925954..6663448a99 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -55,6 +55,10 @@ module ActiveRecord end end + def quoted_binary(value) # :nodoc: + "'#{escape_bytea(value.to_s)}'" + end + def quote_default_expression(value, column) # :nodoc: if value.is_a?(Proc) value.call @@ -76,8 +80,6 @@ module ActiveRecord def _quote(value) case value - when Type::Binary::Data - "'#{escape_bytea(value.to_s)}'" when OID::Xml::Data "xml '#{quote_string(value.to_s)}'" when OID::Bit::Data diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb new file mode 100644 index 0000000000..e1d5089115 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb @@ -0,0 +1,15 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc: + private + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index c3e182b43d..11ea1e5144 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -42,7 +42,7 @@ module ActiveRecord # a record (as primary keys cannot be +nil+). This might be done via the # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. def primary_key(name, type = :primary_key, **options) - options[:auto_increment] = true if [:primary_key, :integer, :bigint].include?(type) && !options.key?(:default) + options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default) if type == :uuid options[:default] = options.fetch(:default, "gen_random_uuid()") elsif options.delete(:auto_increment) == true && %i(integer bigint).include?(type) @@ -88,6 +88,10 @@ module ActiveRecord args.each { |name| column(name, :inet, options) } end + def interval(*args, **options) + args.each { |name| column(name, :interval, options) } + end + def int4range(*args, **options) args.each { |name| column(name, :int4range, options) } end @@ -120,6 +124,10 @@ module ActiveRecord args.each { |name| column(name, :numrange, options) } end + def oid(*args, **options) + args.each { |name| column(name, :oid, options) } + end + def point(*args, **options) args.each { |name| column(name, :point, options) } end @@ -173,24 +181,8 @@ module ActiveRecord end end - class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition - attr_accessor :array - end - class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods - - def new_column_definition(name, type, options) # :nodoc: - column = super - column.array = options[:array] - column - end - - private - - def create_column_definition(name, type) - PostgreSQL::ColumnDefinition.new name, type - end end class Table < ActiveRecord::ConnectionAdapters::Table diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb index 7808d37deb..5555a46b6b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -1,15 +1,7 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL - module ColumnDumper - def column_spec_for_primary_key(column) - spec = super - if schema_type(column) == :uuid - spec[:default] ||= "nil" - end - spec - end - + module ColumnDumper # :nodoc: # Adds +:array+ option to the default set def prepare_column_options(column) spec = super @@ -28,6 +20,10 @@ module ActiveRecord schema_type(column) == :bigserial end + def explicit_primary_key_default?(column) + column.type == :uuid || (column.type == :integer && !column.serial?) + end + def schema_type(column) return super unless column.serial? 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 bfda113e40..afef0da5c7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -3,22 +3,6 @@ require "active_support/core_ext/string/strip" module ActiveRecord module ConnectionAdapters module PostgreSQL - class SchemaCreation < AbstractAdapter::SchemaCreation - private - - def visit_ColumnDefinition(o) - o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array) - super - end - - def add_column_options!(sql, options) - if options[:collation] - sql << " COLLATE \"#{options[:collation]}\"" - end - super - end - end - module SchemaStatements # Drops the database specified on the +name+ attribute # and creates it again using the provided +options+. @@ -148,7 +132,12 @@ module ActiveRecord end # Verifies existence of an index with a given name. - def index_name_exists?(table_name, index_name, default) + def index_name_exists?(table_name, index_name, default = nil) + unless default.nil? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing default to #index_name_exists? is deprecated without replacement. + MSG + end table = Utils.extract_schema_qualified_name(table_name.to_s) index = Utils.extract_schema_qualified_name(index_name.to_s) @@ -441,17 +430,18 @@ module ActiveRecord end def primary_keys(table_name) # :nodoc: + name = Utils.extract_schema_qualified_name(table_name.to_s) select_values(<<-SQL.strip_heredoc, "SCHEMA") - WITH pk_constraint AS ( - SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint - WHERE contype = 'p' - AND conrelid = #{quote(quote_table_name(table_name))}::regclass - ), cons AS ( - SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint - ) - SELECT attr.attname FROM pg_attribute attr - INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum - ORDER BY cons.rownum + SELECT column_name + FROM information_schema.key_column_usage kcu + JOIN information_schema.table_constraints tc + ON kcu.table_name = tc.table_name + AND kcu.table_schema = tc.table_schema + AND kcu.constraint_name = tc.constraint_name + WHERE constraint_type = 'PRIMARY KEY' + AND kcu.table_name = #{quote(name.identifier)} + AND kcu.table_schema = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"} + ORDER BY kcu.ordinal_position SQL end @@ -486,7 +476,7 @@ module ActiveRecord clear_cache! quoted_table_name = quote_table_name(table_name) quoted_column_name = quote_column_name(column_name) - sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array]) + sql_type = type_to_sql(type, options) sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}" if options[:collation] sql << " COLLATE \"#{options[:collation]}\"" @@ -494,12 +484,12 @@ module ActiveRecord if options[:using] sql << " USING #{options[:using]}" elsif options[:cast_as] - cast_as_type = type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale], options[:array]) + cast_as_type = type_to_sql(options[:cast_as], options) sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})" end execute sql - change_column_default(table_name, column_name, options[:default]) if options_include_default?(options) + change_column_default(table_name, column_name, options[:default]) if options.key?(:default) change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment) end @@ -625,12 +615,8 @@ module ActiveRecord end end - def index_name_length - 63 - end - # Maps logical Rails types to PostgreSQL-specific data types. - def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil) + def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc: sql = \ case type.to_s when "binary" @@ -655,7 +641,7 @@ module ActiveRecord else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.") end else - super(type, limit, precision, scale) + super end sql << "[]" if array && type != :primary_key diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index a2c5ef6817..bc04565434 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -9,6 +9,7 @@ require "active_record/connection_adapters/postgresql/explain_pretty_printer" require "active_record/connection_adapters/postgresql/oid" require "active_record/connection_adapters/postgresql/quoting" require "active_record/connection_adapters/postgresql/referential_integrity" +require "active_record/connection_adapters/postgresql/schema_creation" require "active_record/connection_adapters/postgresql/schema_definitions" require "active_record/connection_adapters/postgresql/schema_dumper" require "active_record/connection_adapters/postgresql/schema_statements" @@ -108,6 +109,8 @@ module ActiveRecord bit: { name: "bit" }, bit_varying: { name: "bit varying" }, money: { name: "money" }, + interval: { name: "interval" }, + oid: { name: "oid" }, } OID = PostgreSQL::OID #:nodoc: @@ -212,7 +215,7 @@ module ActiveRecord # @local_tz is initialized as nil to avoid warnings when connect tries to use it @local_tz = nil - @table_alias_length = nil + @max_identifier_length = nil connect add_pg_encoders @@ -233,7 +236,9 @@ module ActiveRecord # Clears the prepared statements cache. def clear_cache! - @statements.clear + @lock.synchronize do + @statements.clear + end end def truncate(table_name, name = nil) @@ -276,16 +281,6 @@ module ActiveRecord NATIVE_DATABASE_TYPES end - # Returns true, since this connection adapter supports migrations. - def supports_migrations? - true - end - - # Does PostgreSQL support finding primary key on non-Active Record tables? - def supports_primary_key? #:nodoc: - true - end - def set_standard_conforming_strings execute("SET standard_conforming_strings = on", "SCHEMA") end @@ -363,8 +358,9 @@ module ActiveRecord # Returns the configured supported identifier length supported by PostgreSQL def table_alias_length - @table_alias_length ||= query("SHOW max_identifier_length", "SCHEMA")[0][0].to_i + @max_identifier_length ||= select_value("SHOW max_identifier_length", "SCHEMA").to_i end + alias index_name_length table_alias_length # Set the authorized user for this session def session_auth=(user) @@ -376,10 +372,6 @@ module ActiveRecord @use_insert_returning end - def valid_type?(type) - !native_database_types[type].nil? - end - def update_table_definition(table_name, base) #:nodoc: PostgreSQL::Table.new(table_name, base) end @@ -404,6 +396,10 @@ module ActiveRecord @connection.server_version end + def default_index_type?(index) # :nodoc: + index.using == :btree || super + end + private # See http://www.postgresql.org/docs/current/static/errcodes-appendix.html @@ -455,7 +451,7 @@ module ActiveRecord register_class_with_limit m, "int2", Type::Integer register_class_with_limit m, "int4", Type::Integer register_class_with_limit m, "int8", Type::Integer - m.alias_type "oid", "int2" + m.register_type "oid", OID::Oid.new m.register_type "float4", Type::Float.new m.alias_type "float8", "float4" m.register_type "text", Type::Text.new @@ -490,8 +486,10 @@ module ActiveRecord m.register_type "polygon", OID::SpecializedString.new(:polygon) m.register_type "circle", OID::SpecializedString.new(:circle) - # FIXME: why are we keeping these types as strings? - m.alias_type "interval", "varchar" + m.register_type "interval" do |_, _, sql_type| + precision = extract_precision(sql_type) + OID::SpecializedString.new(:interval, precision: precision) + end register_class_with_precision m, "time", Type::Time register_class_with_precision m, "timestamp", OID::DateTime @@ -633,8 +631,10 @@ module ActiveRecord if in_transaction? raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message) else - # outside of transactions we can simply flush this query and retry - @statements.delete sql_key(sql) + @lock.synchronize do + # outside of transactions we can simply flush this query and retry + @statements.delete sql_key(sql) + end retry end end @@ -670,19 +670,21 @@ module ActiveRecord # Prepare the statement if it hasn't been prepared, return # the statement key. def prepare_statement(sql) - sql_key = sql_key(sql) - unless @statements.key? sql_key - nextkey = @statements.next_key - begin - @connection.prepare nextkey, sql - rescue => e - raise translate_exception_class(e, sql) + @lock.synchronize do + sql_key = sql_key(sql) + unless @statements.key? sql_key + nextkey = @statements.next_key + begin + @connection.prepare nextkey, sql + rescue => e + raise translate_exception_class(e, sql) + end + # Clear the queue + @connection.get_last_result + @statements[sql_key] = nextkey end - # Clear the queue - @connection.get_last_result - @statements[sql_key] = nextkey + @statements[sql_key] end - @statements[sql_key] end # Connects to a PostgreSQL server and sets up the adapter depending on the diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb index f01ed67b0f..7276a65098 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb @@ -18,15 +18,11 @@ module ActiveRecord quoted_date(value) end - private + def quoted_binary(value) + "x'#{value.hex}'" + end - def _quote(value) - if value.is_a?(Type::Binary::Data) - "x'#{value.hex}'" - else - super - end - end + private def _type_cast(value) case value diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb index 70c0d28830..bc798d1dbb 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb @@ -1,15 +1,8 @@ module ActiveRecord module ConnectionAdapters module SQLite3 - class SchemaCreation < AbstractAdapter::SchemaCreation + class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc: private - - def column_options(o) - options = super - options[:null] = false if o.primary_key - options - end - def add_column_options!(sql, options) if options[:collation] sql << " COLLATE \"#{options[:collation]}\"" diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb index f9bb7e6d82..e157e4b218 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb @@ -13,6 +13,11 @@ module ActiveRecord class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods + + def references(*args, **options) + super(*args, type: :integer, **options) + end + alias :belongs_to :references end class Table < ActiveRecord::ConnectionAdapters::Table diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb index c027fef83c..eec018eda3 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb @@ -1,12 +1,16 @@ module ActiveRecord module ConnectionAdapters module SQLite3 - module ColumnDumper + module ColumnDumper # :nodoc: private def default_primary_key?(column) schema_type(column) == :integer end + + def explicit_primary_key_default?(column) + column.bigint? + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index ca6de37a6b..8b627a6d4d 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -117,15 +117,6 @@ module ActiveRecord true end - # Returns true, since this connection adapter supports migrations. - def supports_migrations? #:nodoc: - true - end - - def supports_primary_key? #:nodoc: - true - end - def requires_reloading? true end @@ -167,10 +158,6 @@ module ActiveRecord true end - def valid_type?(type) - true - end - # Returns 62. SQLite supports index names up to 64 # characters. The rest is used by Rails internally to perform # temporary rename operations @@ -424,11 +411,10 @@ module ActiveRecord def change_column(table_name, column_name, type, options = {}) #:nodoc: alter_table(table_name) do |definition| - include_default = options_include_default?(options) definition[column_name].instance_eval do self.type = type self.limit = options[:limit] if options.include?(:limit) - self.default = options[:default] if include_default + self.default = options[:default] if options.include?(:default) self.null = options[:null] if options.include?(:null) self.precision = options[:precision] if options.include?(:precision) self.scale = options[:scale] if options.include?(:scale) @@ -443,6 +429,11 @@ module ActiveRecord rename_column_indexes(table_name, column.name, new_column_name) end + def add_reference(table_name, ref_name, **options) # :nodoc: + super(table_name, ref_name, type: :integer, **options) + end + alias :add_belongs_to :add_reference + def foreign_keys(table_name) fk_info = select_all("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA") fk_info.map do |row| diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 91d8054ef2..e79167d568 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -970,6 +970,7 @@ module ActiveRecord @fixture_connections = enlist_fixture_connections @fixture_connections.each do |connection| connection.begin_transaction joinable: false + connection.pool.lock_thread = true end # When connections are established in the future, begin a transaction too @@ -985,6 +986,7 @@ module ActiveRecord if connection && !@fixture_connections.include?(connection) connection.begin_transaction joinable: false + connection.pool.lock_thread = true @fixture_connections << connection end end @@ -1007,6 +1009,7 @@ module ActiveRecord ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber @fixture_connections.each do |connection| connection.rollback_transaction if connection.transaction_open? + connection.pool.lock_thread = false end @fixture_connections.clear else diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb index f33456a744..174f716152 100644 --- a/activerecord/lib/active_record/gem_version.rb +++ b/activerecord/lib/active_record/gem_version.rb @@ -8,7 +8,7 @@ module ActiveRecord MAJOR = 5 MINOR = 1 TINY = 0 - PRE = "alpha" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb index e73cb4fc12..263e2a5f7f 100644 --- a/activerecord/lib/active_record/locking/pessimistic.rb +++ b/activerecord/lib/active_record/locking/pessimistic.rb @@ -59,7 +59,16 @@ module ActiveRecord # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns # the locked record. def lock!(lock = true) - reload(lock: lock) if persisted? + if persisted? + if changed? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Locking a record with unpersisted changes is deprecated and will raise an + exception in Rails 5.2. Use `save` to persist the changes, or `reload` to + discard them explicitly. + MSG + end + reload(lock: lock) + end self end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 40f6226315..c47e32df59 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -548,12 +548,10 @@ module ActiveRecord end def call(env) - if connection.supports_migrations? - mtime = ActiveRecord::Migrator.last_migration.mtime.to_i - if @last_check < mtime - ActiveRecord::Migration.check_pending!(connection) - @last_check = mtime - end + mtime = ActiveRecord::Migrator.last_migration.mtime.to_i + if @last_check < mtime + ActiveRecord::Migration.check_pending!(connection) + @last_check = mtime end @app.call(env) end @@ -1058,10 +1056,6 @@ module ActiveRecord Array(@migrations_paths) end - def match_to_migration_filename?(filename) # :nodoc: - Migration::MigrationFilenameRegexp.match?(File.basename(filename)) - end - def parse_migration_filename(filename) # :nodoc: File.basename(filename).scan(Migration::MigrationFilenameRegexp).first end @@ -1069,9 +1063,7 @@ module ActiveRecord def migrations(paths) paths = Array(paths) - files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }] - - migrations = files.map do |file| + migrations = migration_files(paths).map do |file| version, name, scope = parse_migration_filename(file) raise IllegalMigrationNameError.new(file) unless version version = version.to_i @@ -1083,6 +1075,30 @@ module ActiveRecord migrations.sort_by(&:version) end + def migrations_status(paths) + paths = Array(paths) + + db_list = ActiveRecord::SchemaMigration.normalized_versions + + file_list = migration_files(paths).map do |file| + version, name, scope = parse_migration_filename(file) + raise IllegalMigrationNameError.new(file) unless version + version = ActiveRecord::SchemaMigration.normalize_migration_number(version) + status = db_list.delete(version) ? "up" : "down" + [status, version, (name + scope).humanize] + end.compact + + db_list.map! do |version| + ["up", version, "********** NO FILE **********"] + end + + (db_list + file_list).sort_by { |_, version, _| version } + end + + def migration_files(paths) + Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }] + end + private def move(direction, migrations_paths, steps) @@ -1098,8 +1114,6 @@ module ActiveRecord end def initialize(direction, migrations, target_version = nil) - raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations? - @direction = direction @target_version = target_version @migrated_versions = nil diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index ffeca2c91e..85032ce470 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -14,6 +14,13 @@ module ActiveRecord V5_1 = Current class V5_0 < V5_1 + module TableDefinition + def references(*args, **options) + super(*args, type: :integer, **options) + end + alias :belongs_to :references + end + def create_table(table_name, options = {}) if adapter_name == "PostgreSQL" if options[:id] == :uuid && !options.key?(:default) @@ -21,6 +28,12 @@ module ActiveRecord end end + unless adapter_name == "Mysql2" && options[:id] == :bigint + if [:integer, :bigint].include?(options[:id]) && !options.key?(:default) + options[:default] = nil + end + end + # Since 5.1 Postgres adapter uses bigserial type for primary # keys by default and MySQL uses bigint. This compat layer makes old migrations utilize # serial/int type instead -- the way it used to work before 5.1. @@ -28,8 +41,35 @@ module ActiveRecord options[:id] = :integer end - super + if block_given? + super(table_name, options) do |t| + class << t + prepend TableDefinition + end + yield t + end + else + super + end end + + def change_table(table_name, options = {}) + if block_given? + super(table_name, options) do |t| + class << t + prepend TableDefinition + end + yield t + end + else + super + end + end + + def add_reference(table_name, ref_name, **options) + super(table_name, ref_name, type: :integer, **options) + end + alias :add_belongs_to :add_reference end class V4_2 < V5_0 @@ -105,13 +145,13 @@ module ActiveRecord def index_name_for_remove(table_name, options = {}) index_name = index_name(table_name, options) - unless index_name_exists?(table_name, index_name, true) + unless index_name_exists?(table_name, index_name) if options.is_a?(Hash) && options.has_key?(:name) options_without_column = options.dup options_without_column.delete :column index_name_without_column = index_name(table_name, options_without_column) - return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false) + return index_name_without_column if index_name_exists?(table_name, index_name_without_column) end raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 2a28c6bf6d..54216caaaf 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -432,6 +432,7 @@ module ActiveRecord connection.schema_cache.clear_data_source_cache!(table_name) reload_schema_from_cache + initialize_find_by_cache end private diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 246d330b76..1c7206aca4 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -110,28 +110,13 @@ db_namespace = namespace :db do unless ActiveRecord::SchemaMigration.table_exists? abort "Schema migrations table does not exist yet." end - db_list = ActiveRecord::SchemaMigration.normalized_versions - - file_list = - ActiveRecord::Tasks::DatabaseTasks.migrations_paths.flat_map do |path| - Dir.foreach(path).map do |file| - next unless ActiveRecord::Migrator.match_to_migration_filename?(file) - - version, name, scope = ActiveRecord::Migrator.parse_migration_filename(file) - version = ActiveRecord::SchemaMigration.normalize_migration_number(version) - status = db_list.delete(version) ? "up" : "down" - [status, version, (name + scope).humanize] - end.compact - end - db_list.map! do |version| - ["up", version, "********** NO FILE **********"] - end # output puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n" puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name" puts "-" * 50 - (db_list + file_list).sort_by { |_, version, _| version }.each do |status, version, name| + paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths + ActiveRecord::Migrator.migrations_status(paths).each do |status, version, name| puts "#{status.center(8)} #{version.ljust(14)} #{name}" end puts @@ -288,8 +273,7 @@ db_namespace = namespace :db do current_config = ActiveRecord::Tasks::DatabaseTasks.current_config ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) - if ActiveRecord::Base.connection.supports_migrations? && - ActiveRecord::SchemaMigration.table_exists? + if ActiveRecord::SchemaMigration.table_exists? File.open(filename, "a") do |f| f.puts ActiveRecord::Base.connection.dump_schema_information f.print "\n" diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 61a2279292..24ca8b0be4 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -172,8 +172,8 @@ module ActiveRecord JoinKeys = Struct.new(:key, :foreign_key) # :nodoc: - def join_keys(association_klass) - JoinKeys.new(foreign_key, active_record_primary_key) + def join_keys + get_join_keys klass end # Returns a list of scopes that should be applied for this Reflection @@ -187,6 +187,30 @@ module ActiveRecord end deprecate :scope_chain + def join_scopes(table, predicate_builder) # :nodoc: + if scope + [ActiveRecord::Relation.create(klass, table, predicate_builder) + .instance_exec(&scope)] + else + [] + end + end + + def klass_join_scope(table, predicate_builder) # :nodoc: + if klass.current_scope + klass.current_scope.clone.tap { |scope| + scope.joins_values = [] + } + else + relation = ActiveRecord::Relation.create( + klass, + table, + predicate_builder, + ) + klass.send(:build_default_scope, relation) + end + end + def constraints chain.map(&:scopes).flatten end @@ -260,6 +284,20 @@ module ActiveRecord def chain collect_join_chain end + + def get_join_keys(association_klass) + JoinKeys.new(join_pk(association_klass), join_fk) + end + + private + + def join_pk(_) + foreign_key + end + + def join_fk + active_record_primary_key + end end # Base class for AggregateReflection and AssociationReflection. Objects of @@ -687,11 +725,6 @@ module ActiveRecord end end - def join_keys(association_klass) - key = polymorphic? ? association_primary_key(association_klass) : association_primary_key - JoinKeys.new(key, foreign_key) - end - def join_id_for(owner) # :nodoc: owner[foreign_key] end @@ -701,6 +734,14 @@ module ActiveRecord def calculate_constructable(macro, options) !polymorphic? end + + def join_fk + foreign_key + end + + def join_pk(klass) + polymorphic? ? association_primary_key(klass) : association_primary_key + end end class HasAndBelongsToManyReflection < AssociationReflection # :nodoc: @@ -720,7 +761,7 @@ module ActiveRecord class ThroughReflection < AbstractReflection #:nodoc: attr_reader :delegate_reflection delegate :foreign_key, :foreign_type, :association_foreign_key, - :active_record_primary_key, :type, to: :source_reflection + :active_record_primary_key, :type, :get_join_keys, to: :source_reflection def initialize(delegate_reflection) @delegate_reflection = delegate_reflection @@ -806,6 +847,10 @@ module ActiveRecord source_reflection.scopes + super end + def join_scopes(table, predicate_builder) # :nodoc: + source_reflection.join_scopes(table, predicate_builder) + super + end + def source_type_scope through_reflection.klass.where(foreign_type => options[:source_type]) end @@ -816,10 +861,6 @@ module ActiveRecord through_reflection.has_scope? end - def join_keys(association_klass) - source_reflection.join_keys(association_klass) - end - # A through association is nested if there would be more than one join table def nested? source_reflection.through_reflection? || through_reflection.through_reflection? @@ -954,6 +995,7 @@ module ActiveRecord end private + def actual_source_reflection # FIXME: this is a horrible name source_reflection.send(:actual_source_reflection) end @@ -990,6 +1032,15 @@ module ActiveRecord end end + def join_scopes(table, predicate_builder) # :nodoc: + scopes = @previous_reflection.join_scopes(table, predicate_builder) + super + if @previous_reflection.options[:source_type] + scopes + [@previous_reflection.source_type_scope] + else + scopes + end + end + def klass @reflection.klass end @@ -1006,10 +1057,6 @@ module ActiveRecord @reflection.plural_name end - def join_keys(association_klass) - @reflection.join_keys(association_klass) - end - def type @reflection.type end @@ -1023,6 +1070,10 @@ module ActiveRecord source_type = @previous_reflection.options[:source_type] lambda { |object| where(type => source_type) } end + + def get_join_keys(association_klass) + @reflection.get_join_keys(association_klass) + end end class RuntimeReflection < PolymorphicReflection # :nodoc: diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 35c670f1a1..f4cdaf3948 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -232,7 +232,7 @@ module ActiveRecord query_builder = build_count_subquery(spawn, column_name, distinct) else # PostgreSQL doesn't like ORDER BY when there are no GROUP BY - relation = unscope(:order) + relation = unscope(:order).distinct!(false) column = aggregate_column(column_name) @@ -282,7 +282,7 @@ module ActiveRecord operation, distinct).as(aggregate_alias) ] - select_values += select_values unless having_clause.empty? + select_values += self.select_values unless having_clause.empty? select_values.concat group_columns.map { |aliaz, field| if field.respond_to?(:as) @@ -292,7 +292,7 @@ module ActiveRecord end } - relation = except(:group) + relation = except(:group).distinct!(false) relation.group_values = group_fields relation.select_values = select_values diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index f965818ed2..d3ba724507 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -38,6 +38,7 @@ module ActiveRecord delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of, + :to_sentence, :to_formatted_s, :shuffle, :split, :index, to: :records delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 4548944fe6..5d24f5f5ca 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -147,7 +147,7 @@ module ActiveRecord def last(limit = nil) return find_last(limit) if loaded? || limit_value - result = limit(limit || 1) + result = limit(limit) result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key result = result.reverse_order! @@ -430,140 +430,142 @@ module ActiveRecord reflections.none?(&:collection?) end - private + def find_with_ids(*ids) + raise UnknownPrimaryKey.new(@klass) if primary_key.nil? - def find_with_ids(*ids) - raise UnknownPrimaryKey.new(@klass) if primary_key.nil? + expects_array = ids.first.kind_of?(Array) + return ids.first if expects_array && ids.first.empty? - expects_array = ids.first.kind_of?(Array) - return ids.first if expects_array && ids.first.empty? + ids = ids.flatten.compact.uniq - ids = ids.flatten.compact.uniq - - case ids.size - when 0 - raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" - when 1 - result = find_one(ids.first) - expects_array ? [ result ] : result - else - find_some(ids) - end - rescue ::RangeError - raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID" + case ids.size + when 0 + raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" + when 1 + result = find_one(ids.first) + expects_array ? [ result ] : result + else + find_some(ids) end + rescue ::RangeError + raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID" + end - def find_one(id) - if ActiveRecord::Base === id - raise ArgumentError, <<-MSG.squish - You are passing an instance of ActiveRecord::Base to `find`. - Please pass the id of the object by calling `.id`. - MSG - end - - relation = where(primary_key => id) - record = relation.take - - raise_record_not_found_exception!(id, 0, 1) unless record - - record + def find_one(id) + if ActiveRecord::Base === id + raise ArgumentError, <<-MSG.squish + You are passing an instance of ActiveRecord::Base to `find`. + Please pass the id of the object by calling `.id`. + MSG end - def find_some(ids) - return find_some_ordered(ids) unless order_values.present? + relation = where(primary_key => id) + record = relation.take - result = where(primary_key => ids).to_a + raise_record_not_found_exception!(id, 0, 1) unless record - expected_size = - if limit_value && ids.size > limit_value - limit_value - else - ids.size - end + record + end - # 11 ids with limit 3, offset 9 should give 2 results. - if offset_value && (ids.size - offset_value < expected_size) - expected_size = ids.size - offset_value - end + def find_some(ids) + return find_some_ordered(ids) unless order_values.present? - if result.size == expected_size - result + result = where(primary_key => ids).to_a + + expected_size = + if limit_value && ids.size > limit_value + limit_value else - raise_record_not_found_exception!(ids, result.size, expected_size) + ids.size end + + # 11 ids with limit 3, offset 9 should give 2 results. + if offset_value && (ids.size - offset_value < expected_size) + expected_size = ids.size - offset_value end - def find_some_ordered(ids) - ids = ids.slice(offset_value || 0, limit_value || ids.size) || [] + if result.size == expected_size + result + else + raise_record_not_found_exception!(ids, result.size, expected_size) + end + end - result = except(:limit, :offset).where(primary_key => ids).records + def find_some_ordered(ids) + ids = ids.slice(offset_value || 0, limit_value || ids.size) || [] - if result.size == ids.size - pk_type = @klass.type_for_attribute(primary_key) + result = except(:limit, :offset).where(primary_key => ids).records - records_by_id = result.index_by(&:id) - ids.map { |id| records_by_id.fetch(pk_type.cast(id)) } - else - raise_record_not_found_exception!(ids, result.size, ids.size) - end - end + if result.size == ids.size + pk_type = @klass.type_for_attribute(primary_key) - def find_take - if loaded? - records.first - else - @take ||= limit(1).records.first - end + records_by_id = result.index_by(&:id) + ids.map { |id| records_by_id.fetch(pk_type.cast(id)) } + else + raise_record_not_found_exception!(ids, result.size, ids.size) end + end - def find_take_with_limit(limit) - if loaded? - records.take(limit) - else - limit(limit).to_a - end + def find_take + if loaded? + records.first + else + @take ||= limit(1).records.first end + end - def find_nth(index) - @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first + def find_take_with_limit(limit) + if loaded? + records.take(limit) + else + limit(limit).to_a end + end + + def find_nth(index) + @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first + end - def find_nth_with_limit(index, limit) - if loaded? - records[index, limit] || [] + def find_nth_with_limit(index, limit) + if loaded? + records[index, limit] || [] + else + relation = if order_values.empty? && primary_key + order(arel_attribute(primary_key).asc) else - relation = if order_values.empty? && primary_key - order(arel_attribute(primary_key).asc) - else - self - end + self + end + if limit_value.nil? || index < limit_value relation = relation.offset(offset_index + index) unless index.zero? relation.limit(limit).to_a + else + [] end end + end - def find_nth_from_last(index) - if loaded? - records[-index] + def find_nth_from_last(index) + if loaded? + records[-index] + else + relation = if order_values.empty? && primary_key + order(arel_attribute(primary_key).asc) else - relation = if order_values.empty? && primary_key - order(arel_attribute(primary_key).asc) - else - self - end - - relation.to_a[-index] - # TODO: can be made more performant on large result sets by - # for instance, last(index)[-index] (which would require - # refactoring the last(n) finder method to make test suite pass), - # or by using a combination of reverse_order, limit, and offset, - # e.g., reverse_order.offset(index-1).first + self end - end - def find_last(limit) - limit ? records.last(limit) : records.last + relation.to_a[-index] + # TODO: can be made more performant on large result sets by + # for instance, last(index)[-index] (which would require + # refactoring the last(n) finder method to make test suite pass), + # or by using a combination of reverse_order, limit, and offset, + # e.g., reverse_order.offset(index-1).first end + end + + def find_last(limit) + limit ? records.last(limit) : records.last + end end end diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index ef0d059d1c..417b24c7bb 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -25,10 +25,7 @@ module ActiveRecord end def except(*columns) - WhereClause.new( - predicates_except(columns), - binds_except(columns), - ) + WhereClause.new(*except_predicates_and_binds(columns)) end def or(other) @@ -134,20 +131,35 @@ module ActiveRecord end end - def predicates_except(columns) - predicates.reject do |node| - case node - when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual - subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) - columns.include?(subrelation.name.to_s) + def except_predicates_and_binds(columns) + except_binds = [] + binds_index = 0 + + predicates = self.predicates.reject do |node| + except = \ + case node + when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual + binds_contains = node.grep(Arel::Nodes::BindParam).size + subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) + columns.include?(subrelation.name.to_s) + end + + if except && binds_contains > 0 + (binds_index...(binds_index + binds_contains)).each do |i| + except_binds[i] = true + end + + binds_index += binds_contains end + + except end - end - def binds_except(columns) - binds.reject do |attr| - columns.include?(attr.name) + binds = self.binds.reject.with_index do |_, i| + except_binds[i] end + + [predicates, binds] end def predicates_with_wrapped_sql_literals diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index 737bc278bd..04bee73e8f 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -15,9 +15,12 @@ module ActiveRecord attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes) attributes.stringify_keys! - attributes, binds = predicate_builder.create_binds(attributes) - - parts = predicate_builder.build_from_hash(attributes) + if perform_case_sensitive?(options = other.last) + parts, binds = build_for_case_sensitive(attributes, options) + else + attributes, binds = predicate_builder.create_binds(attributes) + parts = predicate_builder.build_from_hash(attributes) + end when Arel::Nodes::Node parts = [opts] else @@ -32,6 +35,43 @@ module ActiveRecord protected attr_reader :klass, :predicate_builder + + private + + def perform_case_sensitive?(options) + options && options.key?(:case_sensitive) + end + + def build_for_case_sensitive(attributes, options) + parts, binds = [], [] + table = klass.arel_table + + attributes.each do |attribute, value| + if reflection = klass._reflect_on_association(attribute) + attribute = reflection.foreign_key.to_s + value = value[reflection.klass.primary_key] unless value.nil? + end + + if value.nil? + parts << table[attribute].eq(value) + else + column = klass.column_for_attribute(attribute) + + binds << predicate_builder.send(:build_bind_param, attribute, value) + value = Arel::Nodes::BindParam.new + + predicate = if options[:case_sensitive] + klass.connection.case_sensitive_comparison(table, attribute, column, value) + else + klass.connection.case_insensitive_comparison(table, attribute, column, value) + end + + parts << predicate + end + end + + [parts, binds] + end end end end diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 9ed70a9c2b..26b1d48e9e 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -41,10 +41,15 @@ module ActiveRecord @column_types = column_types end + # Returns the number of elements in the rows array. def length @rows.length end + # Calls the given block once for each element in row collection, passing + # row as parameter. + # + # Returns an +Enumerator+ if no block is given. def each if block_given? hash_rows.each { |row| yield row } @@ -53,6 +58,7 @@ module ActiveRecord end end + # Returns an array of hashes representing each row record. def to_hash hash_rows end @@ -60,11 +66,12 @@ module ActiveRecord alias :map! :map alias :collect! :map - # Returns true if there are no records. + # Returns true if there are no records, otherwise false. def empty? rows.empty? end + # Returns an array of hashes representing each row record. def to_ary hash_rows end @@ -73,11 +80,15 @@ module ActiveRecord hash_rows[idx] end + # Returns the first record from the rows collection. + # If the rows collection is empty, returns +nil+. def first return nil if @rows.empty? Hash[@columns.zip(@rows.first)] end + # Returns the last record from the rows collection. + # If the rows collection is empty, returns +nil+. def last return nil if @rows.empty? Hash[@columns.zip(@rows.last)] diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 427c0019c6..64bda1539c 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -1,4 +1,3 @@ - module ActiveRecord module Sanitization extend ActiveSupport::Concern @@ -207,9 +206,9 @@ module ActiveRecord end end - # TODO: Deprecate this def quoted_id # :nodoc: self.class.connection.quote(@attributes[self.class.primary_key].value_for_database) end + deprecate :quoted_id end end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 12289511b7..2bbfd01698 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -85,7 +85,7 @@ HEADER end def tables(stream) - sorted_tables = @connection.data_sources.sort - @connection.views + sorted_tables = @connection.tables.sort sorted_tables.each do |table_name| table(table_name, stream) unless ignored?(table_name) @@ -188,7 +188,7 @@ HEADER index_parts << "length: { #{format_options(index.lengths)} }" if index.lengths.present? index_parts << "order: { #{format_options(index.orders)} }" if index.orders.present? index_parts << "where: #{index.where.inspect}" if index.where - index_parts << "using: #{index.using.inspect}" if index.using + index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index) index_parts << "type: #{index.type.inspect}" if index.type index_parts << "comment: #{index.comment.inspect}" if index.comment index_parts diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index 5a408e7b8e..db2bd0b55e 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -9,7 +9,7 @@ module ActiveRecord #:nodoc: end def serializable_hash(options = nil) - options = options.try(:clone) || {} + options = options.try(:dup) || {} options[:except] = Array(options[:except]).map(&:to_s) options[:except] |= Array(self.class.inheritance_column) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 56b75540e3..690deee508 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -11,7 +11,6 @@ module ActiveRecord :before_commit_without_transaction_enrollment, :commit_without_transaction_enrollment, :rollback_without_transaction_enrollment, - terminator: deprecated_false_terminator, scope: [:kind, :name] end @@ -284,7 +283,7 @@ module ActiveRecord fire_on = Array(options[:on]) assert_valid_transaction_action(fire_on) options[:if] = Array(options[:if]) - options[:if] << "transaction_include_any_action?(#{fire_on})" + options[:if].unshift("transaction_include_any_action?(#{fire_on})") end end diff --git a/activerecord/lib/active_record/type/decimal_without_scale.rb b/activerecord/lib/active_record/type/decimal_without_scale.rb index 7ce33e9cd3..53a5e205da 100644 --- a/activerecord/lib/active_record/type/decimal_without_scale.rb +++ b/activerecord/lib/active_record/type/decimal_without_scale.rb @@ -4,6 +4,10 @@ module ActiveRecord def type :decimal end + + def type_cast_for_schema(value) + value.to_s.inspect + end end end end diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index ac9134bfcb..6af05c1860 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -43,7 +43,7 @@ module ActiveRecord def assert_valid_value(value) if coder.respond_to?(:assert_valid_value) - coder.assert_valid_value(value) + coder.assert_valid_value(value, action: "serialize") end end diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb index ca5eda2f84..7cfd55f516 100644 --- a/activerecord/lib/active_record/validations/presence.rb +++ b/activerecord/lib/active_record/validations/presence.rb @@ -57,7 +57,7 @@ module ActiveRecord # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The method, # proc or string should return or evaluate to a +true+ or +false+ value. # * <tt>:strict</tt> - Specifies whether validation should be strict. - # See ActiveModel::Validation#validates! for more information. + # See ActiveModel::Validations#validates! for more information. def validates_presence_of(*attr_names) validates_with PresenceValidator, _merge_attributes(attr_names) end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 9e8edfbfaf..154cf5f1a4 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -50,37 +50,7 @@ module ActiveRecord end def build_relation(klass, attribute, value) - if reflection = klass._reflect_on_association(attribute) - attribute = reflection.foreign_key - value = value.attributes[reflection.klass.primary_key] unless value.nil? - end - - if value.nil? - return klass.unscoped.where!(attribute => value) - end - - # the attribute may be an aliased attribute - if klass.attribute_alias?(attribute) - attribute = klass.attribute_alias(attribute) - end - - attribute_name = attribute.to_s - - table = klass.arel_table - column = klass.columns_hash[attribute_name] - cast_type = klass.type_for_attribute(attribute_name) - - comparison = if !options[:case_sensitive] - # will use SQL LOWER function before comparison, unless it detects a case insensitive collation - klass.connection.case_insensitive_comparison(table, attribute, column, value) - else - klass.connection.case_sensitive_comparison(table, attribute, column, value) - end - klass.unscoped.tap do |scope| - parts = [comparison] - binds = [Relation::QueryAttribute.new(attribute_name, value, cast_type)] - scope.where_clause += Relation::WhereClause.new(parts, binds) - end + klass.unscoped.where!({ attribute => value }, options) end def scope_relation(record, relation) diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index a2b7d53205..070fca240f 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -30,6 +30,16 @@ module ActiveRecord assert_nothing_raised { Book.destroy(0) } end + def test_valid_column + @connection.native_database_types.each_key do |type| + assert @connection.valid_type?(type) + end + end + + def test_invalid_column + assert_not @connection.valid_type?(:foobar) + end + def test_tables tables = @connection.tables assert_includes tables, "accounts" @@ -194,7 +204,7 @@ module ActiveRecord def test_numeric_value_out_of_ranges_are_translated_to_specific_exception error = assert_raises(ActiveRecord::RangeError) do - Book.connection.create("INSERT INTO books(author_id) VALUES (2147483648)") + Book.connection.create("INSERT INTO books(author_id) VALUES (9223372036854775808)") end assert_not_nil error.cause diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 2a528b2cb1..67e1efde27 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -92,8 +92,8 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase assert_equal expected, actual end - expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY" - actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t| + expected = "ALTER TABLE `people` ADD INDEX `index_people_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY" + actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t| t.index :last_name, length: 10, using: :btree, algorithm: :copy end assert_equal expected, actual diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index bae283a048..a2faf43b0d 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -42,7 +42,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase @connection.update("set @@wait_timeout=1") sleep 2 assert !@connection.active? - + ensure # Repair all fixture connections so other tests won't break. @fixture_connections.each(&:verify!) end @@ -63,6 +63,18 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase assert @connection.active? end + def test_verify_with_args_is_deprecated + assert_deprecated do + @connection.verify!(option: true) + end + assert_deprecated do + @connection.verify!([]) + end + assert_deprecated do + @connection.verify!({}) + end + end + def test_execute_after_disconnect @connection.disconnect! @@ -85,6 +97,22 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase assert_equal false, @connection.active? end + def test_wait_timeout_as_string + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.merge(wait_timeout: "60")) + result = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.wait_timeout") + assert_equal 60, result + end + end + + def test_wait_timeout_as_url + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.merge("url" => "mysql2:///?wait_timeout=60")) + result = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.wait_timeout") + assert_equal 60, result + end + end + def test_mysql_connection_collation_is_configured assert_equal "utf8_unicode_ci", @connection.show_variable("collation_connection") assert_equal "utf8_general_ci", ARUnit2Model.connection.show_variable("collation_connection") @@ -120,7 +148,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end end - def test_passing_arbitary_flags_to_adapter + def test_passing_arbitrary_flags_to_adapter run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.merge(flags: Mysql2::Client::COMPRESS)) assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags] diff --git a/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb b/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb deleted file mode 100644 index 5d3125c2be..0000000000 --- a/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb +++ /dev/null @@ -1,60 +0,0 @@ -require "cases/helper" - -class MysqlLegacyMigrationTest < ActiveRecord::Mysql2TestCase - self.use_transactional_tests = false - - class GenerateTableWithoutBigint < ActiveRecord::Migration[5.0] - def change - create_table :legacy_integer_pk do |table| - table.string :foo - end - - create_table :override_pk, id: :bigint do |table| - table.string :bar - end - end - end - - def setup - super - @connection = ActiveRecord::Base.connection - - @migration_verbose_old = ActiveRecord::Migration.verbose - ActiveRecord::Migration.verbose = false - - migrations = [GenerateTableWithoutBigint.new(nil, 1)] - - ActiveRecord::Migrator.new(:up, migrations).migrate - end - - def teardown - ActiveRecord::Migration.verbose = @migration_verbose_old - @connection.drop_table("legacy_integer_pk") - @connection.drop_table("override_pk") - ActiveRecord::SchemaMigration.delete_all rescue nil - super - end - - def test_create_table_uses_integer_as_pkey_by_default - col = column(:legacy_integer_pk, :id) - assert_equal "int(11)", sql_type_for(col) - assert col.auto_increment? - end - - def test_create_tables_respects_pk_column_type_override - col = column(:override_pk, :id) - assert_equal "bigint(20)", sql_type_for(col) - end - - private - - def column(table_name, column_name) - ActiveRecord::Base.connection - .columns(table_name.to_s) - .detect { |c| c.name == column_name.to_s } - end - - def sql_type_for(col) - col && col.sql_type - end -end diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb index aab3dcb724..565130c38f 100644 --- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb @@ -17,17 +17,6 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase end end - def test_valid_column - with_example_table do - column = @conn.columns("ex").find { |col| col.name == "id" } - assert @conn.valid_type?(column.type) - end - end - - def test_invalid_column - assert_not @conn.valid_type?(:foobar) - end - def test_columns_for_distinct_zero_orders assert_equal "posts.id", @conn.columns_for_distinct("posts.id", []) diff --git a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb index bee42d48f1..d6e7f29a5c 100644 --- a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb +++ b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb @@ -8,7 +8,7 @@ class Mysql2SqlTypesTest < ActiveRecord::Mysql2TestCase assert_equal "blob", type_to_sql(:binary) end - def type_to_sql(*args) - ActiveRecord::Base.connection.type_to_sql(*args) + def type_to_sql(type, limit = nil) + ActiveRecord::Base.connection.type_to_sql(type, limit: limit) end end diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index 5c207116c4..505c297cd4 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -32,9 +32,9 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase end def test_binary_columns_are_limitless_the_upper_limit_is_one_GB - assert_equal "bytea", @connection.type_to_sql(:binary, 100_000) + assert_equal "bytea", @connection.type_to_sql(:binary, limit: 100_000) assert_raise ActiveRecord::ActiveRecordError do - @connection.type_to_sql :binary, 4294967295 + @connection.type_to_sql(:binary, limit: 4294967295) end end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 3cbd4ca212..c52d9e37cc 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -105,7 +105,7 @@ module ActiveRecord end def test_table_alias_length_logs_name - @connection.instance_variable_set("@table_alias_length", nil) + @connection.instance_variable_set("@max_identifier_length", nil) @connection.table_alias_length assert_equal "SCHEMA", @subscriber.logged[0][1] end @@ -177,7 +177,7 @@ module ActiveRecord assert_not_equal original_connection_pid, new_connection_pid, "umm -- looks like you didn't break the connection, because we're still " \ "successfully querying with the same connection pid." - + ensure # Repair all fixture connections so other tests won't break. @fixture_connections.each(&:verify!) end diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 0ac8b7339b..0725fde5ae 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -28,12 +28,12 @@ class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase end def test_data_type_of_time_types - assert_equal :string, @first_time.column_for_attribute(:time_interval).type - assert_equal :string, @first_time.column_for_attribute(:scaled_time_interval).type + assert_equal :interval, @first_time.column_for_attribute(:time_interval).type + assert_equal :interval, @first_time.column_for_attribute(:scaled_time_interval).type end def test_data_type_of_oid_types - assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type + assert_equal :oid, @first_oid.column_for_attribute(:obj_id).type end def test_time_values @@ -61,9 +61,9 @@ class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase end def test_text_columns_are_limitless_the_upper_limit_is_one_GB - assert_equal "text", @connection.type_to_sql(:text, 100_000) + assert_equal "text", @connection.type_to_sql(:text, limit: 100_000) assert_raise ActiveRecord::ActiveRecordError do - @connection.type_to_sql :text, 4294967295 + @connection.type_to_sql(:text, limit: 4294967295) end end end diff --git a/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb b/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb deleted file mode 100644 index 082fe95053..0000000000 --- a/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb +++ /dev/null @@ -1,54 +0,0 @@ -require "cases/helper" - -class PostgresqlLegacyMigrationTest < ActiveRecord::PostgreSQLTestCase - class GenerateTableWithoutBigserial < ActiveRecord::Migration[5.0] - def change - create_table :legacy_integer_pk do |table| - table.string :foo - end - - create_table :override_pk, id: :bigint do |table| - table.string :bar - end - end - end - - def setup - super - - @migration_verbose_old = ActiveRecord::Migration.verbose - ActiveRecord::Migration.verbose = false - - migrations = [GenerateTableWithoutBigserial.new(nil, 1)] - ActiveRecord::Migrator.new(:up, migrations).migrate - end - - def teardown - ActiveRecord::Migration.verbose = @migration_verbose_old - - super - end - - def test_create_table_uses_serial_as_pkey_by_default - col = column(:legacy_integer_pk, :id) - assert_equal "integer", sql_type_for(col) - assert col.serial? - end - - def test_create_tables_respects_pk_column_type_override - col = column(:override_pk, :id) - assert_equal "bigint", sql_type_for(col) - end - - private - - def column(table_name, column_name) - ActiveRecord::Base.connection. - columns(table_name.to_s). - detect { |c| c.name == column_name.to_s } - end - - def sql_type_for(col) - col && col.sql_type - end -end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index e6af93a53e..003e6e62e7 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -21,17 +21,6 @@ module ActiveRecord end end - def test_valid_column - with_example_table do - column = @connection.columns("ex").find { |col| col.name == "id" } - assert @connection.valid_type?(column.type) - end - end - - def test_invalid_column - assert_not @connection.valid_type?(:foobar) - end - def test_primary_key with_example_table do assert_equal "id", @connection.primary_key("ex") @@ -54,12 +43,6 @@ module ActiveRecord end end - def test_primary_key_raises_error_if_table_not_found - assert_raises(ActiveRecord::StatementInvalid) do - @connection.primary_key("unobtainium") - end - end - def test_exec_insert_with_returning_disabled connection = connection_without_insert_returning result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], "id", "postgresql_partitioned_table_parent_id_seq") @@ -263,9 +246,12 @@ module ActiveRecord def test_index_with_opclass with_example_table do - @connection.add_index "ex", "data varchar_pattern_ops", name: "with_opclass" - index = @connection.indexes("ex").find { |idx| idx.name == "with_opclass" } + @connection.add_index "ex", "data varchar_pattern_ops" + index = @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" } assert_equal "data varchar_pattern_ops", index.columns + + @connection.remove_index "ex", "data varchar_pattern_ops" + assert_not @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" } end end diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index 141baffa5b..a1e966b915 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require "ipaddr" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 237e9ff6a5..7b065ff320 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -301,13 +301,13 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase def test_index_name_exists with_schema_search_path(SCHEMA_NAME) do - assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME, true) - assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME, true) - assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME, true) - assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME, true) - assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true) - assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true) - assert_not @connection.index_name_exists?(TABLE_NAME, "missing_index", true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME) + assert_not @connection.index_name_exists?(TABLE_NAME, "missing_index") end end @@ -366,14 +366,6 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase end end - def test_primary_key_raises_error_if_table_not_found_on_schema_search_path - with_schema_search_path(SCHEMA2_NAME) do - assert_raises(ActiveRecord::StatementInvalid) do - @connection.primary_key(PK_TABLE_NAME) - end - end - end - def test_pk_and_sequence_for_with_schema_specified pg_name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name [ diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 4655cd1d20..6aa6a79705 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -347,7 +347,6 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase assert_raise ActiveRecord::RecordNotFound do UuidPost.find(123456) end - end def test_find_by_with_uuid diff --git a/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb b/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb deleted file mode 100644 index fcca8d66b5..0000000000 --- a/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb +++ /dev/null @@ -1,59 +0,0 @@ -require "cases/helper" - -class SqliteLegacyMigrationTest < ActiveRecord::SQLite3TestCase - self.use_transactional_tests = false - - class GenerateTableWithoutBigint < ActiveRecord::Migration[5.0] - def change - create_table :legacy_integer_pk do |table| - table.string :foo - end - - create_table :override_pk, id: :bigint do |table| - table.string :bar - end - end - end - - def setup - super - @connection = ActiveRecord::Base.connection - - @migration_verbose_old = ActiveRecord::Migration.verbose - ActiveRecord::Migration.verbose = false - - migrations = [GenerateTableWithoutBigint.new(nil, 1)] - - ActiveRecord::Migrator.new(:up, migrations).migrate - end - - def teardown - ActiveRecord::Migration.verbose = @migration_verbose_old - @connection.drop_table("legacy_integer_pk") - @connection.drop_table("override_pk") - ActiveRecord::SchemaMigration.delete_all rescue nil - super - end - - def test_create_table_uses_integer_as_pkey_by_default - col = column(:legacy_integer_pk, :id) - assert_equal "INTEGER", sql_type_for(col) - assert primary_key?(:legacy_integer_pk, "id"), "id is not primary key" - end - - private - - def column(table_name, column_name) - ActiveRecord::Base.connection - .columns(table_name.to_s) - .detect { |c| c.name == column_name.to_s } - end - - def sql_type_for(col) - col && col.sql_type - end - - def primary_key?(table_name, column) - ActiveRecord::Base.connection.execute("PRAGMA table_info(#{table_name})").find { |col| col["name"] == column }["pk"] == 1 - end -end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 9750840051..aefbb309e6 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -1,6 +1,5 @@ require "cases/helper" require "bigdecimal" -require "yaml" require "securerandom" class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase @@ -15,31 +14,6 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase assert_equal expected, @conn.type_cast(binary) end - def test_type_cast_symbol - assert_equal "foo", @conn.type_cast(:foo) - end - - def test_type_cast_date - date = Date.today - expected = @conn.quoted_date(date) - assert_equal expected, @conn.type_cast(date) - end - - def test_type_cast_time - time = Time.now - expected = @conn.quoted_date(time) - assert_equal expected, @conn.type_cast(time) - end - - def test_type_cast_numeric - assert_equal 10, @conn.type_cast(10) - assert_equal 2.2, @conn.type_cast(2.2) - end - - def test_type_cast_nil - assert_nil @conn.type_cast(nil) - end - def test_type_cast_true assert_equal "t", @conn.type_cast(true) end @@ -53,31 +27,6 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase assert_equal bd.to_f, @conn.type_cast(bd) end - def test_type_cast_unknown_should_raise_error - obj = Class.new.new - assert_raise(TypeError) { @conn.type_cast(obj) } - end - - def test_type_cast_object_which_responds_to_quoted_id - quoted_id_obj = Class.new { - def quoted_id - "'zomg'" - end - - def id - 10 - end - }.new - assert_equal 10, @conn.type_cast(quoted_id_obj) - - quoted_id_obj = Class.new { - def quoted_id - "'zomg'" - end - }.new - assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) } - end - def test_quoting_binary_strings value = "hello".encode("ascii-8bit") type = ActiveRecord::Type::String.new diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index a6afb7816b..fdd7b0157a 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -49,22 +49,6 @@ module ActiveRecord end end - def test_valid_column - with_example_table do - column = @conn.columns("ex").find { |col| col.name == "id" } - assert @conn.valid_type?(column.type) - end - end - - # sqlite3 databases should be able to support any type and not just the - # ones mentioned in the native_database_types. - # - # Therefore test_invalid column should always return true even if the - # type is not valid. - def test_invalid_column - assert @conn.valid_type?(:foobar) - end - def test_column_types owner = Owner.create!(name: "hello".encode("ascii-8bit")) owner.reload diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb index aebcce3691..37ff973397 100644 --- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb @@ -3,7 +3,6 @@ require "cases/helper" class SQLite3StatementPoolTest < ActiveRecord::SQLite3TestCase if Process.respond_to?(:fork) def test_cache_is_per_pid - cache = ActiveRecord::ConnectionAdapters::SQLite3Adapter::StatementPool.new(10) cache["foo"] = "bar" assert_equal "bar", cache["foo"] diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 397ac599b9..5b608d8e83 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -1,146 +1,143 @@ require "cases/helper" -if ActiveRecord::Base.connection.supports_migrations? +class ActiveRecordSchemaTest < ActiveRecord::TestCase + self.use_transactional_tests = false + + setup do + @original_verbose = ActiveRecord::Migration.verbose + ActiveRecord::Migration.verbose = false + @connection = ActiveRecord::Base.connection + ActiveRecord::SchemaMigration.drop_table + end - class ActiveRecordSchemaTest < ActiveRecord::TestCase - self.use_transactional_tests = false + teardown do + @connection.drop_table :fruits rescue nil + @connection.drop_table :nep_fruits rescue nil + @connection.drop_table :nep_schema_migrations rescue nil + @connection.drop_table :has_timestamps rescue nil + @connection.drop_table :multiple_indexes rescue nil + ActiveRecord::SchemaMigration.delete_all rescue nil + ActiveRecord::Migration.verbose = @original_verbose + end - setup do - @original_verbose = ActiveRecord::Migration.verbose - ActiveRecord::Migration.verbose = false - @connection = ActiveRecord::Base.connection - ActiveRecord::SchemaMigration.drop_table - end + def test_has_primary_key + old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type + ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore + assert_equal "version", ActiveRecord::SchemaMigration.primary_key - teardown do - @connection.drop_table :fruits rescue nil - @connection.drop_table :nep_fruits rescue nil - @connection.drop_table :nep_schema_migrations rescue nil - @connection.drop_table :has_timestamps rescue nil - @connection.drop_table :multiple_indexes rescue nil - ActiveRecord::SchemaMigration.delete_all rescue nil - ActiveRecord::Migration.verbose = @original_verbose + ActiveRecord::SchemaMigration.create_table + assert_difference "ActiveRecord::SchemaMigration.count", 1 do + ActiveRecord::SchemaMigration.create version: 12 end + ensure + ActiveRecord::SchemaMigration.drop_table + ActiveRecord::Base.primary_key_prefix_type = old_primary_key_prefix_type + end - def test_has_primary_key - old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type - ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore - assert_equal "version", ActiveRecord::SchemaMigration.primary_key - - ActiveRecord::SchemaMigration.create_table - assert_difference "ActiveRecord::SchemaMigration.count", 1 do - ActiveRecord::SchemaMigration.create version: 12 + def test_schema_define + ActiveRecord::Schema.define(version: 7) do + create_table :fruits do |t| + t.column :color, :string + t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle + t.column :texture, :string + t.column :flavor, :string end - ensure - ActiveRecord::SchemaMigration.drop_table - ActiveRecord::Base.primary_key_prefix_type = old_primary_key_prefix_type end - def test_schema_define - ActiveRecord::Schema.define(version: 7) do - create_table :fruits do |t| - t.column :color, :string - t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle - t.column :texture, :string - t.column :flavor, :string - end - end - - assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } - assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" } - assert_equal 7, ActiveRecord::Migrator::current_version - end + assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } + assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" } + assert_equal 7, ActiveRecord::Migrator::current_version + end - def test_schema_define_w_table_name_prefix - table_name = ActiveRecord::SchemaMigration.table_name - old_table_name_prefix = ActiveRecord::Base.table_name_prefix - ActiveRecord::Base.table_name_prefix = "nep_" - ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}" - ActiveRecord::Schema.define(version: 7) do - create_table :fruits do |t| - t.column :color, :string - t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle - t.column :texture, :string - t.column :flavor, :string - end + def test_schema_define_w_table_name_prefix + table_name = ActiveRecord::SchemaMigration.table_name + old_table_name_prefix = ActiveRecord::Base.table_name_prefix + ActiveRecord::Base.table_name_prefix = "nep_" + ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}" + ActiveRecord::Schema.define(version: 7) do + create_table :fruits do |t| + t.column :color, :string + t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle + t.column :texture, :string + t.column :flavor, :string end - assert_equal 7, ActiveRecord::Migrator::current_version - ensure - ActiveRecord::Base.table_name_prefix = old_table_name_prefix - ActiveRecord::SchemaMigration.table_name = table_name end + assert_equal 7, ActiveRecord::Migrator::current_version + ensure + ActiveRecord::Base.table_name_prefix = old_table_name_prefix + ActiveRecord::SchemaMigration.table_name = table_name + end - def test_schema_raises_an_error_for_invalid_column_type - assert_raise NoMethodError do - ActiveRecord::Schema.define(version: 8) do - create_table :vegetables do |t| - t.unknown :color - end + def test_schema_raises_an_error_for_invalid_column_type + assert_raise NoMethodError do + ActiveRecord::Schema.define(version: 8) do + create_table :vegetables do |t| + t.unknown :color end end end + end - def test_schema_subclass - Class.new(ActiveRecord::Schema).define(version: 9) do - create_table :fruits - end - assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } + def test_schema_subclass + Class.new(ActiveRecord::Schema).define(version: 9) do + create_table :fruits end + assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } + end - def test_normalize_version - assert_equal "118", ActiveRecord::SchemaMigration.normalize_migration_number("0000118") - assert_equal "002", ActiveRecord::SchemaMigration.normalize_migration_number("2") - assert_equal "017", ActiveRecord::SchemaMigration.normalize_migration_number("0017") - assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947") - end + def test_normalize_version + assert_equal "118", ActiveRecord::SchemaMigration.normalize_migration_number("0000118") + assert_equal "002", ActiveRecord::SchemaMigration.normalize_migration_number("2") + assert_equal "017", ActiveRecord::SchemaMigration.normalize_migration_number("0017") + assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947") + end - def test_schema_load_with_multiple_indexes_for_column_of_different_names - ActiveRecord::Schema.define do - create_table :multiple_indexes do |t| - t.string "foo" - t.index ["foo"], name: "multiple_indexes_foo_1" - t.index ["foo"], name: "multiple_indexes_foo_2" - end + def test_schema_load_with_multiple_indexes_for_column_of_different_names + ActiveRecord::Schema.define do + create_table :multiple_indexes do |t| + t.string "foo" + t.index ["foo"], name: "multiple_indexes_foo_1" + t.index ["foo"], name: "multiple_indexes_foo_2" end + end - indexes = @connection.indexes("multiple_indexes") + indexes = @connection.indexes("multiple_indexes") - assert_equal 2, indexes.length - assert_equal ["multiple_indexes_foo_1", "multiple_indexes_foo_2"], indexes.collect(&:name).sort - end + assert_equal 2, indexes.length + assert_equal ["multiple_indexes_foo_1", "multiple_indexes_foo_2"], indexes.collect(&:name).sort + end - def test_timestamps_without_null_set_null_to_false_on_create_table - ActiveRecord::Schema.define do - create_table :has_timestamps do |t| - t.timestamps - end + def test_timestamps_without_null_set_null_to_false_on_create_table + ActiveRecord::Schema.define do + create_table :has_timestamps do |t| + t.timestamps end - - assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null - assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null end - def test_timestamps_without_null_set_null_to_false_on_change_table - ActiveRecord::Schema.define do - create_table :has_timestamps + assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null + end - change_table :has_timestamps do |t| - t.timestamps default: Time.now - end - end + def test_timestamps_without_null_set_null_to_false_on_change_table + ActiveRecord::Schema.define do + create_table :has_timestamps - assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null - assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null + change_table :has_timestamps do |t| + t.timestamps default: Time.now + end end - def test_timestamps_without_null_set_null_to_false_on_add_timestamps - ActiveRecord::Schema.define do - create_table :has_timestamps - add_timestamps :has_timestamps, default: Time.now - end + assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null + end - assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null - assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null + def test_timestamps_without_null_set_null_to_false_on_add_timestamps + ActiveRecord::Schema.define do + create_table :has_timestamps + add_timestamps :has_timestamps, default: Time.now end + + assert !@connection.columns(:has_timestamps).find { |c| c.name == "created_at" }.null + assert !@connection.columns(:has_timestamps).find { |c| c.name == "updated_at" }.null end end diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb index 5d1c1c4b9b..16eff15026 100644 --- a/activerecord/test/cases/associations/eager_singularization_test.rb +++ b/activerecord/test/cases/associations/eager_singularization_test.rb @@ -1,147 +1,146 @@ require "cases/helper" -if ActiveRecord::Base.connection.supports_migrations? - class EagerSingularizationTest < ActiveRecord::TestCase - class Virus < ActiveRecord::Base - belongs_to :octopus - end - - class Octopus < ActiveRecord::Base - has_one :virus - end - - class Pass < ActiveRecord::Base - belongs_to :bus - end - - class Bus < ActiveRecord::Base - has_many :passes - end - - class Mess < ActiveRecord::Base - has_and_belongs_to_many :crises - end - - class Crisis < ActiveRecord::Base - has_and_belongs_to_many :messes - has_many :analyses, dependent: :destroy - has_many :successes, through: :analyses - has_many :dresses, dependent: :destroy - has_many :compresses, through: :dresses - end - - class Analysis < ActiveRecord::Base - belongs_to :crisis - belongs_to :success - end - - class Success < ActiveRecord::Base - has_many :analyses, dependent: :destroy - has_many :crises, through: :analyses - end - - class Dress < ActiveRecord::Base - belongs_to :crisis - has_many :compresses - end - - class Compress < ActiveRecord::Base - belongs_to :dress - end - - def setup - connection.create_table :viri do |t| - t.column :octopus_id, :integer - t.column :species, :string - end - connection.create_table :octopi do |t| - t.column :species, :string - end - connection.create_table :passes do |t| - t.column :bus_id, :integer - t.column :rides, :integer - end - connection.create_table :buses do |t| - t.column :name, :string - end - connection.create_table :crises_messes, id: false do |t| - t.column :crisis_id, :integer - t.column :mess_id, :integer - end - connection.create_table :messes do |t| - t.column :name, :string - end - connection.create_table :crises do |t| - t.column :name, :string - end - connection.create_table :successes do |t| - t.column :name, :string - end - connection.create_table :analyses do |t| - t.column :crisis_id, :integer - t.column :success_id, :integer - end - connection.create_table :dresses do |t| - t.column :crisis_id, :integer - end - connection.create_table :compresses do |t| - t.column :dress_id, :integer - end - end - - teardown do - connection.drop_table :viri - connection.drop_table :octopi - connection.drop_table :passes - connection.drop_table :buses - connection.drop_table :crises_messes - connection.drop_table :messes - connection.drop_table :crises - connection.drop_table :successes - connection.drop_table :analyses - connection.drop_table :dresses - connection.drop_table :compresses - end +class EagerSingularizationTest < ActiveRecord::TestCase + class Virus < ActiveRecord::Base + belongs_to :octopus + end - def connection - ActiveRecord::Base.connection + class Octopus < ActiveRecord::Base + has_one :virus + end + + class Pass < ActiveRecord::Base + belongs_to :bus + end + + class Bus < ActiveRecord::Base + has_many :passes + end + + class Mess < ActiveRecord::Base + has_and_belongs_to_many :crises + end + + class Crisis < ActiveRecord::Base + has_and_belongs_to_many :messes + has_many :analyses, dependent: :destroy + has_many :successes, through: :analyses + has_many :dresses, dependent: :destroy + has_many :compresses, through: :dresses + end + + class Analysis < ActiveRecord::Base + belongs_to :crisis + belongs_to :success + end + + class Success < ActiveRecord::Base + has_many :analyses, dependent: :destroy + has_many :crises, through: :analyses + end + + class Dress < ActiveRecord::Base + belongs_to :crisis + has_many :compresses + end + + class Compress < ActiveRecord::Base + belongs_to :dress + end + + def setup + connection.create_table :viri do |t| + t.column :octopus_id, :integer + t.column :species, :string end + connection.create_table :octopi do |t| + t.column :species, :string + end + connection.create_table :passes do |t| + t.column :bus_id, :integer + t.column :rides, :integer + end + connection.create_table :buses do |t| + t.column :name, :string + end + connection.create_table :crises_messes, id: false do |t| + t.column :crisis_id, :integer + t.column :mess_id, :integer + end + connection.create_table :messes do |t| + t.column :name, :string + end + connection.create_table :crises do |t| + t.column :name, :string + end + connection.create_table :successes do |t| + t.column :name, :string + end + connection.create_table :analyses do |t| + t.column :crisis_id, :integer + t.column :success_id, :integer + end + connection.create_table :dresses do |t| + t.column :crisis_id, :integer + end + connection.create_table :compresses do |t| + t.column :dress_id, :integer + end + end - def test_eager_no_extra_singularization_belongs_to - assert_nothing_raised do - Virus.all.merge!(includes: :octopus).to_a - end + teardown do + connection.drop_table :viri + connection.drop_table :octopi + connection.drop_table :passes + connection.drop_table :buses + connection.drop_table :crises_messes + connection.drop_table :messes + connection.drop_table :crises + connection.drop_table :successes + connection.drop_table :analyses + connection.drop_table :dresses + connection.drop_table :compresses + end + + def test_eager_no_extra_singularization_belongs_to + assert_nothing_raised do + Virus.all.merge!(includes: :octopus).to_a end + end - def test_eager_no_extra_singularization_has_one - assert_nothing_raised do - Octopus.all.merge!(includes: :virus).to_a - end + def test_eager_no_extra_singularization_has_one + assert_nothing_raised do + Octopus.all.merge!(includes: :virus).to_a end + end - def test_eager_no_extra_singularization_has_many - assert_nothing_raised do - Bus.all.merge!(includes: :passes).to_a - end + def test_eager_no_extra_singularization_has_many + assert_nothing_raised do + Bus.all.merge!(includes: :passes).to_a end + end - def test_eager_no_extra_singularization_has_and_belongs_to_many - assert_nothing_raised do - Crisis.all.merge!(includes: :messes).to_a - Mess.all.merge!(includes: :crises).to_a - end + def test_eager_no_extra_singularization_has_and_belongs_to_many + assert_nothing_raised do + Crisis.all.merge!(includes: :messes).to_a + Mess.all.merge!(includes: :crises).to_a end + end - def test_eager_no_extra_singularization_has_many_through_belongs_to - assert_nothing_raised do - Crisis.all.merge!(includes: :successes).to_a - end + def test_eager_no_extra_singularization_has_many_through_belongs_to + assert_nothing_raised do + Crisis.all.merge!(includes: :successes).to_a end + end - def test_eager_no_extra_singularization_has_many_through_has_many - assert_nothing_raised do - Crisis.all.merge!(includes: :compresses).to_a - end + def test_eager_no_extra_singularization_has_many_through_has_many + assert_nothing_raised do + Crisis.all.merge!(includes: :compresses).to_a end end + + private + def connection + ActiveRecord::Base.connection + end 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 efd2124679..d6b595d7e7 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 @@ -745,8 +745,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_find_scoped_grouped_having - assert_equal 2, projects(:active_record).well_payed_salary_groups.to_a.size - assert projects(:active_record).well_payed_salary_groups.all? { |g| g.salary > 10000 } + assert_equal 2, projects(:active_record).well_paid_salary_groups.to_a.size + assert projects(:active_record).well_paid_salary_groups.all? { |g| g.salary > 10000 } end def test_get_ids diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index cbecfa84ff..ede3a44090 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -611,21 +611,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_update_all_on_association_accessed_before_save firm = Firm.new(name: "Firm") - clients_proxy_id = firm.clients.object_id firm.clients << Client.first firm.save! assert_equal firm.clients.count, firm.clients.update_all(description: "Great!") - assert_not_equal clients_proxy_id, firm.clients.object_id end def test_update_all_on_association_accessed_before_save_with_explicit_foreign_key - # We can use the same cached proxy object because the id is available for the scope firm = Firm.new(name: "Firm", id: 100) - clients_proxy_id = firm.clients.object_id firm.clients << Client.first firm.save! assert_equal firm.clients.count, firm.clients.update_all(description: "Great!") - assert_equal clients_proxy_id, firm.clients.object_id end def test_belongs_to_sanity 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 25feae910b..ea52fb5a67 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -883,7 +883,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase book.subscriber_ids = [] assert_equal [], book.subscribers.reload end - end def test_collection_singular_ids_setter_with_changed_primary_key diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index fc1e61124c..7c11d2e7fc 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -661,7 +661,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase belongs_to :book, class_name: "SpecialBook" end - def test_assocation_enum_works_properly + def test_association_enum_works_properly author = SpecialAuthor.create!(name: "Test") book = SpecialBook.create!(status: "published") author.book = book @@ -669,7 +669,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase refute_equal 0, SpecialAuthor.joins(:book).where(books: { status: "published" }).count end - def test_assocation_enum_works_properly_with_nested_join + def test_association_enum_works_properly_with_nested_join author = SpecialAuthor.create!(name: "Test") book = SpecialBook.create!(status: "published") author.book = book diff --git a/activerecord/test/cases/associations/required_test.rb b/activerecord/test/cases/associations/required_test.rb index f8b686721e..45e1803858 100644 --- a/activerecord/test/cases/associations/required_test.rb +++ b/activerecord/test/cases/associations/required_test.rb @@ -22,14 +22,21 @@ class RequiredAssociationsTest < ActiveRecord::TestCase @connection.drop_table "children", if_exists: true end - test "belongs_to associations are not required by default" do - model = subclass_of(Child) do - belongs_to :parent, inverse_of: false, - class_name: "RequiredAssociationsTest::Parent" - end + test "belongs_to associations can be optional by default" do + begin + original_value = ActiveRecord::Base.belongs_to_required_by_default + ActiveRecord::Base.belongs_to_required_by_default = false + + model = subclass_of(Child) do + belongs_to :parent, inverse_of: false, + class_name: "RequiredAssociationsTest::Parent" + end - assert model.new.save - assert model.new(parent: Parent.new).save + assert model.new.save + assert model.new(parent: Parent.new).save + ensure + ActiveRecord::Base.belongs_to_required_by_default = original_value + end end test "required belongs_to associations have presence validated" do @@ -46,6 +53,27 @@ class RequiredAssociationsTest < ActiveRecord::TestCase assert record.save end + test "belongs_to associations can be required by default" do + begin + original_value = ActiveRecord::Base.belongs_to_required_by_default + ActiveRecord::Base.belongs_to_required_by_default = true + + model = subclass_of(Child) do + belongs_to :parent, inverse_of: false, + class_name: "RequiredAssociationsTest::Parent" + end + + record = model.new + assert_not record.save + assert_equal ["Parent must exist"], record.errors.full_messages + + record.parent = Parent.new + assert record.save + ensure + ActiveRecord::Base.belongs_to_required_by_default = original_value + end + end + test "has_one associations are not required by default" do model = subclass_of(Parent) do has_one :child, inverse_of: false, diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index a223b4338f..26056f6f63 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -220,11 +220,6 @@ class AssociationProxyTest < ActiveRecord::TestCase assert_equal david.projects, david.projects.scope end - test "proxy object is cached" do - david = developers(:david) - assert david.projects.equal?(david.projects) - end - test "inverses get set of subsets of the association" do man = Man.create man.interests.create diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 3dc0c0ce53..4d24a980dc 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -866,6 +866,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert subklass.method_defined?(:id), "subklass is missing id method" end + test "define_attribute_method works with both symbol and string" do + klass = Class.new(ActiveRecord::Base) + + assert_nothing_raised { klass.define_attribute_method(:foo) } + assert_nothing_raised { klass.define_attribute_method("bar") } + end + test "read_attribute with nil should not asplode" do assert_nil Topic.new.read_attribute(nil) end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index a611cc208c..979a59f566 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -98,6 +98,13 @@ class BasicsTest < ActiveRecord::TestCase assert_nil Edge.primary_key end + def test_primary_key_and_references_columns_should_be_identical_type + pk = Author.columns_hash["id"] + ref = Post.columns_hash["author_id"] + + assert_equal pk.bigint?, ref.bigint? + end + def test_many_mutations car = Car.new name: "<3<3<3" car.engines_count = 0 diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index b29733a676..3214d778d4 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -166,14 +166,14 @@ class CalculationsTest < ActiveRecord::TestCase end def test_limit_should_apply_before_count - accounts = Account.limit(3).where("firm_id IS NOT NULL") + accounts = Account.limit(4) assert_equal 3, accounts.count(:firm_id) assert_equal 3, accounts.select(:firm_id).count end def test_limit_should_apply_before_count_arel_attribute - accounts = Account.limit(3).where("firm_id IS NOT NULL") + accounts = Account.limit(4) firm_id_attribute = Account.arel_table[:firm_id] assert_equal 3, accounts.count(firm_id_attribute) @@ -227,6 +227,20 @@ class CalculationsTest < ActiveRecord::TestCase assert_match "credit_limit, firm_name", e.message end + def test_apply_distinct_in_count + queries = assert_sql do + Account.distinct.count + Account.group(:firm_id).distinct.count + end + + queries.each do |query| + # `table_alias_length` in `column_alias_for` would execute + # "SHOW max_identifier_length" statement in PostgreSQL adapter. + next if query == "SHOW max_identifier_length" + assert_match %r{\ASELECT(?! DISTINCT) COUNT\(DISTINCT\b}, query + end + end + def test_should_group_by_summed_field_having_condition c = Account.group(:firm_id).having("sum(credit_limit) > 50").sum(:credit_limit) assert_nil c[1] @@ -235,7 +249,8 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_summed_field_having_condition_from_select - c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("MIN(credit_limit) > 50").sum(:credit_limit) + skip unless current_adapter?(:Mysql2Adapter, :SQLite3Adapter) + c = Account.select("MIN(credit_limit) AS min_credit_limit").group(:firm_id).having("min_credit_limit > 50").sum(:credit_limit) assert_nil c[1] assert_equal 60, c[2] assert_equal 53, c[9] diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index 53ff037de1..b3c86586d0 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -6,10 +6,6 @@ class CallbackDeveloper < ActiveRecord::Base self.table_name = "developers" class << self - def callback_string(callback_method) - "history << [#{callback_method.to_sym.inspect}, :string]" - end - def callback_proc(callback_method) Proc.new { |model| model.history << [callback_method, :proc] } end @@ -33,7 +29,6 @@ class CallbackDeveloper < ActiveRecord::Base ActiveRecord::Callbacks::CALLBACKS.each do |callback_method| next if callback_method.to_s.start_with?("around_") define_callback_method(callback_method) - ActiveSupport::Deprecation.silence { send(callback_method, callback_string(callback_method)) } send(callback_method, callback_proc(callback_method)) send(callback_method, callback_object(callback_method)) send(callback_method) { |model| model.history << [callback_method, :block] } @@ -44,11 +39,6 @@ class CallbackDeveloper < ActiveRecord::Base end end -class CallbackDeveloperWithFalseValidation < CallbackDeveloper - before_validation proc { |model| model.history << [:before_validation, :returning_false]; false } - before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } -end - class CallbackDeveloperWithHaltedValidation < CallbackDeveloper before_validation proc { |model| model.history << [:before_validation, :throwing_abort]; throw(:abort) } before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } @@ -137,23 +127,6 @@ class ContextualCallbacksDeveloper < ActiveRecord::Base end end -class CallbackCancellationDeveloper < ActiveRecord::Base - self.table_name = "developers" - - attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called - attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy - - before_save { defined?(@cancel_before_save) ? !@cancel_before_save : false } - before_create { !@cancel_before_create } - before_update { !@cancel_before_update } - before_destroy { !@cancel_before_destroy } - - after_save { @after_save_called = true } - after_update { @after_update_called = true } - after_create { @after_create_called = true } - after_destroy { @after_destroy_called = true } -end - class CallbackHaltedDeveloper < ActiveRecord::Base self.table_name = "developers" @@ -178,7 +151,6 @@ class CallbacksTest < ActiveRecord::TestCase david = CallbackDeveloper.new assert_equal [ [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], @@ -189,12 +161,10 @@ class CallbacksTest < ActiveRecord::TestCase david = CallbackDeveloper.find(1) assert_equal [ [ :after_find, :method ], - [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], @@ -206,17 +176,14 @@ class CallbacksTest < ActiveRecord::TestCase david.valid? assert_equal [ [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], - [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :after_validation, :method ], - [ :after_validation, :string ], [ :after_validation, :proc ], [ :after_validation, :object ], [ :after_validation, :block ], @@ -228,22 +195,18 @@ class CallbacksTest < ActiveRecord::TestCase david.valid? assert_equal [ [ :after_find, :method ], - [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], - [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :after_validation, :method ], - [ :after_validation, :string ], [ :after_validation, :proc ], [ :after_validation, :object ], [ :after_validation, :block ], @@ -254,44 +217,36 @@ class CallbacksTest < ActiveRecord::TestCase david = CallbackDeveloper.create("name" => "David", "salary" => 1000000) assert_equal [ [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], - [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :after_validation, :method ], - [ :after_validation, :string ], [ :after_validation, :proc ], [ :after_validation, :object ], [ :after_validation, :block ], [ :before_save, :method ], - [ :before_save, :string ], [ :before_save, :proc ], [ :before_save, :object ], [ :before_save, :block ], [ :before_create, :method ], - [ :before_create, :string ], [ :before_create, :proc ], [ :before_create, :object ], [ :before_create, :block ], [ :after_create, :method ], - [ :after_create, :string ], [ :after_create, :proc ], [ :after_create, :object ], [ :after_create, :block ], [ :after_save, :method ], - [ :after_save, :string ], [ :after_save, :proc ], [ :after_save, :object ], [ :after_save, :block ], [ :after_commit, :block ], [ :after_commit, :object ], [ :after_commit, :proc ], - [ :after_commit, :string ], [ :after_commit, :method ] ], david.history end @@ -323,49 +278,40 @@ class CallbacksTest < ActiveRecord::TestCase david.save assert_equal [ [ :after_find, :method ], - [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], - [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :after_validation, :method ], - [ :after_validation, :string ], [ :after_validation, :proc ], [ :after_validation, :object ], [ :after_validation, :block ], [ :before_save, :method ], - [ :before_save, :string ], [ :before_save, :proc ], [ :before_save, :object ], [ :before_save, :block ], [ :before_update, :method ], - [ :before_update, :string ], [ :before_update, :proc ], [ :before_update, :object ], [ :before_update, :block ], [ :after_update, :method ], - [ :after_update, :string ], [ :after_update, :proc ], [ :after_update, :object ], [ :after_update, :block ], [ :after_save, :method ], - [ :after_save, :string ], [ :after_save, :proc ], [ :after_save, :object ], [ :after_save, :block ], [ :after_commit, :block ], [ :after_commit, :object ], [ :after_commit, :proc ], - [ :after_commit, :string ], [ :after_commit, :method ] ], david.history end @@ -399,29 +345,24 @@ class CallbacksTest < ActiveRecord::TestCase david.destroy assert_equal [ [ :after_find, :method ], - [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_destroy, :method ], - [ :before_destroy, :string ], [ :before_destroy, :proc ], [ :before_destroy, :object ], [ :before_destroy, :block ], [ :after_destroy, :method ], - [ :after_destroy, :string ], [ :after_destroy, :proc ], [ :after_destroy, :object ], [ :after_destroy, :block ], [ :after_commit, :block ], [ :after_commit, :object ], [ :after_commit, :proc ], - [ :after_commit, :string ], [ :after_commit, :method ] ], david.history end @@ -431,82 +372,16 @@ class CallbacksTest < ActiveRecord::TestCase CallbackDeveloper.delete(david.id) assert_equal [ [ :after_find, :method ], - [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], ], david.history end - def test_deprecated_before_save_returning_false - david = ImmutableDeveloper.find(1) - assert_deprecated do - assert david.valid? - assert !david.save - exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } - assert_equal david, exc.record - assert_equal "Failed to save the record", exc.message - end - - david = ImmutableDeveloper.find(1) - david.salary = 10_000_000 - assert !david.valid? - assert !david.save - assert_raise(ActiveRecord::RecordInvalid) { david.save! } - - someone = CallbackCancellationDeveloper.find(1) - someone.cancel_before_save = true - assert_deprecated do - assert someone.valid? - assert !someone.save - end - assert_save_callbacks_not_called(someone) - end - - def test_deprecated_before_create_returning_false - someone = CallbackCancellationDeveloper.new - someone.cancel_before_create = true - assert_deprecated do - assert someone.valid? - assert !someone.save - end - assert_save_callbacks_not_called(someone) - end - - def test_deprecated_before_update_returning_false - someone = CallbackCancellationDeveloper.find(1) - someone.cancel_before_update = true - assert_deprecated do - assert someone.valid? - assert !someone.save - end - assert_save_callbacks_not_called(someone) - end - - def test_deprecated_before_destroy_returning_false - david = ImmutableDeveloper.find(1) - assert_deprecated do - assert !david.destroy - exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } - assert_equal david, exc.record - assert_equal "Failed to destroy the record", exc.message - end - assert_not_nil ImmutableDeveloper.find_by_id(1) - - someone = CallbackCancellationDeveloper.find(1) - someone.cancel_before_destroy = true - assert_deprecated do - assert !someone.destroy - assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! } - end - assert !someone.after_destroy_called - end - def assert_save_callbacks_not_called(someone) assert !someone.after_save_called assert !someone.after_create_called @@ -564,50 +439,19 @@ class CallbacksTest < ActiveRecord::TestCase assert !someone.after_destroy_called end - def test_callback_returning_false - david = CallbackDeveloperWithFalseValidation.find(1) - assert_deprecated { david.save } - assert_equal [ - [ :after_find, :method ], - [ :after_find, :string ], - [ :after_find, :proc ], - [ :after_find, :object ], - [ :after_find, :block ], - [ :after_initialize, :method ], - [ :after_initialize, :string ], - [ :after_initialize, :proc ], - [ :after_initialize, :object ], - [ :after_initialize, :block ], - [ :before_validation, :method ], - [ :before_validation, :string ], - [ :before_validation, :proc ], - [ :before_validation, :object ], - [ :before_validation, :block ], - [ :before_validation, :returning_false ], - [ :after_rollback, :block ], - [ :after_rollback, :object ], - [ :after_rollback, :proc ], - [ :after_rollback, :string ], - [ :after_rollback, :method ], - ], david.history - end - def test_callback_throwing_abort david = CallbackDeveloperWithHaltedValidation.find(1) david.save assert_equal [ [ :after_find, :method ], - [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], - [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], @@ -615,7 +459,6 @@ class CallbacksTest < ActiveRecord::TestCase [ :after_rollback, :block ], [ :after_rollback, :object ], [ :after_rollback, :proc ], - [ :after_rollback, :string ], [ :after_rollback, :method ], ], david.history end diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb index 1e0b4580af..59ef389326 100644 --- a/activerecord/test/cases/coders/yaml_column_test.rb +++ b/activerecord/test/cases/coders/yaml_column_test.rb @@ -10,19 +10,19 @@ module ActiveRecord end def test_type_mismatch_on_different_classes_on_dump - coder = YAMLColumn.new("attr_name", Array) + coder = YAMLColumn.new("tags", Array) error = assert_raises(SerializationTypeMismatch) do coder.dump("a") end - assert_equal %{Attribute `attr_name` was supposed to be a Array, but was a String. -- "a"}, error.to_s + assert_equal %{can't dump `tags`: was supposed to be a Array, but was a String. -- "a"}, error.to_s end def test_type_mismatch_on_different_classes - coder = YAMLColumn.new("attr_name", Array) + coder = YAMLColumn.new("tags", Array) error = assert_raises(SerializationTypeMismatch) do coder.load "--- foo" end - assert_equal %{Attribute `attr_name` was supposed to be a Array, but was a String. -- "foo"}, error.to_s + assert_equal %{can't load `tags`: was supposed to be a Array, but was a String. -- "foo"}, error.to_s end def test_nil_is_ok diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index a65bb89052..d230700119 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -14,71 +14,19 @@ module ActiveRecord # Avoid column definitions in create table statements like: # `title` varchar(255) DEFAULT NULL def test_should_not_include_default_clause_when_default_is_null - column = Column.new("title", nil, SqlTypeMetadata.new(limit: 20)) - column_def = ColumnDefinition.new( - column.name, "string", - column.limit, column.precision, column.scale, column.default, column.null) + column_def = ColumnDefinition.new("title", "string", limit: 20) assert_equal "title varchar(20)", @viz.accept(column_def) end def test_should_include_default_clause_when_default_is_present - column = Column.new("title", "Hello", SqlTypeMetadata.new(limit: 20)) - column_def = ColumnDefinition.new( - column.name, "string", - column.limit, column.precision, column.scale, column.default, column.null) + column_def = ColumnDefinition.new("title", "string", limit: 20, default: "Hello") assert_equal "title varchar(20) DEFAULT 'Hello'", @viz.accept(column_def) end def test_should_specify_not_null_if_null_option_is_false - type_metadata = SqlTypeMetadata.new(limit: 20) - column = Column.new("title", "Hello", type_metadata, false) - column_def = ColumnDefinition.new( - column.name, "string", - column.limit, column.precision, column.scale, column.default, column.null) + column_def = ColumnDefinition.new("title", "string", limit: 20, default: "Hello", null: false) assert_equal "title varchar(20) DEFAULT 'Hello' NOT NULL", @viz.accept(column_def) end - - if current_adapter?(:Mysql2Adapter) - def test_should_set_default_for_mysql_binary_data_types - type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)") - binary_column = MySQL::Column.new("title", "a", type) - assert_equal "a", binary_column.default - - type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary") - varbinary_column = MySQL::Column.new("title", "a", type) - assert_equal "a", varbinary_column.default - end - - def test_should_be_empty_string_default_for_mysql_binary_data_types - type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)") - binary_column = MySQL::Column.new("title", "", type, false) - assert_equal "", binary_column.default - - type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary") - varbinary_column = MySQL::Column.new("title", "", type, false) - assert_equal "", varbinary_column.default - end - - def test_should_not_set_default_for_blob_and_text_data_types - text_type = MySQL::TypeMetadata.new(SqlTypeMetadata.new(type: :text)) - - text_column = MySQL::Column.new("title", nil, text_type) - assert_nil text_column.default - - not_null_text_column = MySQL::Column.new("title", nil, text_type, false) - assert_nil not_null_text_column.default - end - - def test_has_default_should_return_false_for_blob_and_text_data_types - binary_type = SqlTypeMetadata.new(sql_type: "blob") - blob_column = MySQL::Column.new("title", nil, binary_type) - assert !blob_column.has_default? - - text_type = SqlTypeMetadata.new(type: :text) - text_column = MySQL::Column.new("title", nil, text_type) - assert !text_column.has_default? - end - end end end end diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb index a625299e8d..63f67a9a16 100644 --- a/activerecord/test/cases/comment_test.rb +++ b/activerecord/test/cases/comment_test.rb @@ -113,7 +113,7 @@ if ActiveRecord::Base.connection.supports_comments? assert_match %r[t\.string\s+"content",\s+comment: "Whoa, content describes itself!"], output assert_match %r[t\.integer\s+"rating",\s+comment: "I am running out of imagination"], output assert_match %r[t\.index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output - assert_match %r[t\.index\s+.+\s+name: "idx_obvious",.+\s+comment: "We need to see obvious comments"], output + assert_match %r[t\.index\s+.+\s+name: "idx_obvious",\s+comment: "We need to see obvious comments"], output end def test_schema_dump_omits_blank_comments diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 4f2392042b..681399c8bb 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -20,6 +20,66 @@ module ActiveRecord @handler.remove_connection("readonly") end + def test_establish_connection_using_3_levels_config + previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env" + + config = { + "default_env" => { + "readonly" => { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" }, + "primary" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" } + }, + "another_env" => { + "readonly" => { "adapter" => "sqlite3", "database" => "db/bad-readonly.sqlite3" }, + "primary" => { "adapter" => "sqlite3", "database" => "db/bad-primary.sqlite3" } + }, + "common" => { "adapter" => "sqlite3", "database" => "db/common.sqlite3" } + } + @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config + + @handler.establish_connection(:common) + @handler.establish_connection(:primary) + @handler.establish_connection(:readonly) + + assert_not_nil pool = @handler.retrieve_connection_pool("readonly") + assert_equal "db/readonly.sqlite3", pool.spec.config[:database] + + assert_not_nil pool = @handler.retrieve_connection_pool("primary") + assert_equal "db/primary.sqlite3", pool.spec.config[:database] + + assert_not_nil pool = @handler.retrieve_connection_pool("common") + assert_equal "db/common.sqlite3", pool.spec.config[:database] + ensure + ActiveRecord::Base.configurations = @prev_configs + ENV["RAILS_ENV"] = previous_env + end + + def test_establish_connection_using_two_level_configurations + config = { "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" } } + @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config + + @handler.establish_connection(:development) + + assert_not_nil pool = @handler.retrieve_connection_pool("development") + assert_equal "db/primary.sqlite3", pool.spec.config[:database] + ensure + ActiveRecord::Base.configurations = @prev_configs + end + + def test_establish_connection_using_top_level_key_in_two_level_config + config = { + "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" }, + "development_readonly" => { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" } + } + @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config + + @handler.establish_connection(:development_readonly) + + assert_not_nil pool = @handler.retrieve_connection_pool("development_readonly") + assert_equal "db/readonly.sqlite3", pool.spec.config[:database] + ensure + ActiveRecord::Base.configurations = @prev_configs + end + def test_retrieve_connection assert @handler.retrieve_connection(@spec_name) end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 9f7280634e..7e88c9cf7a 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -307,14 +307,17 @@ module ActiveRecord end end - def test_automatic_reconnect= + def test_automatic_reconnect_restores_after_disconnect pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec assert pool.automatic_reconnect assert pool.connection pool.disconnect! assert pool.connection + end + def test_automatic_reconnect_can_be_disabled + pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec pool.disconnect! pool.automatic_reconnect = false @@ -441,7 +444,7 @@ module ActiveRecord end end - def test_bang_versions_of_disconnect_and_clear_reloadable_connections_if_unable_to_aquire_all_connections_proceed_anyway + def test_bang_versions_of_disconnect_and_clear_reloadable_connections_if_unable_to_acquire_all_connections_proceed_anyway @pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout [:disconnect!, :clear_reloadable_connections!].each do |group_action_method| @pool.with_connection do |connection| diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index 3bc08f80ec..ad7da9de70 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -52,7 +52,7 @@ class DateTimeTest < ActiveRecord::TestCase end def test_assign_in_local_timezone - now = DateTime.now + now = DateTime.civil(2017, 3, 1, 12, 0, 0) with_timezone_config default: :local do task = Task.new starting: now assert_equal now, task.starting diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index 6532efcf22..a6297673c9 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -100,11 +100,21 @@ if current_adapter?(:Mysql2Adapter) include SchemaDumpingHelper if ActiveRecord::Base.connection.version >= "5.6.0" - test "schema dump includes default expression" do + test "schema dump datetime includes default expression" do output = dump_table_schema("datetime_defaults") assert_match %r/t\.datetime\s+"modified_datetime",\s+default: -> { "CURRENT_TIMESTAMP" }/, output end end + + test "schema dump timestamp includes default expression" do + output = dump_table_schema("timestamp_defaults") + assert_match %r/t\.timestamp\s+"modified_timestamp",\s+default: -> { "CURRENT_TIMESTAMP" }/, output + end + + test "schema dump timestamp without default expression" do + output = dump_table_schema("timestamp_defaults") + assert_match %r/t\.timestamp\s+"nullable_timestamp"$/, output + end end class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 0e58e65a07..c13a962e3e 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -301,6 +301,14 @@ class DirtyTest < ActiveRecord::TestCase assert_equal ["arr", "arr matey!"], pirate.catchphrase_change end + def test_virtual_attribute_will_change + assert_deprecated do + parrot = Parrot.create!(name: "Ruby") + parrot.send(:attribute_will_change!, :cancel_save_from_callback) + assert parrot.has_changes_to_save? + end + end + def test_association_assignment_changes_foreign_key pirate = Pirate.create!(catchphrase: "jarl") pirate.parrot = Parrot.create!(name: "Lorre") @@ -558,18 +566,17 @@ class DirtyTest < ActiveRecord::TestCase travel_back end - if ActiveRecord::Base.connection.supports_migrations? - class Testings < ActiveRecord::Base; end - def test_field_named_field - ActiveRecord::Base.connection.create_table :testings do |t| - t.string :field - end - assert_nothing_raised do - Testings.new.attributes - end - ensure - ActiveRecord::Base.connection.drop_table :testings rescue nil + class Testings < ActiveRecord::Base; end + def test_field_named_field + ActiveRecord::Base.connection.create_table :testings do |t| + t.string :field end + assert_nothing_raised do + Testings.new.attributes + end + ensure + ActiveRecord::Base.connection.drop_table :testings rescue nil + ActiveRecord::Base.clear_cache! end def test_datetime_attribute_can_be_updated_with_fractional_seconds diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index e0ad9f5ec1..89d8a8bdca 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -497,7 +497,7 @@ class FinderTest < ActiveRecord::TestCase assert_nil Topic.offset(5).second_to_last #test with limit - # assert_nil Topic.limit(1).second # TODO: currently failing + assert_nil Topic.limit(1).second assert_nil Topic.limit(1).second_to_last end @@ -526,9 +526,9 @@ class FinderTest < ActiveRecord::TestCase assert_nil Topic.offset(5).third_to_last # test with limit - # assert_nil Topic.limit(1).third # TODO: currently failing + assert_nil Topic.limit(1).third assert_nil Topic.limit(1).third_to_last - # assert_nil Topic.limit(2).third # TODO: currently failing + assert_nil Topic.limit(2).third assert_nil Topic.limit(2).third_to_last end @@ -863,13 +863,13 @@ class FinderTest < ActiveRecord::TestCase end def test_bind_variables_with_quotes - Company.create("name" => "37signals' go'es agains") - assert Company.where(["name = ?", "37signals' go'es agains"]).first + Company.create("name" => "37signals' go'es against") + assert Company.where(["name = ?", "37signals' go'es against"]).first end def test_named_bind_variables_with_quotes - Company.create("name" => "37signals' go'es agains") - assert Company.where(["name = :name", { name: "37signals' go'es agains" }]).first + Company.create("name" => "37signals' go'es against") + assert Company.where(["name = :name", { name: "37signals' go'es against" }]).first end def test_named_bind_variables diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 61e596e208..51133e9495 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -104,64 +104,62 @@ class FixturesTest < ActiveRecord::TestCase assert_nil(second_row["author_email_address"]) end - if ActiveRecord::Base.connection.supports_migrations? - def test_inserts_with_pre_and_suffix - # Reset cache to make finds on the new table work - ActiveRecord::FixtureSet.reset_cache - - ActiveRecord::Base.connection.create_table :prefix_other_topics_suffix do |t| - t.column :title, :string - t.column :author_name, :string - t.column :author_email_address, :string - t.column :written_on, :datetime - t.column :bonus_time, :time - t.column :last_read, :date - t.column :content, :string - t.column :approved, :boolean, default: true - t.column :replies_count, :integer, default: 0 - t.column :parent_id, :integer - t.column :type, :string, limit: 50 - end + def test_inserts_with_pre_and_suffix + # Reset cache to make finds on the new table work + ActiveRecord::FixtureSet.reset_cache + + ActiveRecord::Base.connection.create_table :prefix_other_topics_suffix do |t| + t.column :title, :string + t.column :author_name, :string + t.column :author_email_address, :string + t.column :written_on, :datetime + t.column :bonus_time, :time + t.column :last_read, :date + t.column :content, :string + t.column :approved, :boolean, default: true + t.column :replies_count, :integer, default: 0 + t.column :parent_id, :integer + t.column :type, :string, limit: 50 + end - # Store existing prefix/suffix - old_prefix = ActiveRecord::Base.table_name_prefix - old_suffix = ActiveRecord::Base.table_name_suffix + # Store existing prefix/suffix + old_prefix = ActiveRecord::Base.table_name_prefix + old_suffix = ActiveRecord::Base.table_name_suffix - # Set a prefix/suffix we can test against - ActiveRecord::Base.table_name_prefix = "prefix_" - ActiveRecord::Base.table_name_suffix = "_suffix" + # Set a prefix/suffix we can test against + ActiveRecord::Base.table_name_prefix = "prefix_" + ActiveRecord::Base.table_name_suffix = "_suffix" - other_topic_klass = Class.new(ActiveRecord::Base) do - def self.name - "OtherTopic" - end + other_topic_klass = Class.new(ActiveRecord::Base) do + def self.name + "OtherTopic" end + end - topics = [create_fixtures("other_topics")].flatten.first + topics = [create_fixtures("other_topics")].flatten.first - # This checks for a caching problem which causes a bug in the fixtures - # class-level configuration helper. - assert_not_nil topics, "Fixture data inserted, but fixture objects not returned from create" + # This checks for a caching problem which causes a bug in the fixtures + # class-level configuration helper. + assert_not_nil topics, "Fixture data inserted, but fixture objects not returned from create" - first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'David'") - assert_not_nil first_row, "The prefix_other_topics_suffix table appears to be empty despite create_fixtures: the row with author_name = 'David' was not found" - assert_equal("The First Topic", first_row["title"]) + first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'David'") + assert_not_nil first_row, "The prefix_other_topics_suffix table appears to be empty despite create_fixtures: the row with author_name = 'David' was not found" + assert_equal("The First Topic", first_row["title"]) - second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'Mary'") - assert_nil(second_row["author_email_address"]) + second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'Mary'") + assert_nil(second_row["author_email_address"]) - assert_equal :prefix_other_topics_suffix, topics.table_name.to_sym - # This assertion should preferably be the last in the list, because calling - # other_topic_klass.table_name sets a class-level instance variable - assert_equal :prefix_other_topics_suffix, other_topic_klass.table_name.to_sym + assert_equal :prefix_other_topics_suffix, topics.table_name.to_sym + # This assertion should preferably be the last in the list, because calling + # other_topic_klass.table_name sets a class-level instance variable + assert_equal :prefix_other_topics_suffix, other_topic_klass.table_name.to_sym - ensure - # Restore prefix/suffix to its previous values - ActiveRecord::Base.table_name_prefix = old_prefix - ActiveRecord::Base.table_name_suffix = old_suffix + ensure + # Restore prefix/suffix to its previous values + ActiveRecord::Base.table_name_prefix = old_prefix + ActiveRecord::Base.table_name_suffix = old_suffix - ActiveRecord::Base.connection.drop_table :prefix_other_topics_suffix rescue nil - end + ActiveRecord::Base.connection.drop_table :prefix_other_topics_suffix rescue nil end def test_insert_with_datetime @@ -640,6 +638,8 @@ class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase def test_transaction_created_on_connection_notification connection = stub(transaction_open?: false) connection.expects(:begin_transaction).with(joinable: false) + pool = connection.stubs(:pool).returns(ActiveRecord::ConnectionAdapters::ConnectionPool.new(ActiveRecord::Base.connection_pool.spec)) + pool.stubs(:lock_thread=).with(false) fire_connection_notification(connection) end @@ -647,12 +647,16 @@ class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase # Mocha is not thread-safe so define our own stub to test connection = Class.new do attr_accessor :rollback_transaction_called + attr_accessor :pool def transaction_open?; true; end def begin_transaction(*args); end def rollback_transaction(*args) @rollback_transaction_called = true end end.new + connection.pool = Class.new do + def lock_thread=(lock_thread); false; end + end.new fire_connection_notification(connection) teardown_fixtures assert(connection.rollback_transaction_called, "Expected <mock connection>#rollback_transaction to be called but was not") diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 1ddcbf0e4f..5a3b8e3fb5 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -6,7 +6,6 @@ require "active_record" require "cases/test_case" require "active_support/dependencies" require "active_support/logger" -require "active_support/core_ext/string/strip" require "support/config" require "support/connection" diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb index 155e858822..5a1d066aef 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -101,6 +101,17 @@ class JsonSerializationTest < ActiveRecord::TestCase assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json end + def test_uses_serializable_hash_with_frozen_hash + def @contact.serializable_hash(options = nil) + super({ only: %w(name) }.freeze) + end + + json = @contact.to_json + assert_match %r{"name":"Konata Izumi"}, json + assert_no_match %r{awesome}, json + assert_no_match %r{age}, json + end + def test_uses_serializable_hash_with_only_option def @contact.serializable_hash(options = nil) super(only: %w(name)) diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 9e42242e55..23095618a4 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -536,7 +536,10 @@ unless in_memory_db? Person.transaction do person = Person.find 1 old, person.first_name = person.first_name, "fooman" - person.lock! + # Locking a dirty record is deprecated + assert_deprecated do + person.lock! + end assert_equal old, person.first_name end end diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index 48cfe89882..1d305fa11f 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -269,6 +269,8 @@ module ActiveRecord if current_adapter?(:PostgreSQLAdapter) assert_equal "timestamp without time zone", klass.columns_hash["foo"].sql_type + elsif current_adapter?(:Mysql2Adapter) + assert_equal "timestamp", klass.columns_hash["foo"].sql_type else assert_equal klass.connection.type_to_sql("datetime"), klass.columns_hash["foo"].sql_type end diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb index 55c06da411..2329888345 100644 --- a/activerecord/test/cases/migration/columns_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -225,6 +225,16 @@ module ActiveRecord assert_nil TestModel.new.contributor end + def test_change_column_to_drop_default_with_null_false + add_column "test_models", "contributor", :boolean, default: true, null: false + assert TestModel.new.contributor? + + change_column "test_models", "contributor", :boolean, default: nil, null: false + TestModel.reset_column_information + assert_not TestModel.new.contributor? + assert_nil TestModel.new.contributor + end + def test_change_column_with_new_default add_column "test_models", "administrator", :boolean, default: true assert TestModel.new.administrator? diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb index 9296f3da90..7a80bfb899 100644 --- a/activerecord/test/cases/migration/compatibility_test.rb +++ b/activerecord/test/cases/migration/compatibility_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require "support/schema_dumping_helper" module ActiveRecord class Migration @@ -111,3 +112,110 @@ module ActiveRecord end end end + +class LegacyPrimaryKeyTest < ActiveRecord::TestCase + include SchemaDumpingHelper + + self.use_transactional_tests = false + + class LegacyPrimaryKey < ActiveRecord::Base + end + + def setup + @migration = nil + @verbose_was = ActiveRecord::Migration.verbose + ActiveRecord::Migration.verbose = false + end + + def teardown + @migration.migrate(:down) if @migration + ActiveRecord::Migration.verbose = @verbose_was + ActiveRecord::SchemaMigration.delete_all rescue nil + LegacyPrimaryKey.reset_column_information + end + + def test_legacy_primary_key_should_be_auto_incremented + @migration = Class.new(ActiveRecord::Migration[5.0]) { + def change + create_table :legacy_primary_keys do |t| + t.references :legacy_ref + end + end + }.new + + @migration.migrate(:up) + + legacy_pk = LegacyPrimaryKey.columns_hash["id"] + assert_not legacy_pk.bigint? + assert_not legacy_pk.null + + legacy_ref = LegacyPrimaryKey.columns_hash["legacy_ref_id"] + assert_not legacy_ref.bigint? + + record1 = LegacyPrimaryKey.create! + assert_not_nil record1.id + + record1.destroy + + record2 = LegacyPrimaryKey.create! + assert_not_nil record2.id + assert_operator record2.id, :>, record1.id + end + + def test_legacy_integer_primary_key_should_not_be_auto_incremented + skip if current_adapter?(:SQLite3Adapter) + + @migration = Class.new(ActiveRecord::Migration[5.0]) { + def change + create_table :legacy_primary_keys, id: :integer do |t| + end + end + }.new + + @migration.migrate(:up) + + assert_raises(ActiveRecord::NotNullViolation) do + LegacyPrimaryKey.create! + end + + schema = dump_table_schema "legacy_primary_keys" + assert_match %r{create_table "legacy_primary_keys", id: :integer, default: nil}, schema + end + + if current_adapter?(:Mysql2Adapter) + def test_legacy_bigint_primary_key_should_be_auto_incremented + @migration = Class.new(ActiveRecord::Migration[5.0]) { + def change + create_table :legacy_primary_keys, id: :bigint + end + }.new + + @migration.migrate(:up) + + legacy_pk = LegacyPrimaryKey.columns_hash["id"] + assert legacy_pk.bigint? + assert legacy_pk.auto_increment? + + schema = dump_table_schema "legacy_primary_keys" + assert_match %r{create_table "legacy_primary_keys", (?!id: :bigint, default: nil)}, schema + end + else + def test_legacy_bigint_primary_key_should_not_be_auto_incremented + @migration = Class.new(ActiveRecord::Migration[5.0]) { + def change + create_table :legacy_primary_keys, id: :bigint do |t| + end + end + }.new + + @migration.migrate(:up) + + assert_raises(ActiveRecord::NotNullViolation) do + LegacyPrimaryKey.create! + end + + schema = dump_table_schema "legacy_primary_keys" + assert_match %r{create_table "legacy_primary_keys", id: :bigint, default: nil}, schema + end + end +end diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb index 26b1bb4419..c4896f3d6e 100644 --- a/activerecord/test/cases/migration/create_join_table_test.rb +++ b/activerecord/test/cases/migration/create_join_table_test.rb @@ -12,7 +12,7 @@ module ActiveRecord teardown do %w(artists_musics musics_videos catalog).each do |table_name| - connection.drop_table table_name if connection.table_exists?(table_name) + connection.drop_table table_name, if_exists: true end end @@ -78,6 +78,17 @@ module ActiveRecord assert_equal [%w(artist_id music_id)], connection.indexes(:artists_musics).map(&:columns) end + def test_create_join_table_respects_reference_key_type + connection.create_join_table :artists, :musics do |t| + t.references :video + end + + artist_id, music_id, video_id = connection.columns(:artists_musics).sort_by(&:name) + + assert_equal video_id.sql_type, artist_id.sql_type + assert_equal video_id.sql_type, music_id.sql_type + end + def test_drop_join_table connection.create_join_table :artists, :musics connection.drop_join_table :artists, :musics diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb index 96e775a58b..7762d37915 100644 --- a/activerecord/test/cases/migration/foreign_key_test.rb +++ b/activerecord/test/cases/migration/foreign_key_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require "support/ddl_helper" require "support/schema_dumping_helper" if ActiveRecord::Base.connection.supports_foreign_keys_in_create? @@ -26,7 +25,6 @@ if ActiveRecord::Base.connection.supports_foreign_keys? module ActiveRecord class Migration class ForeignKeyTest < ActiveRecord::TestCase - include DdlHelper include SchemaDumpingHelper include ActiveSupport::Testing::Stream @@ -94,20 +92,23 @@ if ActiveRecord::Base.connection.supports_foreign_keys? end def test_add_foreign_key_with_non_standard_primary_key - with_example_table @connection, "space_shuttles", "pk BIGINT PRIMARY KEY" do - @connection.add_foreign_key(:astronauts, :space_shuttles, - column: "rocket_id", primary_key: "pk", name: "custom_pk") + @connection.create_table :space_shuttles, id: false, force: true do |t| + t.bigint :pk, primary_key: true + end - foreign_keys = @connection.foreign_keys("astronauts") - assert_equal 1, foreign_keys.size + @connection.add_foreign_key(:astronauts, :space_shuttles, + column: "rocket_id", primary_key: "pk", name: "custom_pk") - fk = foreign_keys.first - assert_equal "astronauts", fk.from_table - assert_equal "space_shuttles", fk.to_table - assert_equal "pk", fk.primary_key + foreign_keys = @connection.foreign_keys("astronauts") + assert_equal 1, foreign_keys.size - @connection.remove_foreign_key :astronauts, name: "custom_pk" - end + fk = foreign_keys.first + assert_equal "astronauts", fk.from_table + assert_equal "space_shuttles", fk.to_table + assert_equal "pk", fk.primary_key + ensure + @connection.remove_foreign_key :astronauts, name: "custom_pk" + @connection.drop_table :space_shuttles end def test_add_on_delete_restrict_foreign_key @@ -247,7 +248,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys? create_table("cities") { |t| } create_table("houses") do |t| - t.column :city_id, :bigint + t.references :city end add_foreign_key :houses, :cities, column: "city_id" @@ -279,7 +280,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys? create_table(:schools) create_table(:classes) do |t| - t.column :school_id, :bigint + t.references :school end add_foreign_key :classes, :schools end diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb index 0f975026b8..f10fcf1398 100644 --- a/activerecord/test/cases/migration/index_test.rb +++ b/activerecord/test/cases/migration/index_test.rb @@ -31,9 +31,10 @@ module ActiveRecord connection.add_index(table_name, [:foo], name: "old_idx") connection.rename_index(table_name, "old_idx", "new_idx") - # if the adapter doesn't support the indexes call, pick defaults that let the test pass - assert_not connection.index_name_exists?(table_name, "old_idx", false) - assert connection.index_name_exists?(table_name, "new_idx", true) + assert_deprecated do + assert_not connection.index_name_exists?(table_name, "old_idx", false) + assert connection.index_name_exists?(table_name, "new_idx", true) + end end def test_rename_index_too_long @@ -45,8 +46,7 @@ module ActiveRecord } assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message) - # if the adapter doesn't support the indexes call, pick defaults that let the test pass - assert connection.index_name_exists?(table_name, "old_idx", false) + assert connection.index_name_exists?(table_name, "old_idx") end def test_double_add_index @@ -63,7 +63,7 @@ module ActiveRecord def test_add_index_works_with_long_index_names connection.add_index(table_name, "foo", name: good_index_name) - assert connection.index_name_exists?(table_name, good_index_name, false) + assert connection.index_name_exists?(table_name, good_index_name) connection.remove_index(table_name, name: good_index_name) end @@ -75,7 +75,7 @@ module ActiveRecord } assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message) - assert_not connection.index_name_exists?(table_name, too_long_index_name, false) + assert_not connection.index_name_exists?(table_name, too_long_index_name) connection.add_index(table_name, "foo", name: good_index_name) end @@ -83,7 +83,7 @@ module ActiveRecord good_index_name = "x" * connection.index_name_length connection.add_index(table_name, "foo", name: good_index_name, internal: true) - assert connection.index_name_exists?(table_name, good_index_name, false) + assert connection.index_name_exists?(table_name, good_index_name) connection.remove_index(table_name, name: good_index_name) end diff --git a/activerecord/test/cases/migration/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb index 61f5a061b0..6970fdcc87 100644 --- a/activerecord/test/cases/migration/pending_migrations_test.rb +++ b/activerecord/test/cases/migration/pending_migrations_test.rb @@ -21,8 +21,6 @@ module ActiveRecord end def test_errors_if_pending - @connection.expect :supports_migrations?, true - ActiveRecord::Migrator.stub :needs_migration?, true do assert_raise ActiveRecord::PendingMigrationError do @pending.call(nil) @@ -31,22 +29,12 @@ module ActiveRecord end def test_checks_if_supported - @connection.expect :supports_migrations?, true @app.expect :call, nil, [:foo] ActiveRecord::Migrator.stub :needs_migration?, false do @pending.call(:foo) end end - - def test_doesnt_check_if_unsupported - @connection.expect :supports_migrations?, false - @app.expect :call, nil, [:foo] - - ActiveRecord::Migrator.stub :needs_migration?, true do - @pending.call(:foo) - end - end end end end diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb index 560adcbfed..f1ddac1ee2 100644 --- a/activerecord/test/cases/migration/references_foreign_key_test.rb +++ b/activerecord/test/cases/migration/references_foreign_key_test.rb @@ -42,8 +42,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys_in_create? test "options hash can be passed" do @connection.change_table :testing_parents do |t| - t.bigint :other_id - t.index :other_id, unique: true + t.references :other, index: { unique: true } end @connection.create_table :testings do |t| t.references :testing_parent, foreign_key: { primary_key: :other_id } @@ -110,8 +109,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys? test "foreign keys accept options when changing the table" do @connection.change_table :testing_parents do |t| - t.bigint :other_id - t.index :other_id, unique: true + t.references :other, index: { unique: true } end @connection.create_table :testings @connection.change_table :testings do |t| @@ -195,18 +193,31 @@ if ActiveRecord::Base.connection.supports_foreign_keys? test "multiple foreign keys can be added to the same table" do @connection.create_table :testings do |t| - t.bigint :col_1 - t.bigint :col_2 + t.references :parent1, foreign_key: { to_table: :testing_parents } + t.references :parent2, foreign_key: { to_table: :testing_parents } + end + + fks = @connection.foreign_keys("testings").sort_by(&:column) + + fk_definitions = fks.map { |fk| [fk.from_table, fk.to_table, fk.column] } + assert_equal([["testings", "testing_parents", "parent1_id"], + ["testings", "testing_parents", "parent2_id"]], fk_definitions) + end - t.foreign_key :testing_parents, column: :col_1 - t.foreign_key :testing_parents, column: :col_2 + test "multiple foreign keys can be removed to the selected one" do + @connection.create_table :testings do |t| + t.references :parent1, foreign_key: { to_table: :testing_parents } + t.references :parent2, foreign_key: { to_table: :testing_parents } end - fks = @connection.foreign_keys("testings") + assert_difference "@connection.foreign_keys('testings').size", -1 do + @connection.remove_reference :testings, :parent1, foreign_key: { to_table: :testing_parents } + end + + fks = @connection.foreign_keys("testings").sort_by(&:column) fk_definitions = fks.map { |fk| [fk.from_table, fk.to_table, fk.column] } - assert_equal([["testings", "testing_parents", "col_1"], - ["testings", "testing_parents", "col_2"]], fk_definitions) + assert_equal([["testings", "testing_parents", "parent2_id"]], fk_definitions) end end end diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb index df15d7cb45..06c44c8c52 100644 --- a/activerecord/test/cases/migration/references_statements_test.rb +++ b/activerecord/test/cases/migration/references_statements_test.rb @@ -50,6 +50,13 @@ module ActiveRecord assert column_exists?(table_name, :taggable_type, :string, default: "Photo") 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) + assert column_exists?(table_name, :taggable_type, :string) + assert_not column_exists?(table_name, :taggable_type, :string, limit: 2) + end + def test_creates_named_index add_reference table_name, :tag, index: { name: "index_taggings_on_tag_id" } assert index_exists?(table_name, :tag_id, name: "index_taggings_on_tag_id") diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 2fda4d60b8..fabb1662a3 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1138,4 +1138,12 @@ class CopyMigrationsTest < ActiveRecord::TestCase assert_deprecated { ActiveRecord::Base.connection.initialize_schema_migrations_table } assert_deprecated { ActiveRecord::Base.connection.initialize_internal_metadata_table } end + + def test_deprecate_migration_keys + assert_deprecated { ActiveRecord::Base.connection.migration_keys } + end + + def test_deprecate_supports_migrations + assert_deprecated { ActiveRecord::Base.connection.supports_migrations? } + end end diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb index 20d70b75ac..aadbc375af 100644 --- a/activerecord/test/cases/migrator_test.rb +++ b/activerecord/test/cases/migrator_test.rb @@ -124,6 +124,67 @@ class MigratorTest < ActiveRecord::TestCase assert_equal migration_list.last, migrations.first end + def test_migrations_status + path = MIGRATIONS_ROOT + "/valid" + + ActiveRecord::SchemaMigration.create(version: 2) + ActiveRecord::SchemaMigration.create(version: 10) + + assert_equal [ + ["down", "001", "Valid people have last names"], + ["up", "002", "We need reminders"], + ["down", "003", "Innocent jointable"], + ["up", "010", "********** NO FILE **********"], + ], ActiveRecord::Migrator.migrations_status(path) + end + + def test_migrations_status_in_subdirectories + path = MIGRATIONS_ROOT + "/valid_with_subdirectories" + + ActiveRecord::SchemaMigration.create(version: 2) + ActiveRecord::SchemaMigration.create(version: 10) + + assert_equal [ + ["down", "001", "Valid people have last names"], + ["up", "002", "We need reminders"], + ["down", "003", "Innocent jointable"], + ["up", "010", "********** NO FILE **********"], + ], ActiveRecord::Migrator.migrations_status(path) + end + + def test_migrations_status_with_schema_define_in_subdirectories + path = MIGRATIONS_ROOT + "/valid_with_subdirectories" + prev_paths = ActiveRecord::Migrator.migrations_paths + ActiveRecord::Migrator.migrations_paths = path + + ActiveRecord::Schema.define(version: 3) do + end + + assert_equal [ + ["up", "001", "Valid people have last names"], + ["up", "002", "We need reminders"], + ["up", "003", "Innocent jointable"], + ], ActiveRecord::Migrator.migrations_status(path) + ensure + ActiveRecord::Migrator.migrations_paths = prev_paths + end + + def test_migrations_status_from_two_directories + paths = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"] + + ActiveRecord::SchemaMigration.create(version: "20100101010101") + ActiveRecord::SchemaMigration.create(version: "20160528010101") + + assert_equal [ + ["down", "20090101010101", "People have hobbies"], + ["down", "20090101010202", "People have descriptions"], + ["up", "20100101010101", "Valid with timestamps people have last names"], + ["down", "20100201010101", "Valid with timestamps we need reminders"], + ["down", "20100301010101", "Valid with timestamps innocent jointable"], + ["up", "20160528010101", "********** NO FILE **********"], + ], ActiveRecord::Migrator.migrations_status(paths) + end + def test_migrator_interleaved_migrations pass_one = [Sensor.new("One", 1)] diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 1d72899102..12386635f6 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -7,6 +7,7 @@ require "models/movie" require "models/keyboard" require "models/mixed_case_monkey" require "models/dashboard" +require "models/non_primary_key" class PrimaryKeysTest < ActiveRecord::TestCase fixtures :topics, :subscribers, :movies, :mixed_case_monkeys @@ -89,6 +90,12 @@ class PrimaryKeysTest < ActiveRecord::TestCase assert_equal("John Doe", subscriberReloaded.name) end + def test_id_column_that_is_not_primary_key + NonPrimaryKey.create!(id: 100) + actual = NonPrimaryKey.find_by(id: 100) + assert_match %r{<NonPrimaryKey id: 100}, actual.inspect + end + def test_find_with_more_than_one_string_key assert_equal 2, Subscriber.find(subscribers(:first).nick, subscribers(:second).nick).length end @@ -113,38 +120,45 @@ class PrimaryKeysTest < ActiveRecord::TestCase def test_delete_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.delete(1) } end + def test_update_counters_should_quote_pkey_and_quote_counter_columns assert_nothing_raised { MixedCaseMonkey.update_counters(1, fleaCount: 99) } end + def test_find_with_one_id_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find(1) } end + def test_find_with_multiple_ids_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find([1, 2]) } end + def test_instance_update_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find(1).save } end + def test_instance_destroy_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find(1).destroy } end - if ActiveRecord::Base.connection.supports_primary_key? - def test_primary_key_returns_value_if_it_exists - klass = Class.new(ActiveRecord::Base) do - self.table_name = "developers" - end + def test_deprecate_supports_primary_key + assert_deprecated { ActiveRecord::Base.connection.supports_primary_key? } + end - assert_equal "id", klass.primary_key + def test_primary_key_returns_value_if_it_exists + klass = Class.new(ActiveRecord::Base) do + self.table_name = "developers" end - def test_primary_key_returns_nil_if_it_does_not_exist - klass = Class.new(ActiveRecord::Base) do - self.table_name = "developers_projects" - end + assert_equal "id", klass.primary_key + end - assert_nil klass.primary_key + def test_primary_key_returns_nil_if_it_does_not_exist + klass = Class.new(ActiveRecord::Base) do + self.table_name = "developers_projects" end + + assert_nil klass.primary_key end def test_quoted_primary_key_after_set_primary_key @@ -224,13 +238,13 @@ class PrimaryKeyWithAutoIncrementTest < ActiveRecord::TestCase @connection.drop_table(:auto_increments, if_exists: true) end - def test_primary_key_with_auto_increment - @connection.create_table(:auto_increments, id: :integer, auto_increment: true, force: true) + def test_primary_key_with_integer + @connection.create_table(:auto_increments, id: :integer, force: true) assert_auto_incremented end - def test_primary_key_with_auto_increment_and_bigint - @connection.create_table(:auto_increments, id: :bigint, auto_increment: true, force: true) + def test_primary_key_with_bigint + @connection.create_table(:auto_increments, id: :bigint, force: true) assert_auto_incremented end @@ -277,6 +291,14 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase schema = dump_table_schema "barcodes" assert_match %r{create_table "barcodes", primary_key: "code", id: :string, limit: 42}, schema end + + if current_adapter?(:Mysql2Adapter) && subsecond_precision_supported? + test "schema typed primary key column" do + @connection.create_table(:scheduled_logs, id: :timestamp, precision: 6, force: true) + schema = dump_table_schema("scheduled_logs") + assert_match %r/create_table "scheduled_logs", id: :timestamp, precision: 6/, schema + end + end end class CompositePrimaryKeyTest < ActiveRecord::TestCase @@ -291,6 +313,10 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase t.string :region t.integer :code end + @connection.create_table(:barcodes_reverse, primary_key: ["code", "region"], force: true) do |t| + t.string :region + t.integer :code + end end def teardown @@ -301,6 +327,11 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase assert_equal ["region", "code"], @connection.primary_keys("barcodes") end + def test_composite_primary_key_out_of_order + skip if current_adapter?(:SQLite3Adapter) + assert_equal ["code", "region"], @connection.primary_keys("barcodes_reverse") + end + def test_primary_key_issues_warning model = Class.new(ActiveRecord::Base) do def self.table_name @@ -313,76 +344,106 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase assert_match(/WARNING: Active Record does not support composite primary key\./, warning) end - def test_collectly_dump_composite_primary_key + def test_dumping_composite_primary_key schema = dump_table_schema "barcodes" assert_match %r{create_table "barcodes", primary_key: \["region", "code"\]}, schema end + + def test_dumping_composite_primary_key_out_of_order + skip if current_adapter?(:SQLite3Adapter) + schema = dump_table_schema "barcodes_reverse" + assert_match %r{create_table "barcodes_reverse", primary_key: \["code", "region"\]}, schema + end end -if current_adapter?(:Mysql2Adapter) - class PrimaryKeyIntegerNilDefaultTest < ActiveRecord::TestCase - include SchemaDumpingHelper +class PrimaryKeyIntegerNilDefaultTest < ActiveRecord::TestCase + include SchemaDumpingHelper - self.use_transactional_tests = false + self.use_transactional_tests = false - def setup - @connection = ActiveRecord::Base.connection - @connection.create_table(:int_defaults, id: :integer, default: nil, force: true) - end + def setup + @connection = ActiveRecord::Base.connection + end - def teardown - @connection.drop_table :int_defaults, if_exists: true - end + def teardown + @connection.drop_table :int_defaults, if_exists: true + end - test "primary key with integer allows default override via nil" do - column = @connection.columns(:int_defaults).find { |c| c.name == "id" } - assert_equal :integer, column.type - assert_not column.auto_increment? - end + def test_schema_dump_primary_key_integer_with_default_nil + skip if current_adapter?(:SQLite3Adapter) + @connection.create_table(:int_defaults, id: :integer, default: nil, force: true) + schema = dump_table_schema "int_defaults" + assert_match %r{create_table "int_defaults", id: :integer, default: nil}, schema + end - test "schema dump primary key with int default nil" do - schema = dump_table_schema "int_defaults" - assert_match %r{create_table "int_defaults", id: :integer, default: nil}, schema - end + def test_schema_dump_primary_key_bigint_with_default_nil + @connection.create_table(:int_defaults, id: :bigint, default: nil, force: true) + schema = dump_table_schema "int_defaults" + assert_match %r{create_table "int_defaults", id: :bigint, default: nil}, schema end end -class PrimaryKeyIntegerTest < ActiveRecord::TestCase - include SchemaDumpingHelper +if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter) + class PrimaryKeyIntegerTest < ActiveRecord::TestCase + include SchemaDumpingHelper - self.use_transactional_tests = false + self.use_transactional_tests = false - class Widget < ActiveRecord::Base - end + class Widget < ActiveRecord::Base + end - setup do - @connection = ActiveRecord::Base.connection - @connection.create_table(:widgets, force: true) - end + setup do + @connection = ActiveRecord::Base.connection + @pk_type = current_adapter?(:PostgreSQLAdapter) ? :serial : :integer + end - teardown do - @connection.drop_table :widgets, if_exists: true - Widget.reset_column_information - end + teardown do + @connection.drop_table :widgets, if_exists: true + end - if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter) - test "schema dump primary key with bigserial" do - schema = dump_table_schema "widgets" - assert_match %r{create_table "widgets", force: :cascade}, schema + test "primary key column type with serial/integer" do + @connection.create_table(:widgets, id: @pk_type, force: true) + column = @connection.columns(:widgets).find { |c| c.name == "id" } + assert_equal :integer, column.type + assert_not column.bigint? end - end - test "primary key column type" do - column_type = Widget.type_for_attribute(Widget.primary_key) - assert_equal :integer, column_type.type + test "primary key with serial/integer are automatically numbered" do + @connection.create_table(:widgets, id: @pk_type, force: true) + widget = Widget.create! + assert_not_nil widget.id + end - if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter) - assert_equal 8, column_type.limit + test "schema dump primary key with serial/integer" do + @connection.create_table(:widgets, id: @pk_type, force: true) + schema = dump_table_schema "widgets" + assert_match %r{create_table "widgets", id: :#{@pk_type}, force: :cascade}, schema end if current_adapter?(:Mysql2Adapter) - column = @connection.columns(:widgets).find { |c| c.name == "id" } - assert column.auto_increment? + test "primary key column type with options" do + @connection.create_table(:widgets, id: :primary_key, limit: 4, unsigned: true, force: true) + column = @connection.columns(:widgets).find { |c| c.name == "id" } + assert column.auto_increment? + assert_equal :integer, column.type + assert_not column.bigint? + assert column.unsigned? + + schema = dump_table_schema "widgets" + assert_match %r{create_table "widgets", id: :integer, unsigned: true, force: :cascade}, schema + end + + test "bigint primary key with unsigned" do + @connection.create_table(:widgets, id: :bigint, unsigned: true, force: true) + column = @connection.columns(:widgets).find { |c| c.name == "id" } + assert column.auto_increment? + assert_equal :integer, column.type + assert column.bigint? + assert column.unsigned? + + schema = dump_table_schema "widgets" + assert_match %r{create_table "widgets", id: :bigint, unsigned: true, force: :cascade}, schema + end end end end diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index d8cf235000..494663eb04 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -532,4 +532,16 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase end end end + + test "threads use the same connection" do + @connection_1 = ActiveRecord::Base.connection.object_id + + thread_a = Thread.new do + @connection_2 = ActiveRecord::Base.connection.object_id + end + + thread_a.join + + assert_equal @connection_1, @connection_2 + end end diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 5ff5e3c735..f260d043e4 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -82,7 +82,7 @@ module ActiveRecord end def test_quote_with_quoted_id - assert_equal 1, @quoter.quote(Struct.new(:quoted_id).new(1)) + assert_deprecated { assert_equal 1, @quoter.quote(Struct.new(:quoted_id).new(1)) } end def test_quote_nil @@ -150,6 +150,62 @@ module ActiveRecord end end + class TypeCastingTest < ActiveRecord::TestCase + def setup + @conn = ActiveRecord::Base.connection + end + + def test_type_cast_symbol + assert_equal "foo", @conn.type_cast(:foo) + end + + def test_type_cast_date + date = Date.today + expected = @conn.quoted_date(date) + assert_equal expected, @conn.type_cast(date) + end + + def test_type_cast_time + time = Time.now + expected = @conn.quoted_date(time) + assert_equal expected, @conn.type_cast(time) + end + + def test_type_cast_numeric + assert_equal 10, @conn.type_cast(10) + assert_equal 2.2, @conn.type_cast(2.2) + end + + def test_type_cast_nil + assert_nil @conn.type_cast(nil) + end + + def test_type_cast_unknown_should_raise_error + obj = Class.new.new + assert_raise(TypeError) { @conn.type_cast(obj) } + end + + def test_type_cast_object_which_responds_to_quoted_id + quoted_id_obj = Class.new { + def quoted_id + "'zomg'" + end + + def id + 10 + end + }.new + assert_equal 10, @conn.type_cast(quoted_id_obj) + + quoted_id_obj = Class.new { + def quoted_id + "'zomg'" + end + }.new + assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) } + end + end + class QuoteBooleanTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection @@ -165,5 +221,32 @@ module ActiveRecord assert_predicate @connection.type_cast(false), :frozen? end end + + if subsecond_precision_supported? + class QuoteARBaseTest < ActiveRecord::TestCase + class DatetimePrimaryKey < ActiveRecord::Base + end + + def setup + @time = ::Time.utc(2017, 2, 14, 12, 34, 56, 789999) + @connection = ActiveRecord::Base.connection + @connection.create_table :datetime_primary_keys, id: :datetime, precision: 3, force: true + end + + def teardown + @connection.drop_table :datetime_primary_keys, if_exists: true + end + + def test_quote_ar_object + value = DatetimePrimaryKey.new(id: @time) + assert_equal "'2017-02-14 12:34:56.789000'", @connection.quote(value) + end + + def test_type_cast_ar_object + value = DatetimePrimaryKey.new(id: @time) + assert_equal "2017-02-14 12:34:56.789000", @connection.type_cast(value) + end + end + end end end diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb index d2382b9bb2..49d4aeafc9 100644 --- a/activerecord/test/cases/relation/delegation_test.rb +++ b/activerecord/test/cases/relation/delegation_test.rb @@ -32,7 +32,8 @@ module ActiveRecord :exclude?, :find_all, :flat_map, :group_by, :include?, :length, :map, :none?, :one?, :partition, :reject, :reverse, :sample, :second, :sort, :sort_by, :third, - :to_ary, :to_set, :to_xml, :to_yaml, :join + :to_ary, :to_set, :to_xml, :to_yaml, :join, + :in_groups, :in_groups_of, :to_sentence, :to_formatted_s ] ARRAY_DELEGATES.each do |method| diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index dc6311e8bc..0c94e891eb 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1935,6 +1935,18 @@ class RelationTest < ActiveRecord::TestCase assert !Post.all.respond_to?(:by_lifo) end + def test_unscope_with_subquery + p1 = Post.where(id: 1) + p2 = Post.where(id: 2) + + assert_not_equal p1, p2 + + comments = Comment.where(post: p1).unscope(where: :post_id).where(post: p2) + + assert_not_equal p1.first.comments, comments + assert_equal p2.first.comments, comments + end + def test_unscope_removes_binds left = Post.where(id: Arel::Nodes::BindParam.new) column = Post.columns_hash["id"] diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 23bcb0af1e..72f09186e2 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -152,11 +152,15 @@ class SanitizeTest < ActiveRecord::TestCase end def test_bind_record - o = Struct.new(:quoted_id).new(1) - assert_equal "1", bind("?", o) + o = Class.new { + def quoted_id + 1 + end + }.new + assert_deprecated { assert_equal "1", bind("?", o) } os = [o] * 3 - assert_equal "1,1,1", bind("?", os) + assert_deprecated { assert_equal "1,1,1", bind("?", os) } end def test_named_bind_with_postgresql_type_casts diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 34c5f356b8..fccba4738f 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -178,24 +178,20 @@ class SchemaDumperTest < ActiveRecord::TestCase end def test_schema_dumps_index_columns_in_right_order - index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_index/).first.strip + index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_index/).first.strip if current_adapter?(:PostgreSQLAdapter) - assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }, using: :btree', index_definition + assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }', index_definition elsif current_adapter?(:Mysql2Adapter) - assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }, using: :btree', index_definition + assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }', index_definition else assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition end end def test_schema_dumps_partial_indices - index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_partial_index/).first.strip - if current_adapter?(:PostgreSQLAdapter) - assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition - elsif current_adapter?(:Mysql2Adapter) - assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition - elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index? - assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition + index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_partial_index/).first.strip + if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index? + assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)"', index_definition else assert_equal 't.index ["firm_id", "type"], name: "company_partial_index"', index_definition end @@ -248,9 +244,9 @@ class SchemaDumperTest < ActiveRecord::TestCase end def test_schema_dumps_index_type - output = standard_dump - assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output - assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output + output = dump_table_schema "key_tests" + assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext$}, output + assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza"$}, output end end @@ -261,23 +257,34 @@ class SchemaDumperTest < ActiveRecord::TestCase if current_adapter?(:PostgreSQLAdapter) def test_schema_dump_includes_bigint_default - output = standard_dump + output = dump_table_schema "defaults" assert_match %r{t\.bigint\s+"bigint_default",\s+default: 0}, output end def test_schema_dump_includes_limit_on_array_type - output = standard_dump + output = dump_table_schema "bigint_array" assert_match %r{t\.bigint\s+"big_int_data_points\",\s+array: true}, output end def test_schema_dump_allows_array_of_decimal_defaults - output = standard_dump + output = dump_table_schema "bigint_array" assert_match %r{t\.decimal\s+"decimal_array_default",\s+default: \["1.23", "3.45"\],\s+array: true}, output end def test_schema_dump_expression_indices - index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_expression_index/).first.strip - assert_equal 't.index "lower((name)::text)", name: "company_expression_index", using: :btree', index_definition + index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip + assert_equal 't.index "lower((name)::text)", name: "company_expression_index"', index_definition + end + + def test_schema_dump_interval_type + output = dump_table_schema "postgresql_times" + assert_match %r{t\.interval\s+"time_interval"$}, output + assert_match %r{t\.interval\s+"scaled_time_interval",\s+precision: 6$}, output + end + + def test_schema_dump_oid_type + output = dump_table_schema "postgresql_oids" + assert_match %r{t\.oid\s+"obj_id"$}, output end if ActiveRecord::Base.connection.supports_extensions? @@ -341,7 +348,7 @@ class SchemaDumperTest < ActiveRecord::TestCase create_table("dogs") do |t| t.column :name, :string - t.column :owner_id, :bigint + t.references :owner t.index [:name] t.foreign_key :dog_owners, column: "owner_id" end @@ -415,11 +422,12 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase setup do @connection = ActiveRecord::Base.connection - @connection.create_table :defaults, force: true do |t| + @connection.create_table :dump_defaults, force: true do |t| t.string :string_with_default, default: "Hello!" t.date :date_with_default, default: "2014-06-05" t.datetime :datetime_with_default, default: "2014-06-05 07:17:04" t.time :time_with_default, default: "07:17:04" + t.decimal :decimal_with_default, default: "1234567890.0123456789", precision: 20, scale: 10 end if current_adapter?(:PostgreSQLAdapter) @@ -431,17 +439,17 @@ class SchemaDumperDefaultsTest < ActiveRecord::TestCase end teardown do - return unless @connection - @connection.drop_table "defaults", if_exists: true + @connection.drop_table "dump_defaults", if_exists: true end def test_schema_dump_defaults_with_universally_supported_types - output = dump_table_schema("defaults") + output = dump_table_schema("dump_defaults") assert_match %r{t\.string\s+"string_with_default",.*?default: "Hello!"}, output - assert_match %r{t\.date\s+"date_with_default",\s+default: '2014-06-05'}, output - assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: '2014-06-05 07:17:04'}, output - assert_match %r{t\.time\s+"time_with_default",\s+default: '2000-01-01 07:17:04'}, output + assert_match %r{t\.date\s+"date_with_default",\s+default: "2014-06-05"}, output + assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: "2014-06-05 07:17:04"}, output + assert_match %r{t\.time\s+"time_with_default",\s+default: "2000-01-01 07:17:04"}, output + assert_match %r{t\.decimal\s+"decimal_with_default",\s+precision: 20,\s+scale: 10,\s+default: "1234567890.0123456789"}, output end def test_schema_dump_with_float_column_infinity_default diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 3a04f4bf7d..14fb2fbbfa 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -10,6 +10,8 @@ require "concurrent/atomic/cyclic_barrier" class DefaultScopingTest < ActiveRecord::TestCase fixtures :developers, :posts, :comments + self.use_transactional_tests = false + def test_default_scope expected = Developer.all.merge!(order: "salary DESC").to_a.collect(&:salary) received = DeveloperOrderedBySalary.all.collect(&:salary) diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 995ff4dfc5..d261fd5321 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -161,7 +161,7 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_first_and_last_should_allow_integers_for_limit - assert_equal Topic.base.first(2), Topic.base.to_a.first(2) + assert_equal Topic.base.first(2), Topic.base.order("id").to_a.first(2) assert_equal Topic.base.last(2), Topic.base.order("id").to_a.last(2) end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 24b83691f6..673392b4c4 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -250,7 +250,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase error = assert_raise(ActiveRecord::SerializationTypeMismatch) do topic.content end - expected = "Attribute `content` was supposed to be a Array, but was a Hash. -- {:zomg=>true}" + expected = "can't load `content`: was supposed to be a Array, but was a Hash. -- {:zomg=>true}" assert_equal expected, error.to_s end diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb index f45f63c68e..fab3648564 100644 --- a/activerecord/test/cases/statement_cache_test.rb +++ b/activerecord/test/cases/statement_cache_test.rb @@ -105,5 +105,31 @@ module ActiveRecord refute_equal book, other_book end + + def test_find_by_does_not_use_statement_cache_if_table_name_is_changed + book = Book.create(name: "my book") + + Book.find_by(name: book.name) # warming the statement cache. + + # changing the table name should change the query that is not cached. + Book.table_name = :birds + assert_nil Book.find_by(name: book.name) + ensure + Book.table_name = :books + end + + def test_find_does_not_use_statement_cache_if_table_name_is_changed + book = Book.create(name: "my book") + + Book.find(book.id) # warming the statement cache. + + # changing the table name should change the query that is not cached. + Book.table_name = :birds + assert_raise ActiveRecord::RecordNotFound do + Book.find(book.id) + end + ensure + Book.table_name = :books + end end end diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 391bbe8877..eaa4dd09a9 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -551,3 +551,43 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase assert_equal [:rollback], @topic.history end end + +class CallbacksOnActionAndConditionTest < ActiveRecord::TestCase + self.use_transactional_tests = false + + class TopicWithCallbacksOnActionAndCondition < ActiveRecord::Base + self.table_name = :topics + + after_commit(on: [:create, :update], if: :run_callback?) { |record| record.history << :create_or_update } + + def clear_history + @history = [] + end + + def history + @history ||= [] + end + + def run_callback? + self.history << :run_callback? + true + end + + attr_accessor :save_before_commit_history, :update_title + end + + def test_callback_on_action_with_condition + topic = TopicWithCallbacksOnActionAndCondition.new + topic.save + assert_equal [:run_callback?, :create_or_update], topic.history + + topic.clear_history + topic.approved = true + topic.save + assert_equal [:run_callback?, :create_or_update], topic.history + + topic.clear_history + topic.destroy + assert_equal [], topic.history + end +end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 8f9980f168..111495c481 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -206,16 +206,6 @@ class TransactionTest < ActiveRecord::TestCase assert_equal posts_count, author.posts.reload.size end - def test_cancellation_from_returning_false_in_before_filter - def @first.before_save_for_transaction - false - end - - assert_deprecated do - @first.save - end - end - def test_cancellation_from_before_destroy_rollbacks_in_destroy add_cancelling_before_destroy_with_db_side_effect_to_topic @first nbooks_before_destroy = Book.count diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 50e855941b..28605d2f8e 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -372,7 +372,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase e2 = Event.create(title: "abcdefgh") assert_not e2.valid?, "Created an event whose title is not unique" - elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter) + elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter) assert_raise(ActiveRecord::ValueTooLong) do Event.create(title: "abcdefgh") end @@ -391,7 +391,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase e2 = Event.create(title: "一二三四五六七八") assert_not e2.valid?, "Created an event whose title is not unique" - elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter) + elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter) assert_raise(ActiveRecord::ValueTooLong) do Event.create(title: "一二三四五六七八") end diff --git a/activerecord/test/fixtures/subscribers.yml b/activerecord/test/fixtures/subscribers.yml index c6a8c2fa24..0f6e0cd48e 100644 --- a/activerecord/test/fixtures/subscribers.yml +++ b/activerecord/test/fixtures/subscribers.yml @@ -6,6 +6,6 @@ second: nick: webster132 name: David Heinemeier Hansson -thrid: +third: nick: swistak name: Marcin Raczkowski
\ No newline at end of file diff --git a/activerecord/test/models/non_primary_key.rb b/activerecord/test/models/non_primary_key.rb new file mode 100644 index 0000000000..1cafb09608 --- /dev/null +++ b/activerecord/test/models/non_primary_key.rb @@ -0,0 +1,2 @@ +class NonPrimaryKey < ActiveRecord::Base +end diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index 5009f8f54b..4fbd986e40 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -11,7 +11,7 @@ class Project < ActiveRecord::Base after_add: Proc.new { |o, r| o.developers_log << "after_adding#{r.id || '<new>'}" }, before_remove: Proc.new { |o, r| o.developers_log << "before_removing#{r.id}" }, after_remove: Proc.new { |o, r| o.developers_log << "after_removing#{r.id}" } - has_and_belongs_to_many :well_payed_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, class_name: "Developer" + has_and_belongs_to_many :well_paid_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, class_name: "Developer" belongs_to :firm has_one :lead_developer, through: :firm, inverse_of: :contracted_projects diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index 9a203a7293..90a314c83c 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -6,6 +6,11 @@ ActiveRecord::Schema.define do end end + create_table :timestamp_defaults, force: true do |t| + t.timestamp :nullable_timestamp + t.timestamp :modified_timestamp, default: -> { "CURRENT_TIMESTAMP" } + end + create_table :binary_fields, force: true do |t| t.binary :var_binary, limit: 255 t.binary :var_binary_large, limit: 4095 diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 15ba2d67ab..860c63b27c 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -23,16 +23,24 @@ ActiveRecord::Schema.define do t.string :char2, limit: 50, default: "a varchar field" t.text :char3, default: "a text field" t.bigint :bigint_default, default: -> { "0::bigint" } - t.text :multiline_default, default: '--- [] + t.text :multiline_default, default: "--- [] -' +" end - %w(postgresql_times postgresql_oids postgresql_timestamp_with_zones - postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name| - drop_table table_name, if_exists: true + create_table :postgresql_times, force: true do |t| + t.interval :time_interval + t.interval :scaled_time_interval, precision: 6 end + create_table :postgresql_oids, force: true do |t| + t.oid :obj_id + end + + drop_table "postgresql_timestamp_with_zones", if_exists: true + drop_table "postgresql_partitioned_table", if_exists: true + drop_table "postgresql_partitioned_table_parent", if_exists: true + execute "DROP SEQUENCE IF EXISTS companies_nonstd_seq CASCADE" execute "CREATE SEQUENCE companies_nonstd_seq START 101 OWNED BY companies.id" execute "ALTER TABLE companies ALTER COLUMN id SET DEFAULT nextval('companies_nonstd_seq')" @@ -45,21 +53,6 @@ ActiveRecord::Schema.define do end execute <<_SQL - CREATE TABLE postgresql_times ( - id SERIAL PRIMARY KEY, - time_interval INTERVAL, - scaled_time_interval INTERVAL(6) - ); -_SQL - - execute <<_SQL - CREATE TABLE postgresql_oids ( - id SERIAL PRIMARY KEY, - obj_id OID - ); -_SQL - - execute <<_SQL CREATE TABLE postgresql_timestamp_with_zones ( id SERIAL PRIMARY KEY, time TIMESTAMP WITH TIME ZONE diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 4c31548fff..08bef08abc 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -54,8 +54,8 @@ ActiveRecord::Schema.define do create_table :authors, force: true do |t| t.string :name, null: false - t.bigint :author_address_id - t.integer :author_address_extra_id + t.references :author_address + t.references :author_address_extra t.string :organization_id t.string :owned_essay_id end @@ -88,7 +88,7 @@ ActiveRecord::Schema.define do end create_table :books, force: true do |t| - t.integer :author_id + t.references :author t.string :format t.column :name, :string t.column :status, :integer, default: 0 @@ -201,7 +201,7 @@ ActiveRecord::Schema.define do t.integer :account_id t.string :description, default: "" t.index [:firm_id, :type, :rating], name: "company_index", length: { type: 10 }, order: { rating: :desc } - t.index [:firm_id, :type], name: "company_partial_index", where: "rating > 10" + t.index [:firm_id, :type], name: "company_partial_index", where: "(rating > 10)" t.index :name, name: "company_name_index", using: :btree t.index "lower(name)", name: "company_expression_index" if supports_expression_index? end @@ -306,7 +306,7 @@ ActiveRecord::Schema.define do end create_table :engines, force: true do |t| - t.bigint :car_id + t.references :car, index: false end create_table :entrants, force: true do |t| @@ -664,8 +664,8 @@ ActiveRecord::Schema.define do end create_table :posts, force: true do |t| - t.integer :author_id - t.string :title, null: false + t.references :author + t.string :title, null: false # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in # Oracle SELECT WHERE clause which causes many unit test failures if current_adapter?(:OracleAdapter) @@ -1039,6 +1039,10 @@ ActiveRecord::Schema.define do create_table :test_with_keyword_column_name, force: true do |t| t.string :desc end + + create_table :non_primary_keys, force: true, id: false do |t| + t.integer :id + end end Course.connection.create_table :courses, force: true do |t| diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb index bc5af36a28..1a609e13c3 100644 --- a/activerecord/test/support/connection.rb +++ b/activerecord/test/support/connection.rb @@ -10,7 +10,10 @@ module ARTest end def self.connection_config - config["connections"][connection_name] + config.fetch("connections").fetch(connection_name) do + puts "Connection #{connection_name.inspect} not found. Available connections: #{config['connections'].keys.join(', ')}" + exit 1 + end end def self.connect |