Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Arel: active relation from Arel::SelectManager with join

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

like image 267
sunless Avatar asked Mar 16 '17 17:03

sunless


People also ask

What is Arel in ActiveRecord?

Arel is an AST manager used in ActiveRecord to create SQL query. Every SQL statements are represented as ruby code in Arel. Because of this, it enable to write a complicated SQL query without using raw SQL string. But it's not recommended to use Arel in the application because it's a private API.

What is Arel query?

Arel is a library that was introduced in Rails 3 for use in constructing SQL queries. Every time you pass a hash to where, it goes through Arel eventually. Rails exposes this with a public API that we can hook into when we need to build a more complex query.

What is Arel SQL rails?

Arel is a powerful SQL AST manager that lets us appropriately combine selection statements for simple to very complicated queries. However, reader be cautioned – Arel is still a private API provided by Rails. Meaning that future versions of Rails could be subject to changes in Arel.


1 Answers

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

like image 187
Ilya Lavrov Avatar answered Sep 26 '22 02:09

Ilya Lavrov