Rails associate record counting inside view - ruby-on-rails

I have two models, story & vote. I have a list of articles which a user can up vote which is stored as a separate vote in the vote model.
The story model has many votes and the story_id is stored with the vote record.
At the moment in my index action on the story view I'm just trying to do a simple count to see how many votes a given story has, I'm achieving this with the following code.
<%= #total_votes = Vote.where(:story_id => story.id).count %>
However I'd like to remove this from my story index action view but not sure were to store it? Is there a more efficient way of doing this?

Seems the relationship is story has many votes. So, you can simply do:
<%= story.votes.count %>
If you are worried about database performance you may want to add a counter cache.

You said you'd like to store the total votes value:
$~ rails g migration add_total_votes_to_stories total_votes:integer
class Story
after_save, :update_total_votes
private
def update_total_votes
write_attribute :total_votes, votes.count
end

Inside the model you could define a method and use it in the controller.
def total_votes(sid)
where(:story_id => sid ).count
end
Then in the controller:
#total_votes = Vote.total_votes(story_id)

this should go in controller
def index
#total_votes = Vote.where(:story_id => story.id).count
end

Related

rails, nested associated models AR query

i have a User model and a Post model. User has_many Posts; Post belongs_to User.
I want to create a table that lists a User and the total number of Posts per user as long as the post's public_flag is 't'. Is this possible directly in the view?
In my controller:
#users = User.all
In my view:
<% #users.each do |user| %>
<%= user.posts.size%>
<% end %>
Of course, this gets ALL of the user's posts regardless if the public_flag is true. Is there any way to set a condition in the View, or will I need to do something fancy in the controller (AR Query?).
Thanks for your help in advance!
You can daisy-chain restrictions for models, like so:
user.posts.where(:public_flag => 't').count
The advantage of using count over size is that instead of pulling all the posts into memory and counting them, ActiveRecord will execute a count query against the database and save a lot of processing.
user.posts.find_by_public_flag(true).count

Find multiple database objects by attribute in Rails?

I have a Track table and a Section table. A track has many sections. Sections are connected to their respective task by the Section's :track_id, which corresponds to the Track's :id attribute.
<% #track = Track.find(params[:id]) %>
<% #sections = Section.find_by_track_id(#track.id) %>
In the code above I'm trying to find multiple sections that share the same :track_id attribute, but find_by_track_id() only returns the first. What's the best way to get all of them?
Thanks!
If your tracks and sections are related in this way, then the best way to relate them is by using the methods that come automatically from Rails' associations.
in this case, I expect in your model files, you have the following:
class Track < ActiveRecord::Base
has_many :sections
end
class Section < ActiveRecord::Base
belongs_to :track
end
Then you can get the sections for a track like this:
#track = Track.find(params[:id])
#sections = #track.sections
You're looking for where, which finds all records where a specific set of conditions are met.
#sections = Section.where(track_id: #track.id)
This is unrelated to your question, but you should set #sections and #track in your controller. As it seems like you're new to Rails, I'd highly recommend reading through the Rails Guides. They will help you immensely on your journey.
EDIT: I was solving for the general question of "Find multiple database objects by attribute in Rails?", which is how to find multiple database objects in the general case. #TarynEast's method is the way to go to find all of the sections for a track, or more generally, all of the objects that belong to the desired object. For the specific case you're asking for above, go with #TarynEast's solution.
Association
To extend Taryn East's answer, you need to look into ActiveRecord Associations.
In your model, if you have the following has_many relationship:
#app/models/track.rb
Class Track < ActiveRecord::Base
has_many :sections
end
#app/models/section.rb
Class Section < ActiveRecord::Base
belongs_to :track
end
This will set up a relational database association between your tracks and sections datatables.
--
Associative Data
The magic of Rails comes into play here
When you call the "parent" object, you'll be able to locate it using its primary key (typically the ID). The magic happens when Rails automatically uses this primary_key as a foreign_key of the child model - allowing you to call all its data as an append to the parent object:
#track = Track.find params[:id] #-> find single Track by primary key
#sections = #track.sections #-> automagically finds sections using the track primary key
This means if you call the following, it will work exactly how you want:
#sections.each do |section|
section.name
end
Where
Finally, if you wanted to look up more than one record at a time, you should identify which ActiveRecord method you should use:
find is to locate a single record by id
finy_by key: "value" is to locate a single record by your defined key/column
where is to return multiple items using your own conditions
So to answer your base line question, you'll want to use where:
#sections = Section.where track_id: params[:id]
This is not the right answer, but it should help you
<% #sections=#track.sections%>
Use find when you are looking for one specific element identified by it's id.
Model.find is using the primary key column. Therefore there is always exactly one or no result.

How to reduce number of calls to a helper method

In my view, I need a User object to display a few different properties. There is an instance variable #comments that's being sent from the controller. I loop through the comments and get the User information through a helper method in order to reduce db calls.
Here is the helper method:
def user(id)
if #user.blank? == false && id == #user.id
return #user
else
return #user = User.find(id)
end
end
And in the view, I display the details as follows:
<h4> <%=user(comment.user_id).name%> </h4>
<p><%=user(comment.user_id).bio%></p>
<p><%=user(comment.user_id).long_bio%></p>
<p><%=user(comment.user_id).email%></p>
<hr>
<p><%=user(comment.admin_id).bio%></p>
<p><%=user(comment.admin_id).long_bio%></p>
<p><%=user(comment.admin_id).email%></p>
I was told that assigning a variable in the view is bad practice and hence I am calling the helper method multiple times instead of assigning the returned User object.
Is there a better way to do this?
I think you are overcomplicating things here.
Let's say you have a user model
class User < ActiveRecord::Base
has_many :comments
end
an admin model
class Admin < User
end
a comment model
class Comment < ActiveRecord::Base
belongs_to :user
end
Now you only need a type column in your users table and you can do things like this:
Admin.all (All users with type "Admin")
User.all (Really all users including type "Admin" and all other types)
and for every comment you can just use
comment.user.bio
and it doesn't matter if it's an admin or not.
See http://www.therailworld.com/posts/18-Single-Table-Inheritance-with-Rails for example
Additional info: To reduce db calls in general(N+1 queries) watch http://railscasts.com/episodes/372-bullet
It's perfectly fine to pass models to your view and build the data on the view off of the data contained in the model. Keep in mind that I'm not entirely certain how you want your page to work, but one option you may have is to use a partial view and pass it the user object. This allows you to still only have the one model in your partial view without setting additional variables.
Also, without knowing what kind of database you're using or if your models have any associations, and assuming that you're doing some input validation, you may not need this helper method and may be able to lean on your ORM to get the user object.
For Example:
<%= comment.user.age %>
This isn't any more efficient than what you've currently got, but it certainly makes the code look cleaner.
Another alternative: set a user variable in the view. You're not performing logic in your view at this point, you're simply storing some data to the heap for later use.

How can I filter ActiveAdmin index listings by a join table?

Let's say I have a table posts, and another table reviews that has a post_id and a rating (integer).
How can I add a filter to app/admin/post.rb which returns posts with a certain total score? (e.g. SUM(reviews.rating) GROUP BY (posts.id)). I want the filter to show up on the right side of the index, along with the other filters, and ideally to function as a range input.
To be clear, when I say "filter", I mean the ActiveAdmin filter method which adds filters to the right sidebar on the index page.
I created a scope in Post which returns posts with scores, but I haven't been able to find a way to use that in an ActiveAdmin filter.
Note: I rewrote my example because my original one didn't capture the complexity of the question.
It's common to override scoped_collection to join associated records to increase performance:
ActiveAdmin.register Post do
controller do
def scoped_collection
super.includes :author, :publisher
end
end
end
Since the entire collection now has author and publisher included, you can have a scope that queries those:
scope :random_house do |scope|
scope.where publishers: {name: 'Random House'}
end
I haven't come up with a proper solution to this question, but I have found a workaround.
I can change the scoped_collection based on a query param, and simply pass the param in when I want to use it. For example, if I have a scope with_rating(x), which returns posts with a score of at least x, I can write:
controller do
def scoped_collection
if params[:with_rating]
super.with_rating(params[:with_rating])
else
super
end
end
end
Then I can go to /admin/posts?with_rating=100 and get back posts with a rating of at least 100.
Thanks to #seanlinsley for making me aware of the scoped_collection method that I used in this solution.
Use counter cache column to store the comments count
http://railscasts.com/episodes/23-counter-cache-column
Then the column will get updated each time a comment is created to that post.This would also help in increasing the performance of search.

method error on joining table

Hey I hope you can give me advise on my problem. I got a Course Model, a CourseEnrollment Model and a User Model. In the Course index view it lists all the courses. I got a link_to to join a course. I want to create a new entry on the course_enrollments table.
link_to 'join', current_user.course_enrollments.create(:course_id => course,:user_id => current_user)
In my course_enrollments_controller I have:
def create
#course_enrollment = CourseEnrollment.new(params[:course_id, :user_id])
end
thx for your time
Seems like you are really mixing things up here. You are linking to the return value of the create method of a model but I guess you want to link to the "create" action of a controller.
Maybe you should (re-?) read a guide or introduction on Rails first.

Resources