I have a convenience scope that includes
related models to speed up rendering of tables, etc:
class Post < ApplicationRecord
...
scope :includes_for_post_row, -> { includes(:reasons).includes(:feedbacks => [:user]) }
It works fine. Now, however, I'd like to select
an additional attribute. If I already knew what initial attributes I wanted, I could do this (in the console):
2.3.3 :005 > Post.select("`posts`.*, 42 AS column_forty_two").last.column_forty_two
Post Load (1.0ms) SELECT `posts`.*, 42 AS column_forty_two FROM `posts` ORDER BY `posts`.`id` DESC LIMIT 1
=> 42
This assumes that I know I want to select posts.*
, then I just tack on my column_forty_two
column and it all works.
I want to add column_forty_two
to my results, without affecting the initial select. For example, this should work:
p = Post.select("`posts`.*, 8 as column_eight").includes_for_post_row_with_forty_two
p.last.column_forty_two # => 42
p.last.column_eight # => 8
p.last.some_activerecord_property # => value
As should this:
p = Post.all.includes_for_post_row_with_forty_two.last
p.last.column_forty_two # => 42
p.last.some_activerecord_property # => value
How can I select
an additional column, without affecting or overwrite the existing columns selected by default by .all
or my own earlier select
?
If you go digging through the ActiveRecord source (an often necessary task with Rails), you'll see what's going on:
def build_select(arel)
if select_values.any?
arel.project(*arel_columns(select_values.uniq))
else
arel.project(@klass.arel_table[Arel.star])
end
end
select_values
is a list of everything you've handed to select
and is an empty array by default:
> Model.where(...).select_values
=> []
> Model.where(...).select('a').select_values
=> ["a"]
> Model.where(...).select('a').select('b').select_values
=> ["a", "b"]
and when ActiveRecord finally gets around to building the SELECT clause, it either uses what you've passed to select
(the if
branch in build_select
) or it uses table_name.*
(the else
branch in build_select
).
You should be able to use the same logic that build_select
uses to ensure that select_values
has something before you start adding more so that you sort of execute both the if
and else
branches of build_select
by pre-filling select_values
with the default table_name.*
. You could patch your own version of select
into the ActiveRecord::QueryMethods
module:
module ActiveRecord
module QueryMethods
def select_append(*fields)
if(!select_values.any?)
fields.unshift(arel_table[Arel.star])
end
select(*fields)
end
end
end
and then say things like:
> Post.select_append('6 as column_six').to_sql
=> "select `posts`.*, 6 as column_six from ..."
while leaving the "normal" select
behavior alone:
> Post.select('11 as column_eleven').to_sql
=> "select 11 as column_eleven from ..."
You don't have to monkey patch of course but it seems reasonable for this sort of thing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With