Activerecord relationship joins - ruby-on-rails

I'm working in Rails and Activerecord and trying to merge some data from related tables together in my view, here are my models:
class Report < ActiveRecord::Base
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :reports
end
class User < ActiveRecord::Base
has_many :votes
end
Each vote has a user and a report.
In my view I need the following, hopefully as easily as possible:
a total number of votes for each report from all users
a true/false if the user has voted on the particular report
Right now, my basic understanding of ActiveRecord queries only takes me as far as creating a helper with the report and the current user and than querying for the existence of report
Same goes for counting the total number of votes for all users for a report as follows:
Controller
def index
#this is where I need some help to get the related information into a single
#object
#reports = Report.where('...')
end
View
<% #reports.each do |report| %>
<% if(hasVoted(#current_user.id, report.id)) %>
<!-- display the 'has voted html' -->
<% end %>
<% end %>
Helper
def hasVoted(current_user_id, report_id)
if(Vote.exists?(:user_id => current_user_id, :report_id => report_id))
true
else
false
end
end
Hope that gives you some insight into helping...thanks!

Sure.
Firstly, please consider naming your method has_voted? instead of hasVoted. Secondly, consider moving that method in the user model.
#user.rb
def voted_on?(report_id)
votes.where(:report_id => report_id).exists?
end
Your view will then read
<% if current_user.voted_on?(report) %>
...
<% end %>
The other question you had was to find the number of votes a report has received. This is simple too. You could do this in your view inside the loop where you iterate over #reports
<% vote_count = report.votes.size %>
Please keep in mind that his would result in N queries (where N = number of reports). Since you are new to Rails i'm not going to complicate your Reports query in the controller where you fetch you reports to include the vote count (unless you ask me to). But once you are comfortable with what happening in here, thats where you would optimize.

Related

Showing all nested models in Rails 4

I'm new to Rails and I'm teaching myself how to use it by working on my own project.
I have a model called Users that has a one-to-many relationship with a model called Pets. The Pets model has a belongs_to relationship to User. I'm trying to build a page where a user can see the list of all Pets. How can I go about doing that?
Thanks.
I agree with Tamer Shlash. It sounds like you'd benefit from practicing a tutorial to get more exposure to model-view-controller (MVC) concepts, and interacting with a database.
Since you want all users to see all pets, Pet.all will return all rows of the pets table.
In a controller action, assign #pets to Pet.all then use that instance variable to loop through the pets and display information about them.
<ul>
<%= #pets.each do |pet| %>
<li><%= pet.name %></li>
<% end %>
</ul>
You'll be best looking at the Rails getting started guide to see how to do this properly. Basically, everything you want to achieve in Rails is done using the MVC programming pattern - each view takes data from the controller, which builds that data from its models
Here is a basic example for you:
Models
#app/models/pet.rb
Class Pet < ActiveRecord::Base
belongs_to :user
end
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :pets
end
--
Routes
#config/routes.rb
resources :pets #-> /pets
--
Controllers
#app/controllers/pets_controller.rb
def index
#pets = Pet.all
end
--
Views
#app/views/pets/index.html.erb
<% #pets.each do |pet| %>
<%= pet.name %>
<% end %>

cascading relational models Ruby on Rails

I have three models
User has_many :articles
Article has_many :daily_view_metrics & belongs_to :user
DailyViewMetric belongs_to :article
I want each user to have an overview of the metrics for their articles. At the moment I pass #user = User.find(params[:id]) in the show action of the users controller to open a specific users's show page. I also pass in #articles = Article.where(user_id: params[:id]).load in order to limit the available articles to only the user's articles.
Since the DailyViewMetric model has the same article multiple times (at different days) I need to aggregate them into a new array of arrays. I.e. I need
article_id date views
1 feb-01 20
1 feb-02 50
2 feb-01 30
to be returned as [[1,70],[2,30]] so I can sort it according to user wishes. How do I do this? Do I make a named scope in the DailyViewMetric model? or in the user_helper.rb?
It would be nice if I could have something that I can run newArray.each do |a| on in my view to make a table with stuff like <%= a.article_id %> and <%= sumviews %>, etc.. with something I can pass in the user_id so the aggregate is limited to his/her articles
You should be able to do it in the following way
Long form:
views_array = []
#articles.each do |a|
a.views do |v|
views_array << [a.id, v.date, v.count]
end
end
Short form:
#articles.collect {|a| a.daily_view_metrics.collect {|dv| [a.id, dv.date, dv.count] } }

Finding user object in view? Ruby on Rails

<% #review.each do |review|%>
<% if review.host_id == #host.id>
<%= #user = User.find(review.user_id) %>
<% end %>
<% end %>
So I'm a bit confused. I have a few things going on here. I'm doing a loop through all reviews of hosts and then checking if the stored host.id value is equal to the active #host object's id that is passed from the controller. Problem is.. Now I need get the user object from the user ID stored in the review but, I'm unsure exactly how to do it. I can't do it from the controller as all this is done in the loop. As you can see I tried to do it with the code above but, I highly doubt I did it right. Please help me out on this. Thanks.
You should pre-load users with loading reviews, in controller. First, you should have belongs_to association, like this:
class Review < ActiveRecord::Base
belongs_to :user
# ...
end
then, in controller, you could use includes, this way:
#reviews = Review.includes(:user)
Now, for every review record in #reviews relation, to get associated user you can call user method, like this:
review.user
What's more, (and that's advantage of using includes) it doesn't fire new SQL query for every single review, so you avoid quite common N + 1 problem.
You can make a relationship in
class Review < ActiveRecord::Base
belongs_to :user
end
and then in view
review.user #gives you user
Put association in Review Model
class Review < ActiveRecord::Base
.
.
.
belongs_to :user
.
.
.
end
After putting association you can directly call association to find user object using Review object.
review.user
But this will raise N+1 query problem, so better user include user while finding review, It will execute only two queries one for finding reviews and another for finding users.
#reviews = Review.includes(:user)

Testing for lack of associated objects in Ruby on Rails

This follows on from an earlier question as I am bending my brain around Ruby on Rails.
I have items which are displayed on a webpage, depending on whether their status allows the display or not, using a named scope - if the document status ("For Sale", "Sold", "Deleted" etc) has the show_latest_items flag set to 1, it will allow associated items to be displayed on the page :
class Item < ActiveRecord::Base
belongs_to :status
scope :show_latest_items, joins(:status).where(:statuses => {:show_latest_items => ["1"]})
end
class Status < ActiveRecord::Base
has_many :items
end
This is how it is displayed currently
<% latest_items = Items.show_latest_items.last(30) %>
<% latest_items.each do |i| %>
:
<% end %>
So this is all well and good, but I now want to only display the item if it has an associated photo.
class Item < ActiveRecord::Base
has_many :item_photos
end
class ItemPhoto < ActiveRecord::Base
belongs_to :item
end
So in my mind, I should, using the named scope, be able to pull back a list of Items for display, and then filter them using .present? or .any? methods. Curious thing is this:
<% latest_items = Items.show_latest_items.where(:item_photos.any?).last(30) %>
returns an error:
undefined method `any?' for :item_photos:Symbol
Whereas:
<% latest_items = Items.show_latest_items.where(:item_photos.present?).last(30) %>
doesn't error, but it doesn't filter out items with no photos, either.
I've tried various other methods, as well as trying to do custom finders, writing names scopes for photos, but nothing is making a lot of sense. Should I be approaching this from a different angle?
:item_photos.any?
This doesn't work because Ruby's Symbol has no any? method.
.where(:item_photos.present?)
This doesn't do the filtering you're after because you're calling .present? on the Symbol :item_photos which evaluates to true, making the condition really
.where(true)
Try simply
<% latest_items = Items.show_latest_items.joins(:item_photos).last(30) %>
The SQL for this .joins(:item_photos) is going to be an INNER JOIN, causing Item instances with no associated ItemPhoto instances to be omitted from the result.

How do I make an method for an object in rails?

Hey guys, so I have two models in my project, grinders, and votes.
So each grinder has many votes, and votes belongs to grinder.
The votes table has 3 columns: grinder_id:integer, choice:string, and voter_ip:string
How and WHERE can I make a method for my grinders? I want to be able to do something like
<% #grinders.each do |grinder| %>
<%= grinder.votes_up %>
<% end %>
Where do I define this?
def self.votes_up
grinder.votes.find(:all, :choice => "up").count
end
If that is the right way to do it, correct me if I'm wrong, please.
inside app/models/grinders.rb you should write
class Grinder < ActiveRecord::Base
def votes_up
count "choice = 'up'"
end
end

Resources