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).