Getting count by drilling down through relations - ruby-on-rails

I'm using Rails 4 and I want to get a count of votes for each individual post review for a given post.
Schema:
Post PostReview PostReviewVote
id post_id post_review_id
user_id user_id
voted_on_date
I have something like:
table.table.table-striped.table-hover
thead
tr
th Post Name
th Reviews Created For Post
th Total Votes in All Reviews For Post
tbody
- for post in #posts
tr
td= post.name
td= PostReview.where(post_id: post.id).size
td= PostReview.where(post_id: post.id).PostReviewVotes.size
But it's not fetching, citing a runtime error:
undefined method `PostReviewVotes' for #<ActiveRecord::Relation []>
Any suggestions? My models have the proper associations, FYI.

You could simply do this:
post.post_reviews.count
post.post_review_votes.count
If you have all the associations defined as you say.
or if you want a method for it...
In your Post model:
def post_votes_total
self.post_review_votes.count
end
As long as you have defined the relationship in Post:
has_many :post_review_votes, through: :post_reviews
Since you are passing the #posts variable to the view :
post.post_votes_total
Using the built in association methods in the views is fine, but if logic gets more complicated you should really use a model method or a helper method.

Couple of issues I see:
PostReview.where(post_id: post.id) can return more than one record.
Your relationship should be defined as post_review_votes, so you'd want to call .post_review_votes rather than PostReviewvotes
You might want one of the following:
`PostReview.where(post_id: post.id).first.post_review_votes.size- If you only expect one PostReview this is probably what you want.
PostReview.where(post_id: post.id).collect{|pr| pr.post_review_votes.size}.sum - This will take all of the post reviews, get the size of their votes, and add them together.
Or pure SQL:
PostReviewVotes.includes(:post_review).where("post_reviews.post_id: ?", post.id).count

Related

ActiveRecord Eager Loading after initial query

Let's say I have a simple model association, where a blog Post has many Comments on it.
class Post < ActiveRecord::Base
has_may :comments
end
If I wanted to avoid "N + 1" queries and eager load all the associations beforehand, I could do -
Post.includes(:comments).where(title: "foo")
Which runs two queries. The first one looks up the Post using the where condition and the second looks up all the associated comments at once.
But what if I already have a Post object? Can I "add" an includes to it after the initial result set to run the 2nd bulk query that looks up all the associations?
It seems counter intuitive to do a delayed eager load, but I assume looking up all the associations at once would still save me from having to look them up individually as I loop through them.
e.g.
p = Post.where(title: "foo")
# Will the below work?
p.includes(:comments)
p.comments.each do |comment|
puts comment.text
end
Let's break down your code.
posts = Post.where(title: 'foo')
That searches for all posts with a title of foo. posts is an ActiveRecord::Relation object and you can chain more ActiveRecord commands like select, limit, includes, etc.
So doing posts.includes(:comments) is valid and should eager load the comments. Just don't forget to assign the result to posts again (or another variable).
What will not work is posts.comments because comments is a method that works on an instance of Post. Changing to the following code will work
posts = Post.where(title: 'foo')
posts = posts.includes(:comments)
posts.each do |post|
post.comments.each do |comment|
puts comment.text
end
end

How to access variables in model

I am trying to get access to a property contained inside my user object.
My user model has_many: posts. In the controller how would i gain access to these posts? Would i create a method in the model?
def posts
#posts = Post.find(User_id: params[:id])
end
or can i directly access the posts for the user. User.posts Since i am currently residing in the controller, is the controller aware of the currently selected model? Or do i have to pull the information again?
You can query the database for all the posts with a specific user_id, like this:
#posts = Post.where(user_id: params[:id])
Alternatively, you can find the user first and then fetch all posts associated with that user, like this:
user = User.find(params[:id])
#posts = user.posts
Assuming your id in params is the id of your user, you can use user = User.find(params[:id]) to get the user and #posts = user.posts to get all the posts of this user.
So, it is not about where you are, It is about what you are calling.
I'm sure you are familiar with relationships...
When you have relationships, it means that you can get to one relation from the other through whatever association exists between them.
If I am my father's son, then you can get me directly by checking my father's children. ( you don't necessarily have to get all children in the village first )
So, bringing all my story above together, with the association between your Post and User, you can always call user.posts (user being an instance of User) and post.user ( with post being an instance of Post)
The Ruby on Rails guides have a section on associations, which is what you want. It's here: http://guides.rubyonrails.org/association_basics.html
In a nutshell, because you have added an association in your user model to a number of post records, Rails will build a helper method in your user model called posts. You can use that to access all the posts associated with that user.
When you create a post, the post record needs to have a column called user_id. This will provide the 'physical' link between the user and post models. You can access the posts from a user like so:
user.posts each do |post|
# do something with post.content
end
To get posts that match some criteria in the posts collection you can query like this:
posts = user.posts.where(:something => 'matches criteria')
If you know there's only one post that matches the criteria, you can do this:
post = user.posts.where(:something => 'matches criteria').first
The post model also needs a belongs_to :user association. (The belongs_to will generate a helper method called user in the post model which you can then use to access the user record from the post.) For example:
user_email = post.user.email
The user record does not require a post_id column since Rails knows that user.post refers to the post table and automagically generates a query using user_id.
Anyway, the guide I linked to above will give you all the information you need and more too.

Ruby on rails implementing "repost" action for Post's model

I have a Userand Post model with the association one-to-many. I tried to implement a repost action, add a link has_and_belongs_to_many through a table reposts.
But I was faced with the following challenges:
1) Post to feed loaded as follows:
followed_users="SELECT followed_id FROM relationships WHERE follower_id = :user ";
replics_posts="SELECT micropost_id FROM replics_users WHERE user_id = (:user)"
reposts="SELECT post_id FROM reposts WHERE user_id = (:user)"
where("user_id IN(#{followed_users}) OR user_id= (:user) OR id IN(#{replics_posts}) OR id in (#{reposts})", user: user);
and sorted by date modified. Repost similarly sorted, from which there is a situation that is repost in the middle feed.
2) No additional effort, followers do not see reposts user.
These problems can be solved through the auxiliary array with the need to fast, but it looks ridiculous and non-optimal solution.
How can I get out of the situation?
P.S. I think the solution can be found by reference in the field "Content" in the Post model on the same field, another object. Then repost action will not need a separate table and will consist only of a new Post object with a pointer to the contents of the original post. But I do not know how to do this in Ruby on Rails.
Thank you for your help!
I corrected as follows:
1) In the Post model added a new field repost_id and reference to yourself:
has_many: reposts, class_name: "Post", foreign_key: "repost_id", dependent:: destroy;
(relation to the model User not changed)
2) Added to Post's controller method repost
def repost
orig_post=Micropost.find(params[:id]);
if(orig_post)
Micropost.create(user_id:current_user.id,
content: orig_post.content,
repost_id:orig_post.id);
respond_to do |format|
format.js
end
end
end
(Do not forget to realize meets both the route and validations creation of the post)
The result is a correct model of behavior actions repost with dependencies and correct display in the feed. But sadly, this approach involves storing duplicate data in the table Posts in the "Content" field.

Finding data through a habtm association

I can't wrap my head around how to accomplish this and hoping that someone will be able to help - I am sure this will be something simple!
I'm trying to implement a "tag cloud" from scratch in my rails app, (fairly similar to the episode as posted on railscasts.com) with the added complexity that I only want to show tags in my "cloud", that relate to the results that are returned. For example, let's say that in the index view, the user has filtered the results. My desire is to only show tags from the filtered result set.
I have two models that contain a HABTM association between them:
class Article < ActiveRecord::Base
has_and_belongs_to_many :tags
...
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :articles
...
end
In my Article controller, I have an #articles variable which gives me the results that show in my index action. For the purpose of this explanation, let's say it's:
#article = Article.all
What I thought I'd be able to do is to call #article.tags, hoping it would return the associated tags for all articles, within the results. I thought I'd be able to then group and iterate through them, showing them in the cloud. However, this throws an "undefined method error".
I've been playing in the rails console and found that if I run:
>> #articles = Article.find(1)
>> #articles.tags
Then all tags associated with that article are returned to me. Why can't you call that on .all?
If I was to use SQL directly I'd do something like:
SELECT name, COUNT(*)
FROM tags INNER JOIN articles_tags on ... INNER JOIN articles on...
WHERE // filtered results
GROUP BY name
I guess that's a simplified equivalent of what I'm trying to do but using the rails-query-lingo.
Any ideas?
You can get the Tags that have Articles by:
Tag.joins(:articles)
Same applies for atricles that have tags.
Article.joins(:tags)
You may prefer using has_many through instead of habtm, that gives you more control over the join table, check this question

How to retrieve information that is a join of tables in rails?

My rails app is now working with ActiveRecord and I am really amazed about ruby on rails.
My DB has three tables:
Students(Id, name, parentId)
Parents(Id, name)
Grades(Id, studentId, value)
And the three models.
I have three views, in one of them I have to list the students, so I make a Student.all and it works.
But in one of the views I need to list the student with the parent's name and in the third one I nbeed the student's name and their grades.
I am completely lost now: if my model is my table, how can I retrieve this information which is a join of tables?
Thanks
Edit:
I added the line
Student.includes(:parent, :grades).all.each do |student|
And added the relations:
class Student < ActiveRecord::Base
belongs_to :parent
end
class Parent < ActiveRecord::Base
has_many :student
end
I had some problems when setting the relations, but now my page loads. Still I can't use the Student.parent.name property.
using <%= debug student %> I get the debug info, but no information about the parent. (students.parent shows the parent_id)
Any ideas what I'm missing?
It was a name conflict: my Students table had a column called parent. changing to parent_id solved it.
If you have your relations setup correct, you can do something like:
Student.all.each do |student|
puts student.name
puts student.parent.name
student.grades.each do |grade|
puts grade.value
end
end
You might also be interested in using includes in this case to avoid triggering a query for each student whose grades you retrieve. In this example you would replace:
Student.all.each do |student|
with
Student.includes(:parent, :grades).all.each do |student|
The difference is that in the first case Rails triggers a separate query to retrieve the grades for each student (and a query to get each parent's name). This is often called the "N+1 Problem," since if you have 10 students, to loop through each of them and get their parent's name would take 11 queries (one to get all the students, and one for each of the 10 students to get their parent's name).
When you use includes, Rails eager loads all the associated models and attributes in one query. For more information, check out:
http://guides.rubyonrails.org/active_record_querying.html

Resources