Kevin Sylvestre Kevin Sylvestre - 1 year ago 61
Ruby Question

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 ="items.*").from(source).includes([:images])
p items # [#<Item id: 1>, #<Item id:2>]

However running:

p items.count

Results in
NoMethodError: undefined method
map' 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?

Answer Source

Can you call all on that object to essentially cast it to an Array?"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:

>'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:

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