aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md11
-rw-r--r--activerecord/lib/active_record/associations.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb7
-rw-r--r--activerecord/lib/active_record/core.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb20
-rw-r--r--activerecord/test/cases/base_test.rb14
-rw-r--r--activerecord/test/cases/finder_test.rb42
-rw-r--r--activerecord/test/cases/relations_test.rb4
8 files changed, 93 insertions, 27 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index c2e79e9f02..b9af8584ae 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,14 @@
+* `default_sequence_name` from the PostgreSQL adapter returns a `String`.
+
+ *Yves Senn*
+
+* Fixed a regression where whitespaces were stripped from DISTINCT queries in
+ PostgreSQL.
+
+ *Agis Anastasopoulos*
+
+ Fixes #16623.
+
* Fix has_many :through relation merging failing when dynamic conditions are
passed as a lambda with an arity of one.
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 4ec1c8d545..18d4291599 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1146,6 +1146,20 @@ module ActiveRecord
# has_many :employees, -> { joins(:address) }
# has_many :posts, ->(post) { where("max_post_length > ?", post.length) }
#
+ # === Extensions
+ #
+ # The +extension+ argument allows you to pass a block into a has_many
+ # association. This is useful for adding new finders, creators and other
+ # factory-type methods to be used as part of the association.
+ #
+ # Extension examples:
+ # has_many :employees do
+ # def find_or_create_by_name(name)
+ # first_name, last_name = name.split(" ", 2)
+ # find_or_create_by(first_name: first_name, last_name: last_name)
+ # end
+ # end
+ #
# === Options
# [:class_name]
# Specify the class name of the association. Use it only if that name can't be inferred
@@ -1277,7 +1291,7 @@ module ActiveRecord
# when you access the associated object.
#
# Scope examples:
- # has_one :auther, -> { where(comment_id: 1) }
+ # has_one :author, -> { where(comment_id: 1) }
# has_one :employer, -> { joins(:company) }
# has_one :dob, ->(dob) { where("Date.new(2000, 01, 01) > ?", dob) }
#
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 7042817672..767b6b614a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -281,9 +281,9 @@ module ActiveRecord
def default_sequence_name(table_name, pk = nil) #:nodoc:
result = serial_sequence(table_name, pk || 'id')
return nil unless result
- Utils.extract_schema_qualified_name(result)
+ Utils.extract_schema_qualified_name(result).to_s
rescue ActiveRecord::StatementInvalid
- PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq")
+ PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
end
def serial_sequence(table, column)
@@ -549,7 +549,8 @@ module ActiveRecord
# Convert Arel node to string
s = s.to_sql unless s.is_a?(String)
# Remove any ASC/DESC modifiers
- s.gsub(/\s+(?:ASC|DESC)?\s*(?:NULLS\s+(?:FIRST|LAST)\s*)?/i, '')
+ s.gsub(/\s+(?:ASC|DESC)\b/i, '')
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
[super, *order_columns].join(', ')
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index d22806fbdf..82b9c79533 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -151,7 +151,7 @@ module ActiveRecord
end
def find_by(*args)
- return super if current_scope || args.length > 1 || reflect_on_all_aggregations.any?
+ return super if current_scope || !(Hash === args.first) || reflect_on_all_aggregations.any?
hash = args.first
@@ -177,6 +177,10 @@ module ActiveRecord
end
end
+ def find_by!(*args)
+ find_by(*args) or raise RecordNotFound
+ end
+
def initialize_generated_modules
super
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index cfff1f980b..a71c0dfb26 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -134,18 +134,18 @@ module ActiveRecord
end
def test_default_sequence_name
- assert_equal PostgreSQL::Name.new('public', 'accounts_id_seq'),
+ assert_equal 'public.accounts_id_seq',
@connection.default_sequence_name('accounts', 'id')
- assert_equal PostgreSQL::Name.new('public', 'accounts_id_seq'),
+ assert_equal 'public.accounts_id_seq',
@connection.default_sequence_name('accounts')
end
def test_default_sequence_name_bad_table
- assert_equal PostgreSQL::Name.new(nil, 'zomg_id_seq'),
+ assert_equal 'zomg_id_seq',
@connection.default_sequence_name('zomg', 'id')
- assert_equal PostgreSQL::Name.new(nil, 'zomg_id_seq'),
+ assert_equal 'zomg_id_seq',
@connection.default_sequence_name('zomg')
end
@@ -153,7 +153,7 @@ module ActiveRecord
with_example_table do
pk, seq = @connection.pk_and_sequence_for('ex')
assert_equal 'id', pk
- assert_equal @connection.default_sequence_name('ex', 'id'), seq
+ assert_equal @connection.default_sequence_name('ex', 'id'), seq.to_s
end
end
@@ -161,7 +161,7 @@ module ActiveRecord
with_example_table 'code serial primary key' do
pk, seq = @connection.pk_and_sequence_for('ex')
assert_equal 'code', pk
- assert_equal @connection.default_sequence_name('ex', 'code'), seq
+ assert_equal @connection.default_sequence_name('ex', 'code'), seq.to_s
end
end
@@ -334,6 +334,14 @@ module ActiveRecord
@connection.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',
+ @connection.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",
@connection.columns_for_distinct("posts.id", ["posts.created_at desc", "", " "])
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 4c0b0c868a..fb535e74fc 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1540,20 +1540,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "", Company.new.description
end
- ["find_by", "find_by!"].each do |meth|
- test "#{meth} delegates to scoped" do
- record = stub
-
- scope = mock
- scope.expects(meth).with(:foo, :bar).returns(record)
-
- klass = Class.new(ActiveRecord::Base)
- klass.stubs(:all => scope)
-
- assert_equal record, klass.public_send(meth, :foo, :bar)
- end
- end
-
test "scoped can take a values hash" do
klass = Class.new(ActiveRecord::Base)
assert_equal ['foo'], klass.all.merge!(select: 'foo').select_values
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index b42a60fea5..befbec4e1b 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -1027,6 +1027,48 @@ class FinderTest < ActiveRecord::TestCase
assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.offset("3").to_a }
end
+ test "find_by with hash conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.find_by(id: posts(:eager_other).id)
+ end
+
+ test "find_by with non-hash conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.find_by("id = #{posts(:eager_other).id}")
+ end
+
+ test "find_by with multi-arg conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.find_by('id = ?', posts(:eager_other).id)
+ end
+
+ test "find_by returns nil if the record is missing" do
+ assert_equal nil, Post.find_by("1 = 0")
+ end
+
+ test "find_by doesn't have implicit ordering" do
+ assert_sql(/^((?!ORDER).)*$/) { Post.find_by(id: posts(:eager_other).id) }
+ end
+
+ test "find_by! with hash conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.find_by!(id: posts(:eager_other).id)
+ end
+
+ test "find_by! with non-hash conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.find_by!("id = #{posts(:eager_other).id}")
+ end
+
+ test "find_by! with multi-arg conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.find_by!('id = ?', posts(:eager_other).id)
+ end
+
+ test "find_by! doesn't have implicit ordering" do
+ assert_sql(/^((?!ORDER).)*$/) { Post.find_by!(id: posts(:eager_other).id) }
+ end
+
+ test "find_by! raises RecordNotFound if the record is missing" do
+ assert_raises(ActiveRecord::RecordNotFound) do
+ Post.find_by!("1 = 0")
+ end
+ end
+
protected
def bind(statement, *vars)
if vars.first.is_a?(Hash)
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 88df997a2f..cc6a3888e3 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1570,7 +1570,7 @@ class RelationTest < ActiveRecord::TestCase
end
test "find_by doesn't have implicit ordering" do
- assert_sql(/^((?!ORDER).)*$/) { Post.find_by(author_id: 2) }
+ assert_sql(/^((?!ORDER).)*$/) { Post.all.find_by(author_id: 2) }
end
test "find_by! with hash conditions returns the first matching record" do
@@ -1586,7 +1586,7 @@ class RelationTest < ActiveRecord::TestCase
end
test "find_by! doesn't have implicit ordering" do
- assert_sql(/^((?!ORDER).)*$/) { Post.find_by!(author_id: 2) }
+ assert_sql(/^((?!ORDER).)*$/) { Post.all.find_by!(author_id: 2) }
end
test "find_by! raises RecordNotFound if the record is missing" do