diff options
Diffstat (limited to 'activerecord')
30 files changed, 303 insertions, 128 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index fb6c5e7e81..9144ab6695 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,13 @@ +* When calling `first` with a `limit` argument, return directly from the + `loaded?` records if available. + + *Ben Woosley* + +* Deprecate sending the `offset` argument to `find_nth`. Please use the + `offset` method on relation instead. + + *Ben Woosley* + ## Rails 5.0.0.beta1 (December 18, 2015) ## * Order the result of `find(ids)` to match the passed array, if the relation @@ -50,7 +60,7 @@ change, passing a string containing a comma to `limit` has been deprecated, and passing an Arel node to `limit` is no longer supported. - Fixes #22250 + Fixes #22250. *Sean Griffin* @@ -312,7 +322,7 @@ * Don't cache arguments in `#find_by` if they are an `ActiveRecord::Relation`. - Fixes #20817 + Fixes #20817. *Hiroaki Izu* @@ -441,9 +451,9 @@ * Ensure `select` quotes aliased attributes, even when using `from`. - Fixes #21488 + Fixes #21488. - *Sean Griffin & @johanlunds* + *Sean Griffin*, *@johanlunds* * MySQL: support `unsigned` numeric data types. @@ -769,7 +779,7 @@ * Include the `Enumerable` module in `ActiveRecord::Relation` - *Sean Griffin & bogdan* + *Sean Griffin*, *bogdan* * Use `Enumerable#sum` in `ActiveRecord::Relation` if a block is given. @@ -805,7 +815,7 @@ Fixes #20515. - *Sean Griffin & jmondo* + *Sean Griffin*, *jmondo* * Deprecate the PostgreSQL `:point` type in favor of a new one which will return `Point` objects instead of an `Array` @@ -1195,13 +1205,16 @@ *Sean Griffin* * `scoping` no longer pollutes the current scope of sibling classes when using - STI. e.x. + STI. + + Fixes #18806. + + Example: StiOne.none.scoping do StiTwo.all end - Fixes #18806. *Sean Griffin* @@ -1242,7 +1255,7 @@ * Use `SCHEMA` instead of `DB_STRUCTURE` for specifying a structure file. - This makes the db:structure tasks consistent with test:load_structure. + This makes the `db:structure` tasks consistent with `test:load_structure`. *Dieter Komendera* @@ -1278,7 +1291,7 @@ Fixes #17621. - *Eileen M. Uchitelle, Aaron Patterson* + *Eileen M. Uchitelle*, *Aaron Patterson* * Fix n+1 query problem when eager loading nil associations (fixes #18312) diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE index 7c2197229d..1f496cf280 100644 --- a/activerecord/MIT-LICENSE +++ b/activerecord/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2015 David Heinemeier Hansson +Copyright (c) 2004-2016 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 264f869c68..f35e1d889e 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2015 David Heinemeier Hansson +# Copyright (c) 2004-2016 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index fc12c3f45a..bac5a38a5d 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -331,15 +331,30 @@ module ActiveRecord validation_context = self.validation_context unless [:create, :update].include?(self.validation_context) unless valid = record.valid?(validation_context) if reflection.options[:autosave] + indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors) + record.errors.each do |attribute, message| - if index.nil? || (!reflection.options[:index_errors] && !ActiveRecord::Base.index_nested_attribute_errors) - attribute = "#{reflection.name}.#{attribute}" - else + if indexed_attribute attribute = "#{reflection.name}[#{index}].#{attribute}" + else + attribute = "#{reflection.name}.#{attribute}" end errors[attribute] << message errors[attribute].uniq! end + + record.errors.details.each_key do |attribute| + if indexed_attribute + reflection_attribute = "#{reflection.name}[#{index}].#{attribute}" + else + reflection_attribute = "#{reflection.name}.#{attribute}" + end + + record.errors.details[attribute].each do |error| + errors.details[reflection_attribute] << error + errors.details[reflection_attribute].uniq! + end + end else errors.add(reflection.name) end 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 7bf548fcba..a918a8b035 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1028,11 +1028,12 @@ module ActiveRecord end # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT. - # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax - they + # PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they # require the order columns appear in the SELECT. # # columns_for_distinct("posts.id", ["posts.created_at desc"]) - def columns_for_distinct(columns, orders) #:nodoc: + # + def columns_for_distinct(columns, orders) # :nodoc: columns end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 3b8d1c1a9f..9cbc973f2e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -391,19 +391,17 @@ module ActiveRecord def release_savepoint(name = nil) end - def case_sensitive_modifier(node, table_attribute) - node - end - def case_sensitive_comparison(table, attribute, column, value) - table_attr = table[attribute] - value = case_sensitive_modifier(value, table_attr) unless value.nil? - table_attr.eq(value) + if value.nil? + table[attribute].eq(value) + else + table[attribute].eq(Arel::Nodes::BindParam.new) + end end def case_insensitive_comparison(table, attribute, column, value) if can_perform_case_insensitive_comparison_for?(column) - table[attribute].lower.eq(table.lower(value)) + table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new)) else case_sensitive_comparison(table, attribute, column, value) 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 5ebd8a8dcb..f5b5482efb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -31,8 +31,6 @@ module ActiveRecord def extract_default if blob_or_text_column? @default = null || strict ? nil : '' - elsif missing_default_forged_as_empty_string?(default) - @default = nil end end @@ -59,17 +57,6 @@ module ActiveRecord private - # MySQL misreports NOT NULL column default when none is given. - # We can't detect this for columns which may have a legitimate '' - # default (string) but we can for others (integer, datetime, boolean, - # and the rest). - # - # Test whether the column has default '', is not null, and is not - # a type allowing default ''. - def missing_default_forged_as_empty_string?(default) - type != :string && !null && default == '' - end - def assert_valid_default(default) if blob_or_text_column? && default.present? raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}" @@ -636,6 +623,7 @@ module ActiveRecord # it can be helpful to provide these in a migration's +change+ method so it can be reverted. # In that case, +options+ and the block will be used by create_table. def drop_table(table_name, options = {}) + create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name) execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end @@ -764,16 +752,11 @@ module ActiveRecord SQL end - def case_sensitive_modifier(node, table_attribute) - node = Arel::Nodes.build_quoted node, table_attribute - Arel::Nodes::Bin.new(node) - end - def case_sensitive_comparison(table, attribute, column, value) - if column.case_sensitive? - table[attribute].eq(value) - else + if value.nil? || column.case_sensitive? super + else + table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new)) end end @@ -781,10 +764,25 @@ module ActiveRecord if column.case_sensitive? super else - table[attribute].eq(value) + table[attribute].eq(Arel::Nodes::BindParam.new) end end + # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use + # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for + # distinct queries, and requires that the ORDER BY include the distinct column. + # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html + def columns_for_distinct(columns, orders) # :nodoc: + order_columns = orders.reject(&:blank?).map { |s| + # Convert Arel node to string + s = s.to_sql unless s.is_a?(String) + # Remove any ASC/DESC modifiers + s.gsub(/\s+(?:ASC|DESC)\b/i, '') + }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" } + + [super, *order_columns].join(', ') + end + def strict_mode? self.class.type_cast_config_to_boolean(@config.fetch(:strict, true)) end @@ -964,11 +962,13 @@ module ActiveRecord subsubselect = select.clone subsubselect.projections = [key] + # Materialize subquery by adding distinct + # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' + subsubselect.distinct unless select.limit || select.offset || select.orders.any? + subselect = Arel::SelectManager.new(select.engine) subselect.project Arel.sql(key.name) - # Materialized subquery by adding distinct - # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' - subselect.from subsubselect.distinct.as('__active_record_temp') + subselect.from subsubselect.as('__active_record_temp') end def mariadb? @@ -1031,9 +1031,12 @@ module ActiveRecord end end + def create_table_info_cache # :nodoc: + @create_table_info_cache ||= {} + end + def create_table_info(table_name) # :nodoc: - @create_table_info_cache = {} - @create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] + create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] end def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index f4686b680c..96a3a44b30 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -99,33 +99,15 @@ module ActiveRecord # DATABASE STATEMENTS ====================================== #++ - # FIXME: re-enable the following once a "better" query_cache solution is in core - # - # The overrides below perform much better than the originals in AbstractAdapter - # because we're able to take advantage of mysql2's lazy-loading capabilities - # - # # Returns a record hash with the column names as keys and column values - # # as values. - # def select_one(sql, name = nil) - # result = execute(sql, name) - # result.each(as: :hash) do |r| - # return r - # end - # end - # - # # Returns a single value from a record - # def select_value(sql, name = nil) - # result = execute(sql, name) - # if first = result.first - # first.first - # end - # end - # - # # Returns an array of the values of the first column in a select: - # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3] - # def select_values(sql, name = nil) - # execute(sql, name).map { |row| row.first } - # end + # Returns a record hash with the column names as keys and column values + # as values. + def select_one(arel, name = nil, binds = []) + arel, binds = binds_from_relation(arel, binds) + execute(to_sql(arel, binds), name).each(as: :hash) do |row| + @connection.next_result while @connection.more_results? + return row + end + end # Returns an array of arrays containing the field values. # Order is the same as that returned by +columns+. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index a1ec570042..e313a34c3a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -390,12 +390,12 @@ module ActiveRecord "average" => "avg", } - protected + # Returns the version of the connected PostgreSQL server. + def postgresql_version + @connection.server_version + end - # Returns the version of the connected PostgreSQL server. - def postgresql_version - @connection.server_version - end + protected # See http://www.postgresql.org/docs/current/static/errcodes-appendix.html FOREIGN_KEY_VIOLATION = "23503" diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 7ded96f8fb..04519f4dc3 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -124,7 +124,7 @@ module ActiveRecord def deserialize(value) return if value.nil? - mapping.key(value.to_i) + mapping.key(value) end def serialize(value) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index ba238ea142..8c9eab436d 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -126,9 +126,9 @@ module ActiveRecord class PendingMigrationError < MigrationError#:nodoc: def initialize(message = nil) if !message && defined?(Rails.env) - super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate RAILS_ENV=#{::Rails.env}.") + super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate RAILS_ENV=#{::Rails.env}") elsif !message - super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate.") + super("Migrations are pending. To resolve this issue, run:\n\n\tbin/rails db:migrate") else super end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 19244bcf95..3cbb12a09d 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -117,9 +117,9 @@ module ActiveRecord # def first(limit = nil) if limit - find_nth_with_limit(offset_index, limit) + find_nth_with_limit_and_offset(0, limit, offset: offset_index) else - find_nth(0, offset_index) + find_nth 0 end end @@ -169,7 +169,7 @@ module ActiveRecord # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4) # Person.where(["user_name = :u", { u: user_name }]).second def second - find_nth(1, offset_index) + find_nth 1 end # Same as #second but raises ActiveRecord::RecordNotFound if no record @@ -185,7 +185,7 @@ module ActiveRecord # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5) # Person.where(["user_name = :u", { u: user_name }]).third def third - find_nth(2, offset_index) + find_nth 2 end # Same as #third but raises ActiveRecord::RecordNotFound if no record @@ -201,7 +201,7 @@ module ActiveRecord # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6) # Person.where(["user_name = :u", { u: user_name }]).fourth def fourth - find_nth(3, offset_index) + find_nth 3 end # Same as #fourth but raises ActiveRecord::RecordNotFound if no record @@ -217,7 +217,7 @@ module ActiveRecord # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7) # Person.where(["user_name = :u", { u: user_name }]).fifth def fifth - find_nth(4, offset_index) + find_nth 4 end # Same as #fifth but raises ActiveRecord::RecordNotFound if no record @@ -233,7 +233,7 @@ module ActiveRecord # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44) # Person.where(["user_name = :u", { u: user_name }]).forty_two def forty_two - find_nth(41, offset_index) + find_nth 41 end # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record @@ -488,27 +488,39 @@ module ActiveRecord end end - def find_nth(index, offset) + def find_nth(index, offset = nil) if loaded? @records[index] else - offset += index - @offsets[offset] ||= find_nth_with_limit(offset, 1).first + # TODO: once the offset argument is removed we rely on offset_index + # within find_nth_with_limit, rather than pass it in via + # find_nth_with_limit_and_offset + if offset + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing an offset argument to find_nth is deprecated, + please use Relation#offset instead. + MSG + else + offset = offset_index + end + @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first end end def find_nth!(index) - find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") + find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") end - def find_nth_with_limit(offset, limit) + def find_nth_with_limit(index, limit) + # TODO: once the offset argument is removed from find_nth, + # find_nth_with_limit_and_offset can be merged into this method relation = if order_values.empty? && primary_key order(arel_table[primary_key].asc) else self end - relation = relation.offset(offset) unless offset.zero? + relation = relation.offset(index) unless index.zero? relation.limit(limit).to_a end @@ -524,5 +536,16 @@ module ActiveRecord end end end + + private + + def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc: + if loaded? + @records[index, limit] + else + index += offset + find_nth_with_limit(index, limit) + end + end end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 983bf019bc..b86cc094fc 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -649,8 +649,8 @@ module ActiveRecord # they must differ only by #where (if no #group has been defined) or #having (if a #group is # present). Neither relation may have a #limit, #offset, or #distinct set. # - # Post.where("id = 1").or(Post.where("id = 2")) - # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2')) + # Post.where("id = 1").or(Post.where("author_id = 3")) + # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'author_id = 3')) # def or(other) spawn.or!(other) diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index a81ff98e49..dbf172a577 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -22,6 +22,7 @@ module ActiveRecord parts = predicate_builder.build_from_hash(attributes) when Arel::Nodes::Node parts = [opts] + binds = other else raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})" end diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 4e89ba4dd1..2bfc5ff7ae 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -213,7 +213,7 @@ module ActiveRecord end # TODO: Deprecate this - def quoted_id + def quoted_id # :nodoc: self.class.quote_value(@attributes[self.class.primary_key].value_for_database) end end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index edc1325b25..a376e2a17f 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -73,15 +73,18 @@ module ActiveRecord value = value.to_s[0, column.limit] end - value = Arel::Nodes::Quoted.new(value) - comparison = if !options[:case_sensitive] && !value.nil? # 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.where(comparison) + if value.nil? + klass.unscoped.where(comparison) + else + bind = Relation::QueryAttribute.new(attribute.to_s, value, Type::Value.new) + klass.unscoped.where(comparison, bind) + end rescue RangeError klass.none end diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb index 15aecf28ca..7395839fca 100644 --- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb +++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb @@ -45,7 +45,13 @@ module ActiveRecord def determine_default_parent_class application_record = nil - in_root { application_record = File.exist?('app/models/application_record.rb') } + in_root do + application_record = if mountable_engine? + File.exist?("app/models/#{namespaced_path}/application_record.rb") + else + File.exist?('app/models/application_record.rb') + end + end if application_record "ApplicationRecord" diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb new file mode 100644 index 0000000000..4efd728754 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb @@ -0,0 +1,44 @@ +require "cases/helper" + +class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase + def setup + @conn = ActiveRecord::Base.connection + end + + def test_columns_for_distinct_zero_orders + assert_equal "posts.id", + @conn.columns_for_distinct("posts.id", []) + end + + def test_columns_for_distinct_one_order + assert_equal "posts.id, posts.created_at AS alias_0", + @conn.columns_for_distinct("posts.id", ["posts.created_at desc"]) + end + + def test_columns_for_distinct_few_orders + assert_equal "posts.id, posts.created_at AS alias_0, posts.position AS alias_1", + @conn.columns_for_distinct("posts.id", ["posts.created_at desc", "posts.position asc"]) + end + + def test_columns_for_distinct_with_case + assert_equal( + 'posts.id, CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END AS alias_0', + @conn.columns_for_distinct('posts.id', + ["CASE WHEN author.is_active THEN UPPER(author.name) ELSE UPPER(author.email) END"]) + ) + end + + def test_columns_for_distinct_blank_not_nil_orders + assert_equal "posts.id, posts.created_at AS alias_0", + @conn.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "]) + end + + def test_columns_for_distinct_with_arel_order + order = Object.new + def order.to_sql + "posts.created_at desc" + end + assert_equal "posts.id, posts.created_at AS alias_0", + @conn.columns_for_distinct("posts.id", [order]) + end +end diff --git a/activerecord/test/cases/adapters/mysql2/sp_test.rb b/activerecord/test/cases/adapters/mysql2/sp_test.rb index cdaa2cca44..4197ba45f1 100644 --- a/activerecord/test/cases/adapters/mysql2/sp_test.rb +++ b/activerecord/test/cases/adapters/mysql2/sp_test.rb @@ -22,6 +22,12 @@ class Mysql2StoredProcedureTest < ActiveRecord::Mysql2TestCase assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_rows'" end + def test_multi_results_from_select_one + row = @connection.select_one('CALL topics(1);') + assert_equal 'David', row['author_name'] + assert @connection.active?, "Bad connection use by 'Mysql2Adapter.select_one'" + end + def test_multi_results_from_find_by_sql topics = Topic.find_by_sql 'CALL topics(3);' assert_equal 3, topics.size diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 049ed1732e..3d90790367 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -146,8 +146,6 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase end setup do - enable_extension!('uuid-ossp', connection) - connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t| t.string 'name' t.uuid 'other_uuid', default: 'uuid_generate_v4()' @@ -172,7 +170,6 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase drop_table "pg_uuids" drop_table 'pg_uuids_2' connection.execute 'DROP FUNCTION IF EXISTS my_uuid_generator();' - disable_extension!('uuid-ossp', connection) end if ActiveRecord::Base.connection.supports_extensions? @@ -217,8 +214,6 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper setup do - enable_extension!('uuid-ossp', connection) - connection.create_table('pg_uuids', id: false) do |t| t.primary_key :id, :uuid, default: nil t.string 'name' @@ -227,7 +222,6 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase teardown do drop_table "pg_uuids" - disable_extension!('uuid-ossp', connection) end if ActiveRecord::Base.connection.supports_extensions? @@ -260,8 +254,6 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase end setup do - enable_extension!('uuid-ossp', connection) - connection.transaction do connection.create_table('pg_uuid_posts', id: :uuid) do |t| t.string 'title' @@ -276,7 +268,6 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase teardown do drop_table "pg_uuid_comments" drop_table "pg_uuid_posts" - disable_extension!('uuid-ossp', connection) end if ActiveRecord::Base.connection.supports_extensions? diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 0df8f1f798..3608063b01 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -433,6 +433,53 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config end + def test_errors_details_should_be_set + molecule = Molecule.new + valid_electron = Electron.new(name: 'electron') + invalid_electron = Electron.new + + molecule.electrons = [valid_electron, invalid_electron] + + assert_not invalid_electron.valid? + assert valid_electron.valid? + assert_not molecule.valid? + assert_equal [{error: :blank}], molecule.errors.details["electrons.name"] + end + + def test_errors_details_should_be_indexed_when_passed_as_array + guitar = Guitar.new + tuning_peg_valid = TuningPeg.new + tuning_peg_valid.pitch = 440.0 + tuning_peg_invalid = TuningPeg.new + + guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid] + + assert_not tuning_peg_invalid.valid? + assert tuning_peg_valid.valid? + assert_not guitar.valid? + assert_equal [{error: :not_a_number, value: nil}] , guitar.errors.details["tuning_pegs[1].pitch"] + assert_equal [], guitar.errors.details["tuning_pegs.pitch"] + end + + def test_errors_details_should_be_indexed_when_global_flag_is_set + old_attribute_config = ActiveRecord::Base.index_nested_attribute_errors + ActiveRecord::Base.index_nested_attribute_errors = true + + molecule = Molecule.new + valid_electron = Electron.new(name: 'electron') + invalid_electron = Electron.new + + molecule.electrons = [valid_electron, invalid_electron] + + assert_not invalid_electron.valid? + assert valid_electron.valid? + assert_not molecule.valid? + assert_equal [{error: :blank}], molecule.errors.details["electrons[1].name"] + assert_equal [], molecule.errors.details["electrons.name"] + ensure + ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config + end + def test_valid_adding_with_nested_attributes molecule = Molecule.new valid_electron = Electron.new(name: 'electron') diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index f343a15317..ba3e16bdb2 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1252,6 +1252,7 @@ class BasicsTest < ActiveRecord::TestCase original_logger = ActiveRecord::Base.logger log = StringIO.new ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) + ActiveRecord::Base.logger.level = Logger::DEBUG ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false) { ActiveRecord::Base.logger.debug "Quiet" } assert_match(/Quiet/, log.string) ensure diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index da0d7f5195..783a374116 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -49,6 +49,16 @@ module ActiveRecord 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 = AbstractMysqlAdapter::Column.new("title", "", type, false) + assert_equal "", binary_column.default + + type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary") + varbinary_column = AbstractMysqlAdapter::Column.new("title", "", type, false) + assert_equal "", varbinary_column.default + end + def test_should_not_set_default_for_blob_and_text_data_types assert_raise ArgumentError do AbstractMysqlAdapter::Column.new("title", "a", SqlTypeMetadata.new(sql_type: "blob")) diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 9f90ab79a1..75a74c052d 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -19,7 +19,7 @@ require 'models/car' require 'models/tyre' class FinderTest < ActiveRecord::TestCase - fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations, :cars + fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars def test_find_by_id_with_hash assert_raises(ActiveRecord::StatementInvalid) do @@ -993,10 +993,13 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct - assert_equal 2, Post.includes(authors: :author_address).order('author_addresses.id DESC ').limit(2).to_a.size + assert_equal 2, Post.includes(authors: :author_address). + where.not(author_addresses: { id: nil }). + order('author_addresses.id DESC').limit(2).to_a.size assert_equal 3, Post.includes(author: :author_address, authors: :author_address). - order('author_addresses_authors.id DESC ').limit(3).to_a.size + where.not(author_addresses_authors: { id: nil }). + order('author_addresses_authors.id DESC').limit(3).to_a.size end def test_find_with_nil_inside_set_passed_for_one_attribute diff --git a/activerecord/test/cases/invalid_connection_test.rb b/activerecord/test/cases/invalid_connection_test.rb index c26623e3ca..a16b52751a 100644 --- a/activerecord/test/cases/invalid_connection_test.rb +++ b/activerecord/test/cases/invalid_connection_test.rb @@ -1,5 +1,6 @@ require "cases/helper" +if current_adapter?(:Mysql2Adapter) class TestAdapterWithInvalidConnection < ActiveRecord::TestCase self.use_transactional_tests = false @@ -20,3 +21,4 @@ class TestAdapterWithInvalidConnection < ActiveRecord::TestCase assert_equal "#{Bird.name} (call '#{Bird.name}.connection' to establish a connection)", Bird.inspect end end +end diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb index 8eb027d53f..b926a92849 100644 --- a/activerecord/test/cases/migration/rename_table_test.rb +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -80,12 +80,10 @@ module ActiveRecord end def test_renaming_table_doesnt_attempt_to_rename_non_existent_sequences - enable_extension!('uuid-ossp', connection) connection.create_table :cats, id: :uuid assert_nothing_raised { rename_table :cats, :felines } ActiveSupport::Deprecation.silence { assert connection.table_exists? :felines } ensure - disable_extension!('uuid-ossp', connection) connection.drop_table :cats, if_exists: true connection.drop_table :felines, if_exists: true end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index f46d414b95..2f0382e273 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -77,7 +77,6 @@ module ActiveRecord def test_tree_is_not_traversed relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) - # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1 left = relation.table[:id].eq(10) right = relation.table[:id].eq(10) combine = left.and right @@ -104,7 +103,6 @@ module ActiveRecord def test_create_with_value_with_wheres relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) - # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1 relation.where! relation.table[:id].eq(10) relation.create_with_value = {:hello => 'world'} assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create) @@ -115,7 +113,6 @@ module ActiveRecord relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) assert_equal({}, relation.scope_for_create) - # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1 relation.where! relation.table[:id].eq(10) assert_equal({}, relation.scope_for_create) diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 7149c7d072..0638edacbd 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -111,15 +111,38 @@ class RelationTest < ActiveRecord::TestCase def test_loaded_first topics = Topic.all.order('id ASC') + topics.to_a # force load - assert_queries(1) do - topics.to_a # force load - 2.times { assert_equal "The First Topic", topics.first.title } + assert_no_queries do + assert_equal "The First Topic", topics.first.title end assert topics.loaded? end + def test_loaded_first_with_limit + topics = Topic.all.order('id ASC') + topics.to_a # force load + + assert_no_queries do + assert_equal ["The First Topic", + "The Second Topic of the day"], topics.first(2).map(&:title) + end + + assert topics.loaded? + end + + def test_first_get_more_than_available + topics = Topic.all.order('id ASC') + unloaded_first = topics.first(10) + topics.to_a # force load + + assert_no_queries do + loaded_first = topics.first(10) + assert_equal unloaded_first, loaded_first + end + end + def test_reload topics = Topic.all diff --git a/activerecord/test/fixtures/author_addresses.yml b/activerecord/test/fixtures/author_addresses.yml index 7b90572187..cf75e5998d 100644 --- a/activerecord/test/fixtures/author_addresses.yml +++ b/activerecord/test/fixtures/author_addresses.yml @@ -3,3 +3,9 @@ david_address: david_address_extra: id: 2 + +mary_address: + id: 3 + +bob_address: + id: 4 diff --git a/activerecord/test/fixtures/authors.yml b/activerecord/test/fixtures/authors.yml index 832236a486..41c124179e 100644 --- a/activerecord/test/fixtures/authors.yml +++ b/activerecord/test/fixtures/authors.yml @@ -9,7 +9,9 @@ david: mary: id: 2 name: Mary + author_address_id: 3 bob: id: 3 name: Bob + author_address_id: 4 |