[Question] Is plucking columns from joined table a legit usage?

From the examples in documents, it looks like AR#pluck is mainly for retrieving columns from the same table. Like Post.pluck(:author_id)

``

But we can also find some people using pluck for joined tables’ columns, like Post.joins(:author).pluck(“author.name”, :title)

``

And normally this works fine, except for cases like Plucking associated data from an enum column returns nil · Issue #36042 · rails/rails · GitHub.

class Post < ActiveRecord::Base

enum status: {

b: “b”,

c: “c”

}

has_many :comments

end

class Comment < ActiveRecord::Base

enum status: {

red: “red”,

green: “green”,

blue: “blue”

}

belongs_to :post

end

class BugTest < Minitest::Test

def test_association_stuff

post = Post.create!(status: “a”)

post.comments << Comment.create!(status: “blue”, not_enum: “blue”)

expected = [[“a”, “blue”, “blue”]]

actual = Post.includes(:comments).pluck(:status, “comments.status”, “comments.not_enum”)

assert_equal expected, actual

end

end

``

After digging into the issue, I think plucking joined tables’ columns is kind of a misuse of pluck. Here’s the source code of pluck

def pluck(*column_names)

if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?

return records.pluck(*column_names)

end

if has_include?(column_names.first)

relation = apply_join_dependency

relation.pluck(*column_names)

else

klass.disallow_raw_sql!(column_names)

relation = spawn

relation.select_values = column_names

result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) }

result.cast_values(klass.attribute_types)

end

end

``

And the cause of the issue above is

result.cast_values(klass.attribute_types)

``

Pluck seems assume it’ll only be used for the same table, so it uses the original class’ attribute types to cast column values (in this case, the Post’s attribute types). Hence when the joined table’s columns need different casting methods (like different enum definitions), Rails may get confused. (For generic types like integer, string that won’t be a problem though, that’s why most people don’t have issue). And for our issue example, Rails tried to use Post’s status type definition to cast Comment’s status type, and as expected it failed.

So my question is, should we have a full support on this behavior? Which means we’d need to be able to cast types for every tables envolved. And this won’t be a small change since we now need to get all tables’ type definitions. Or we should consider this as a misuse and not receommending using it (or even deprecate it).

At the very least on PostgreSQL (I only say that because that's where I'm the most familiar) there's no reason to need to cast based purely on the class's attributes -- the database result knows the database types of the columns returns. For example, my relation_to_struct gem does this for arbitrary calculated columns using only the ActiveRecord APIs: relation_to_struct/active_record_relation_extension.rb at master · jcoleman/relation_to_struct · GitHub

I believe `pluck` should guarantee the same.

I’ve added a link in the pull request you mention so that people finding this in Github’s issues view can track this.