Arel: active relation from Arel::SelectManager with join
Asked Answered
G

1

8

Let us we have a Rails 4.2.x app and we have two tables posts and authors, and we want to use Arel to get the posts authored by an author with name == 'Karl'. (In this case we could be happy with Active Record joins but this is just to keep the example simple.)

posts = Arel::Table.new :posts
authors = Arel::Table.new :authors

my_query = posts.project(Arel.star)
                .join(authors)
                .on(posts[:author_id].eq(authors[:id]))
                .where(authors[:name].eq('Karl'))

> my_query.class
=> Arel::SelectManager

Now we could get back an array (of class Array) of posts by doing:

> Post.find_by_sql my_query
[master]  Post Load (3.1ms)  SELECT * FROM "posts" INNER JOIN "authors"
                             ON "posts"."author_id" = "authors"."id"
                             WHERE "authors"."name" = 'Karl'

=> [#<Post:0x005612815ebdf8
    id: 7474,
    ...
   ]

So we do get an array of posts, not an active record relation:

 > Post.find_by_sql(my_query).class
 => Array

Also injecting the manager into Post.where won't work

> Post.where my_query
=> #<Post::ActiveRecord_Relation:0x2b13cdc957bc>
> Post.where(my_query).first
ActiveRecord::StatementInvalid: PG::SyntaxError:
ERROR:  subquery must return only one column
SELECT  "posts".* FROM "posts"
WHERE ((SELECT * FROM "posts" INNER JOIN "authors" ON "posts"."author_id" = "authors"."id" WHERE "authors"."name" = 'Karel'))
ORDER BY "posts"."id" ASC LIMIT 1

I am thinking I must be missing something. In short: how do you get an active record relation from a select manager like my_query above (or another select manager accomplishing the same thing).

Gabie answered 16/3, 2017 at 17:28 Comment(0)
S
16

You can't get ActiveRecord::Relation from Arel::SelectManager neither from sql string. You have two ways to load data through ActiveRecord:

  1. Do all query logic in Arel. In this case you can't use any of ActiveRecord::Relation methods. But you have same functionality in Arel. In your example you may set limit through Arel:

    my_query.take(10)
    
  2. Other way is to use Arel in ActiveRecord::Relation methods. You may rewrite your query like this:

    posts = Arel::Table.new :posts
    authors = Arel::Table.new :authors
    
    join = posts.join(authors).
              on(posts[:author_id].eq(authors[:id])).
              join_sources
    
    my_query = Post.
                  joins(join).
                  where(authors[:name].eq('Karl'))
    
    > my_query.class
    => ActiveRecord::Relation
    

In this case you may use my_query as ActiveRecord::Relation

Style answered 17/3, 2017 at 8:21 Comment(1)
Great answer that helped me out of a jam, thank you.Etesian

© 2022 - 2024 — McMap. All rights reserved.