Kevin Sylvestre
Kevin Sylvestre

Reputation: 38032

No Method Error 'map' for #<Arel::Nodes::SqlLiteral>

I have the following example query:

source = "(SELECT DISTINCT source.* FROM (SELECT * FROM items) AS source) AS items"
items = Item.select("items.*").from(source).includes([:images])
p items # [#<Item id: 1>, #<Item id:2>]

However running:

p items.count 

Results in NoMethodError: undefined methodmap' for Arel::Nodes::SqlLiteral`

I appreciate the query is silly, however the non-simplifieid query is a bit too complicated to copy and this was the smallest crashing version I could create. Any ideas?

Upvotes: 4

Views: 3336

Answers (2)

karmakaze
karmakaze

Reputation: 36154

The issue is Rails #24193 https://github.com/rails/rails/issues/24193 and has to do with from combined with eager loading. The workaround is to use the form: Item.select("items.*").from([Arel.sql(source)]).includes([:images])

Upvotes: 3

Brandan
Brandan

Reputation: 14983

Can you call all on that object to essentially cast it to an Array?

Item.select("items.*").from(source).includes([:images]).all.count

Or perhaps in that case, size would be more appropriate. In any case, this will execute the query and load all the objects into memory, which may not be desirable.


It looks like the problem is with your includes([:images]). On a similar application, I can execute this from the console:

> Category.select('categories.*').from('(SELECT DISTINCT source.* FROM (SELECT * FROM categories) AS source) AS categories').count
  (0.5ms)  SELECT COUNT(*) FROM (SELECT DISTINCT source.* FROM (SELECT * FROM categories) AS source) AS categories

(Notice that the count overrides the SELECT clause, even though I explicitly specified items.*. But they're still equivalent queries.)

As soon as I add an includes scope, it fails:

> Category.select('categories.*').from('(SELECT DISTINCT source.* FROM (SELECT * FROM categories) AS source) AS categories').includes(:projects).count
NoMethodError: undefined method `left' for #<Arel::Nodes::SqlLiteral:0x131d35248>

I tried a few different means of acquiring the count, like select('COUNT(categories.*)'), but they all failed in various ways. ActiveRecord seems to be falling back on a basic LEFT OUTER JOIN to perform the eager loading, possibly because it thinks you're using some kind of condition or external table to perform the join, and this seems to confuse its normal methods of performing the count. See the end of the section on Eager Loading in the ActiveRecord::Associations docs.

My Suggestion

If the join doesn't affect the number of rows returned in the outer query, I'd say your best bet is to execute one query to get the count and one query to get the actual results. We have to do something similar in our application for paging: one query returns the current page of results, and one returns the total number of records matching the filter criteria.

Upvotes: 4

Related Questions