Ask A Question

Notifications

You’re not receiving notifications from this thread.

How do I use includes to return all columns of associated table?

Oliver asked in Rails

I hope someone here can help me. There's a lot to ActiveRecord I'm yet to understand and I'm a little lost at the moment.

I have three models. Issue, Language, Labels.

An issue has a language and many labels. Languages and labels have many issues.

Because issues and labels have a many to many relationship, I have created a join table tags. (I suppose I could chose a more appropriate name.)

class Label < ApplicationRecord
  has_many :tags
  has_many :issues, through: :tags
end

class Issue < ApplicationRecord
  has_many :tags
  has_many :labels, through: :tags
  belongs_to :language
  ...
end

class Tag < ApplicationRecord
  belongs_to :issue
  belongs_to :label
end

I want to be able to filter issues by label or language. To that end, I've added two scopes to Issue.

scope :by_labels, lambda { |labels|
  joins(:labels).where(labels: { name: labels })
}

scope :by_language, lambda { |language|
  joins(:language).where(languages: { name: language })
}

I can search for an issue by a single label like so:

> Issue.by_labels('bug').first.labels.map(&:name)
=> ["bug", "help wanted"]

Now, in my controller I want to eager load labels so not to end up with N+1 queries. I use includes:

  @issues = Issue.by_labels('bug').includes(:labels, :language)

However, I now see that only the label specified in the where query is returned:

> Issue.by_labels('bug').includes(:labels, :language).first.labels.map(&:name)
=> ["bug"]

I need each issue row to point to all of its labels. ["bug", "help wanted"] not ["bug"].

Why is it returning the single label? What is includes doing that it doesn't return all the labels?

The executed SQL is:

  SELECT "issues"."id" AS t0_r0, "issues"."assigned" AS t0_r1,
  "issues"."description" AS t0_r2, "issues"."repo_name" AS t0_r3,
  "issues"."title" AS t0_r4, "issues"."url" AS t0_r5,
  "issues"."user_avatar_url" AS t0_r6, "issues"."created_at" AS t0_r7,
  "issues"."updated_at" AS t0_r8, "issues"."language_id" AS t0_r9,
  "labels"."id" AS t1_r0, "labels"."name" AS t1_r1, "labels"."issue_id" AS
  t1_r2, "labels"."created_at" AS t1_r3, "labels"."updated_at" AS t1_r4,
  "languages"."id" AS t2_r0, "languages"."name" AS t2_r1,
  "languages"."color" AS t2_r2, "languages"."created_at" AS t2_r3,
  "languages"."updated_at" AS t2_r4 FROM "issues" INNER JOIN "tags" ON
  "tags"."issue_id" = "issues"."id" INNER JOIN "labels" ON "labels"."id" =
  "tags"."label_id" LEFT OUTER JOIN "languages" ON "languages"."id" =
  "issues"."language_id" WHERE "labels"."name" = ? ORDER BY
  "issues"."created_at" DESC  [["name", "bug"]]

What should I be doing to ensure each issue is returned with all of its corresponding labels?

Any help would be appreciated.

Reply

I don't quite know why it works but passing a string to includes instead of a symbol returns both labels.

> Issue.by_labels('bug').includes('labels', 'language').first.labels.map(&:name)
=> ["bug", "help wanted"]
Reply
Join the discussion

Want to stay up-to-date with Ruby on Rails?

Join 73,723+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.

    Screencast tutorials to help you learn Ruby on Rails, Javascript, Hotwire, Turbo, Stimulus.js, PostgreSQL, MySQL, Ubuntu, and more. Icons by Icons8

    © 2023 GoRails, LLC. All rights reserved.