Rails - Getting the ID of the Wrong Model in Query - ruby-on-rails

In my Rails 3.2 app, I have User, Comment, and Article models. In a view I'm trying to show all of a User's comments ordered by the article date (comments belong to articles). The problem is that in my view, when I try to retrieve the comment id, I get the article id.
***Models***
# user.rb
has_many :comments
# comment.rb
belongs_to :user
belongs_to :article
scope :by_article_date,
select("*, articles.date as article_date").
joins(:article).
order("article_date DESC")
# article.rb
has_many :comments
# users_controller
def comments
#user = current_user
#comments = #user.comments.by_article_date
end
# comments.html.erb
<% #comments.each do |comment| %>
<%= comment.id %> # shows the article id, but should be comment.id
<%= comment.article.id %> # shows article id correctly
<% end %>
Can someone please help me figure out what's going on?

Try this in your scope:
scope :by_article_date, -> { joins(:article).order("articles.date DESC") }

Related

Displaying top level category name in view | ruby on rails

I have one blog_categories table with sub_categories based on a parent_id value that points to the main category's .id that is above the subcategory. Main categories have a parent_id of NULL. This is all done through self-referential relations.
How do I display the name of the current sub_category and parent blog_category in the blog_categories.show view based on the /blog_categories/#?
For example;
"News" has .id 1 and parent_id NULL since it's the main category.
"Good" has .id 2 and parent_id 1 since it belongs to the category with an .id of 1.
"Bad" has .id 3 and parent_id 1 since it belongs to the category with an .id of 1.
When /blog_categories/2 is rendered I am trying to have it show the
name of the subcategory ("Good" in this case) followed by its
parent category ("News" in this case).
The desired result is to be a header stating "Good News"
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
Blog_categories controller:
class BlogCategoriesController < ApplicationController
def index
#categories = BlogCategory.top_level.includes(sub_categories: :posts)
#category = BlogCategory.find_by_id(params[:id])
unless #category.nil? # Allows for categories to have nothing in them, eliminating the NoMethodError
#sub_category = #category.sub_categories.first
#posts = #subcategory.posts
end
#all_posts = Post.all
end
def show
#category = BlogCategory.find_by_id(params[:id])
#sub_category = #category.sub_categories
#posts = #category.posts
end
private
def cat_params
params.require(:blog_category).permit(:name, :parent_id, :sub_category)
end
end
My Show View:
<% BlogCategory.top_level do |category| %>
<% category.sub_categories do |sub_category| %>
<h2 class="center p-space blog-h2"><%= sub_category.name %> <%= category.name %></h2>
<% end %>
<% end %>
I've tried a few combinations of do statements but I really cant crack this problem. I would love some help in figuring this one out, thanks!
If this helps, I've had some success with <%= #category.name %> but it only shows the sub_category's name.
If I'm not mistaken, you need to display current category name + current category subcategories names. Try to adapt this example to your needs:
# controller
def show
#category = BlogCategory.find(params[:id])
if #category.present?
#sub_cat_names = #category.sub_categories.map(&:name)
#posts = #category.posts
end
end
# view
<% if #category.present? %>
<h2>Current category:</h2> <%= #category.name %>
<h3>Sub categories:</h3> <%= #sub_cat_names.join(', ') %>
<% end %>
UPDATE
I don't understand why do you need #category and #sub_category variables in show action if you don't use them in the view. In accordance with your latest additions, the solution can look like this:
# model
class BlogCategory < ApplicationRecord
has_many :posts
has_many :sub_categories, class_name: 'BlogCategory', foreign_key: :parent_id
belongs_to :parent, class_name: 'BlogCategory'
scope :top_level, -> { where(parent_id: nil).includes :posts, sub_categories: :posts }
end
# controller
def show
#sub_category = BlogCategory.find(params[:id])
#category = #sub_category.parent
end
# view
<h2 class="center p-space blog-h2">
<%= "#{#sub_category.name} #{#category.name}" %>
</h2>

Include associated model for all objects (in index action)

I am trying to develop ratings for my application, where a User is able to set a specific rating for a comment. I have followed the following tutorial in order to do so.
Here are my associations:
class Rating < ActiveRecord::Base
belongs_to :comment
belongs_to :user
end
class Comment < ActiveRecord::Base
has_many :ratings
belongs_to :user
end
class User < ActiveRecord::Base
has_many :ratings
has_many :comments
end
My problem here is that, in the index action of my comments controller, I need to include the rating that the user has done for that comment. In the tutorial is just shown how to select a particular rating by doing this:
#rating = Rating.where(comment_id: #comment.id, user_id: #current_user.id).first
unless #rating
#rating = Rating.create(comment_id: #comment.id, user_id: #current_user.id, score: 0)
end
However, I will have several ratings, because in my controller I have:
def index
#comments = #page.comments #Here each comment should have the associated rating for the current_user, or a newly created rating if it does not exist.
end
You want to find the comment's rating where the rating's user_id matches the current user.
<%= comment.ratings.where(user_id: current_user.id).first %>
However this sort of logic is pretty cumbersome in the views, a better strategy would be to define a scope in Rating that returns all ratings made by a specific user.
class Rating
scope :by_user, lambda { |user| where(user_id: user.id) }
end
class Comment
# this will return either the rating created by the given user, or nil
def rating_by_user(user)
ratings.by_user(user).first
end
end
Now in your view, you have access to the rating for the comment created by the current user:
<% #comments.each do |comment| %>
<%= comment.rating_by_user(current_user) %>
<% end %>
If you want to eager load all ratings in your index page, you can do the following:
def index
#comments = page.comments.includes(:ratings)
end
You can then find the correct rating with the following:
<% #comments.each do |comment| %>
<%= comment.ratings.find { |r| r.user_id == current_user.id } %>
<% end %>
This would return the correct rating without generating any extra SQL queries, at the expense of loading every associated rating for each comment.
I'm not aware of a way in ActiveRecord to eager load a subset of a has_many relationship. See this related StackOverflow question, as well as this blog post that contains more information about eager loading.

Rails: Show all associated records on a has_many through association

I'm building a guestlist app and I have defined both Guest (name) and List models - guests can have many lists and lists can have many guests. Both are associated in a has_many through association (after reading that HABTM associations aren't a good idea).
Here are my models:
class Guest < ActiveRecord::Base
has_many :lists, through: :checklists
end
class List < ActiveRecord::Base
has_many :guests, through: :checklists
end
class Checklist < ActiveRecord::Base
belongs_to :list
belongs_to :guest
end
EDIT - my lists controller for show:
def show
#list = List.find(params[:id])
end
On the List show view, I want to display the all of the guest names that are tied to that list through the checklist table. I can figure out if I need a do loop or an array...this is a bit beyond my current skill.
I've tried things like the following:
<%= #list.checklist.guest.name %>
I'm clearly missing some key bit of code and concept here.
Thanks in advance.
You need to iterate over guests like this:
<% #list.guests.each do |guest| %> # For each guest in list.guests
<%= guest.name %> # print guest.name
<% end %>
It should be something like this
<% #list.guests.each do |guest| %>
<%= guest.name %>
<% end %>

Rails form_for and has_many through argument error

I'm trying to build a form_for to create a join model between two other models. I have a Book model and User model, with another called Reads that is my join. Here is how I've set up the associations:
class User < ActiveRecord::Base
has_many :reads
has_many :books, :through => :reads
end
class Book < ActiveRecord::Base
has_many :reads
has_many :users, :through => :reads
end
class Read < ActiveRecord::Base
belongs_to :book
belongs_to :user
end
I've looked at the docs for form_for and watched the railscast episode on many-to-many associations, but I can't figure out why I'm getting the error when I try to render the Book#show view where I've put the form:
First argument in form cannot contain nil or be empty
Here is my form in app/views/books/show.html.erb:
<%= form_for(#read) do |f| %>
<%= f.hidden_field :book_id, value: #book.id %>
<%= button_to 'Add to Reads', {controller: 'reads', action: 'create'}, {class: 'btn'} %>
<% end %>
I think part of the problem is that I am trying to create a 'Reads' object from the Books model, but I'm not sure what I am doing wrong. I need the 'Add to Reads' button on the Book's page so that a user can select that particular book to add to their 'reads.' I'm also adding the current_user id in the controller, rather than in the view. Here is my create action from the Reads controller if that helps...
def create
#read = Read.new(read_params)
#read.user_id = current_user.id
#read.save
if #read.save
# do this
else
# do that
end
end
And I'm using strong params...
def read_params
params.require(:read).permit(:user_id, :book_id)
end
Thanks for any help.
First argument in form cannot contain nil or be empty
This means that #read in your form is nil. Since you are in the show action of your Books controller, you have to define this variable in the books controller.
def show
#read = Read.new
...
end

Understanding associations in rails 3

Seems I need to brush up on my associations in rails. At present I am trying to display all posts that have the department name as staff.
two models exist at present, posts and departments
class Post < ActiveRecord::Base
belongs_to :department
attr_accessible :title, :comments, :department_id
end
class Department < ActiveRecord::Base
has_many :posts
attr_accessible :name, :post_id
#Scopes
scope :staff_posts, where(:name => "Staff")
end
So i want to display all posts that have the department name staff
to do this i have put this in my controller
class PublicPagesController < ApplicationController
def staffnews
#staffpost = Department.staff_posts
end
end
In my view i am trying to display all these posts like so
<% #staffpost.each do |t| %>
<h2><%= t.title %>
<h2><%= t.comments %></h2>
<% end %>
Clearly going wrong somewhere as i get undefined method nil, even though i have 3 posts with the name 'Staff'
Can someone please explain where i am misunderstanding the association as would love to get this right
EDIT
Routes
scope :controller => :public_pages do
get "our_news"
match "our_news/staffnews" => "public_pages#staffnews"
In controller it returns department with name staff. And you are using title and comments on on department objects thats why its giving nil method error.
Use like this:
def staffnews
#dept_staff = Department.staff_posts
end
<% #dept_staff.each do |ds| %>
<% ds.posts.each do |p| %>
<h2><%= p.title %></h2>
<h2><%= p.comments %></h2>
<% end %>
<% end %>
or
In post model create named_scope
class Post < ActiveRecord::Base
belongs_to :department
attr_accessible :title, :comments, :department_id
scope :staff_posts, :include => :department, :conditions => {"departments.name" => "Staff"}
end
class Department < ActiveRecord::Base
has_many :posts
attr_accessible :name, :post_id
end
Controller:
def staffnews
#staffpost = Post.staff_posts
end
View: #No change
<% #staffpost.each do |t| %>
<h2><%= t.title %></h2>
<h2><%= t.comments %></h2>
<% end %>
Your staff_posts scope is only selecting the Departments with the name "Staff". Assuming you will have one and only one department named staff, you have a few ways to handle this.
This will find all departments with the name staff, and eager load the posts that go along with it:
#department = Department.where(name: "Staff").include(:posts).first
Since you are trying to scope Post, however, this belongs in Post. Here's an example using a method as scope:
class Post < ActiveRecord::Base
belongs_to :department
attr_accessible :title, :comments, :department_id
def self.staff
where(department_id: staff_department_id)
end
def staff_department_id
Department.find_by_name!("Staff").id
end
end
This way, you can use #staff_posts = Post.staff and iterate over that collection (Note: I don't recommend getting staff_department_id this way permanently. This could be set to a constant when the app boots up, or some other more robust solution).
You can find the all the posts that have the department name staff by following changes:
class PublicPagesController < ApplicationController
def staffnews
#get all the department which have name is staff
departments = Department.where("name=?","staff")
#get all the ids
department_ids = departments.map(&:id)
#retrieve post that department name is staff
#staffpost = Post.find_by_department_id(department_ids)
end
end

Resources