Couldn't find [model] without an ID with has_many through association - ruby-on-rails

I have 3 models - Mom, Dad and Kid. The Mom and Dad only belong to each other through the Kid, so the associations are like this:
class Kid < ActiveRecord::Base
belongs_to :mom
belongs_to :dad
end
class Mom < ActiveRecord::Base
has_many :kids
has_many :dads, through: :kids
end
class Dad < ActiveRecord::Base
has_many :kids
has_many :moms, through: :kids
end
I'm trying to route to a Dads' moms by searching for any mom and not just the one's through the kids of the Dad:
http://localhost:3000/dads/superdad/moms
resources :dads do
resources :kids
resources :moms
end
In my Moms controller I tried to find the ID of "superdad":
def index
#dad = Dad.find(params[:id])
if params[:q].present?
#moms = Mom.search(params[:q], page: params[:page], per_page: 25)
else
#moms = Mom.none
end
end
But run into this error:
Couldn't find Dad without an ID
# line 8 #dad = Dad.find(params[:id])
Is it possible to use #dad in this way when the Mom has no direct id towards it? What do you suggest I do? I need to get to #dad.name (and more) on the Mom's index page.

Use this:
def index
#dad = Dad.find(params[:dad_id])
if params[:q].present?
#moms = Mom.search(params[:q], page: params[:page], per_page: 25)
else
#moms = Mom.none
end
end
use params[:dad_id] instead of params[:id]. The reason is that the route generated for index action of MomsController would be:
dad_moms GET /dads/:dad_id/moms(.:format) moms#index
params[:dad_id] would give you dad_id as superdad from http://localhost:3000/dads/superdad/moms. In your case, you are looking for params[:id] which does not exist. Hence, the error.

Related

How to eliminate N+1 queries from database query in Ruby on Rails?

In my Rails 6 app I have these models:
class User < ApplicationRecord
has_many :read_news_items
has_many :news_items, :through => :read_news_items
end
class NewsItem < ApplicationRecord
has_many :read_news_items
has_many :users, :through => :read_news_items
def read?(user)
read_news_items.where(:user_id => user.id).any?
end
end
class ReadNewsItem < ApplicationRecord
belongs_to :user
belongs_to :news_item
end
In my controller action I want to list all news items and highlight the ones that have not yet been read by the user:
class NewsItemsController < ApplicationController
def index
#news_items = NewsItem.all
end
end
The problem is that this generates N+1 queries for each record because the read?(current_user) gets called for each user record.
How can this problem be overcome?
I tried appending includes(:read_news_items) and joins(:read_news_items) to the database query in my controller but to no avail.
You could try:
class NewsItem < ApplicationRecord
has_many :read_news_items
def read?(user)
if read_news_items.loaded?
read_news_items.any? {|rni| rni.user_id == user.id }
else
read_news_items.where(:user_id => user.id).any?
end
end
end
class NewsItemsController < ApplicationController
def index
#news_items = NewsItem.includes(:read_news_items).all
end
end
OK, I learned something from every answer that was given here. So thanks for that.
I changed my read? method to the following which seems to have eliminated the N+1 queries:
class NewsItem < ApplicationRecord
def read?(user)
user.read_news_items.pluck(:news_item_id).include?(id)
end
end

undefined method `sub_categories' for nil:NilClass

I'm trying to make a category system for my blog but I've hit this error. Each blog_category can have multiple sub_categories by having parent_id pointing to the id of the main category. Some sub_categories and main blog_categories don't have anything in them. How would I prevent this NoMethodError from hitting?
BlogCategoriesController:
class BlogCategoriesController < ApplicationController
def index
#category = BlogCategory.find_by_id(params[:id])
#sub_category = #category.sub_categories.first
#posts = #subcategory.posts
end
private
def cat_params
params.require(:blog_category).permit(:name, :parent_id, :sub_category)
end
end
BlogCategory Model:
class BlogCategory < ApplicationRecord
has_many :posts
# This is called a self referential relation. This is where records in a table may point to other records in the same table.
has_many :sub_categories, class_name: "BlogCategory", foreign_key: :parent_id
# This is a scope to load the top level categories and eager-load their posts, subcategories, and the subcategories' posts too.
scope :top_level, -> { where(parent_id: nil).includes :posts, sub_categories: :posts }
end
The Posts point to the blog category via t.integer "blog_category_id" in the post table and has a belongs_to :blog_category in the Post model.
You can add a validation
def index
#category = BlogCategory.find_by_id(params[:id])
unless #category.nil?
#sub_category = #category.sub_categories.first
#posts = #subcategory.posts
end
end

Association not working

I have three models:
Department
class Department < ActiveRecord::Base
has_many :patients, :dependent => :destroy
has_many :waitingrooms, :dependent => :destroy
end
Waitingroom with fields patient_id:integer and department_id:integer
class Waitingroom < ActiveRecord::Base
belongs_to :patient
end
Patient with department_id:integer
class Patient < ActiveRecord::Base
belongs_to :department
has_many :waitingrooms
end
I save a waitingroom after a patient was in the waitingroom! So now i tried to retrieve the patients who where in the the waitingroom of the department:
def index
#waited = #current_department.waitingrooms.patients
end
Somehow it didnt worked it returned this error:
undefined method `patients' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Waitingroom:0x374c658>
But this worked: What did i wrong? Thanks!
def index
#waited = #current_department.waitingrooms
end
You can't invoke an association on a collection. You need to invoke it on a specific record. If you want to get all the patients for a set of waiting rooms, you need to do this:
def index
rooms = #current_department.waitingrooms
#waited = rooms.map { |r| r.patients }
end
If you want a flat array, you could (as a naive first pass) use rooms.map { |r| r.patients }.flatten.uniq. A better attempt would just build a list of patient ids and fetch patients once:
#waited = Patient.where(id: rooms.pluck(:patient_id).uniq)

rails adding tags to controller

I am implementing a basic tagging feature to my app. New to rails.
I have a listings model and I am working in the listings_controller # index. When the user goes to the listing page and sets the :tag param I want #users only to hold the users which match the tag.
So if they goto www.example.com/listings?tag=foo only pages which have been tagged with foo are loaded. This is what I have come up with so far but there is several problems with it.
def index
if params[:tag]
#id = Tag.where(:name => params[:tag]).first.id
#listingid = Tagging.where(:tag_id => #id)
#listingid.each do |l|
#users = User.find(l.listing_id)
end
else
#users = User.all
end
end
I am not sure how to loop and add each found user to #users. I think I may be going about this whole thing the wrong way.. My tag/tagging models look as follows:
tag.rb
class Tag < ActiveRecord::Base
has_many :taggings
has_many :listings, through: :taggings
end
tagging.rb
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :listing
end
Taggings has the following colums:
id, tag_id, listing_id
Tags has the following columns:
id, name
Any guidance would be appreciated, been trying to fix this for a while with no luck.
Trying with
def index
#tag = Tag.where(:name => params[:tag]).first
if #tag
#listings = #tag.listings.includes(:user)
#users = #listings.map{|l| l.user}
else
#users = User.all
end
end

How to define scope to get one record of a has_many association?

class User
has_many :posts do
def latest(report_date)
order(:report_date).where('report_date <= ?', report_date).limit(1)
end
end
end
class Post
belongs_to :user
end
I would like to retrieve the records of user with the last post for each user.
I could do this:
users = User.all.map{|user| user.posts.latest(7.weeks.ago).first}.compact
Is there a better way to write this? something like:
users = User.posts.latest(7.weeks.ago).all
if that were valid?
I tend to add something like this. Would it work in your case? It's nice because you can 'include' it in list queries...
class User < ActiveRecord::Base
has_many :posts
has_one :latest_post, :class_name => 'Post', :order => 'report_date desc'
...
end
In practice, you would do something like this in the controller:
#users = User.include(:latest_post)
And then, in the view where you render the user, you could refer to user.lastest_post and it will be eager loaded.
For example - if this was in index.html.haml
= render #users
you can access latest_post in _user.html.haml
= user.name
= user.latest_post.report_date

Resources