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: https://github.com/jcoleman/relation_to_struct/blob/master/lib/relation_to_struct/active_record_relation_extension.rb#L26
I believe `pluck` should guarantee the same. On Sat, Apr 20, 2019 at 1:28 AM Stan Lo <stan001...@gmail.com> wrote: > > 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 > https://github.com/rails/rails/issues/36042. > > class Post < ActiveRecord::Base > enum status: { > a: "a", > 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). > > > > > > > -- > You received this message because you are subscribed to the Google Groups > "Ruby on Rails: Core" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to rubyonrails-core+unsubscr...@googlegroups.com. > To post to this group, send email to rubyonrails-core@googlegroups.com. > Visit this group at https://groups.google.com/group/rubyonrails-core. > For more options, visit https://groups.google.com/d/optout. -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-core+unsubscr...@googlegroups.com. To post to this group, send email to rubyonrails-core@googlegroups.com. Visit this group at https://groups.google.com/group/rubyonrails-core. For more options, visit https://groups.google.com/d/optout.