3

Assume I have the following models:

class Article < ActiveRecord::Base
    has_many :keyword_mappings, :as => :entity, :dependent => :delete_all
    has_many :keywords, -> {select('keywords.*, keyword_mappings.count as count')}, :through => :keyword_mappings
end

class Keyword < ActiveRecord::Base
    belongs_to :entity, polymorphic: true
    has_many :keyword_mappings, :dependent => :delete_all
end

class KeywordMapping < ActiveRecord::Base
    belongs_to :keyword
    belongs_to :entity, polymorphic: true

    validates :keyword_id, :uniqueness => {
        :scope => [:entity_id, :entity_type]
    }
end

The intention behind the custom select in the has_many :keywords for Article is that I can loop through an article's keywords while checking the count, individually retrieve the count, or pass it to a Javascript app with the count being part each keyword in a JSON response. count is a field of the keyword_mappings table, which servers as a join table / join model object between the keywords and the articles.

(I found this solution from these two answers.)

With this setup, if I call .includes(:keywords) on a set of articles which contains at least one article that has associated keyword mappings, I get this SQL error:

2.1.1 :001 > Article.order('id desc').limit(1).includes(:keywords)

  Article Load (0.7ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`deleted_at` IS NULL ORDER BY id desc LIMIT 1
  KeywordMapping Load (0.7ms)  SELECT `keyword_mappings`.* FROM `keyword_mappings` WHERE `keyword_mappings`.`entity_type` = 'Article' AND `keyword_mappings`.`entity_id` IN (20813)
  Keyword Load (1.2ms)  SELECT keywords.*, keyword_mappings.count as count FROM `keywords` WHERE `keywords`.`id` IN (2)

Mysql2::Error: Unknown column 'keyword_mappings.count' in 'field list': SELECT keywords.*, keyword_mappings.count as count FROM `keywords`  WHERE `keywords`.`id` IN (2

It seems that it's attempting to access the count field in a separate query from the one in which it access the keyword mappings table, whereas with accessing a single article's keywords (which succeeds), it joins on both:

2.1.1 :002 > Article.last.keywords
  Article Load (0.7ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`deleted_at` IS NULL ORDER BY `articles`.`id` DESC LIMIT 1
  Keyword Load (0.9ms)  SELECT keywords.*, keyword_mappings.count as count FROM `keywords` INNER JOIN `keyword_mappings` ON `keywords`.`id` = `keyword_mappings`.`keyword_id` WHERE `keyword_mappings`.`entity_id` = 20813 AND `keyword_mappings`.`entity_type` = 'Article'

I'd like to use eager loading in this case, while retaining results that do not have any keywords -- is there any way to force .includes() to recognize this pattern and successfully load in the join table field? Or perhaps some way to alter the custom SQL in the has_many to ensure it always joins with the keyword_mappings table?

Community
  • 1
  • 1
cwsault
  • 112
  • 5

0 Answers0