Display a list of user's posts in Rails - ruby-on-rails

I have a rails app where users post reviews of albums. The reviews that are posted are called "pins" and are uploaded with a release date, album title, artist name, album cover image, and rank for the year. (as in my #1 favorite album of the year) The pins belong users and the users have many pins. What I want to do is create a new page for 2013 that displays each user, then lists a descending ordered list of the album image, title, artist as their top ten list for the year. Something like:
<% #users.each do |user| %>
<%= link_to (image_tag user.image(:small)), user %> <%= user.name %>
<ol>
<li><%= #pin.album %> - <% #pin.artist%></li>
</ol>
<% end %>
I need to limit the pins to only :date => "2013" and I need to list them in descending order.
I'm having trouble figuring out the controller and view. Here is my page controller so far:
def tens2013
#users = User.all
#pin = Pin.where(:date => "2013")
end
How should I set my controller to be able to call <%= #pin.user.album %> ?

The way I see it, your models should be set up like this:
class User < ActiveRecord::Base
has_many :pins
end
class Pin < ActiveRecord::Base
belongs_to :user
belongs_to :album
end
class Album < ActiveRecord::Base
has_many :pins
end
Pin should have user_id and album_id to achieve this.
Then in the controller, you can eager load all users and their pins, with each pin's respective album, like this:
#users = User.includes(:pins => :album)
Or to limit to a certain year, do:
#users = User.includes(:pins => :album).where('pins.date>=?','2013-01-01').references(:pins)
Now you can iterate through the users in your view, and through each user's pins, and each pin's album.
You don't need to use #pin for each user's pin. Make the necessary changes in your view, and iterate through them using this style:
#users.each do |user|
# do something
user.pins.each do |pin|
# Now you have "pin" and you can use it:
# pin.album...
# pin.artist...
end
end

To call #pin.user.album, you need to define the dependency first at the model level. So in the Pin model, you should have belongs_to :users and in the User model, you should include has_many :pins. Now this will assume that in the Pin model, there is a field called user_id, which will be the foreign key. Also use :include in the queries when you are going to access dependent classes like this. It avoids the N+1 query problem. eg:
#pin = Pin.includes(:users).where("date >= ?","2013-01-01")
To limit responses to only the year 2013, you may want to search your query likewise:
#pin = Pin.where("date >= ?","2013-01-01")

Related

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] } }

Ruby on Rails model association issue

Sorry for the vague title, but it's a little much to explain in a sentence.
I've got three models, User, Device, and DeviceMessage. Their relationships are fairly simple:
a User has_many :devices,
a Device belongs_to :user,
a Device has_many :device_messages,
and a DeviceMessage belongs_to :device.
Rails provides ways to start playing with these associations quickly, like the ability to get all device messages that belong to a certain user (from any device).
In order to do this, I defined a method in the User model:
class User < ActiveRecord::Base
...
has_many :devices, :as => : owner #Other entities may "own" a device
def device_feed
DeviceMessage.that_belong_to_user(self)
end
end
And I define the called method in the DeviceMessage model:
class DeviceMessage < ActiveRecord::Base
...
belongs_to :device
def self.that_belong_to_user(user)
device_ids = "SELECT owner_id FROM devices WHERE owner_id = :user_id
AND owner_type = \"User\""
where("device_id IN (#{device_ids})", user_id: user.id)
end
end
I define a user page where they can associate a device to their account (the device has a name as well), and upon adding the device to the account, it will add the name to a list of device names in a pane to the left, while showing the user's device feed much like a twitter feed (yes, I followed Michael Hartl's RoR tutorial). At this point it is important to note that I am using helper functions to keep track of the current user so I can display this information when a user visits the root_path while logged in. When visiting the root_path, the controller for the root_path is defined so that:
if user_signed_in?
#device_feed_items = current_user.device_feed.paginate(page: params[:page])
end
And this all works perfectly!
So... what's the issue? When I create a new user via the signup page, and associate the device via the device-association page, I am redirected to the root_path, the device name is correctly displayed in the left pane (which mean the device is correctly associated with the new user), but the device_feed is not displayed.
I've used the Rails console to verify that the device messages should be showing (User.find(2).devices.first.device_messages.first displays the first message associated with the first device that is newly associated with the 2nd user), so I know that I need to reach down into the database to get a fresh rather than cached copy of the current_user, but I'm confused because it seems like that should be happening every time the user.device_feed method is called because of it's use of where() which is a part of the query API...
Any ideas? Thanks in advance for any and all answers.
-MM
I am just wondering why you have the device_feed function. For your feed display could you not just a loop like this one, this is
class Device < ActiveRecord::Base
scope :in_new_message_order, :joins => :device_messages, :order => "created_at DESC"
end
Added a joined scope
class User < ActiveRecord::Base
...
has_many :devices, :as => : owner #Other entities may "own" a device
scope :in_sort_order, order("message_date DESC")
def device_feed
DeviceMessage.that_belong_to_user(self)
end
end
Above I have added a scope to sort your messages
<% user.devices.in_new_message_order.each do |device| %>
<% device.device_messages_in_sort_order.each do |message| %>
<%= ....render out the message %>
<% end %>
<% end %>

Activerecord relationship joins

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.

Fetching data using one-many association

I am new to ror. I have 2 tables for group (called 'ab') and sub-group(called 'cd').Each group has several sub-groups.I have defined belongs_to and has_many relationship.
Model ab.rb
class Ab < ActiveRecord::Base
has_many:cds
end
Model cd.rb
class Cd < ActiveRecord::Base
belongs_to :ab
end
ab and cd have 2 columns each called title and Dscr.Do I have to create a join table (ab_cd_join_table)
I want to display a particular group and its sub-groups in a view.
The controller for the view
class DisplayController < ApplicationController
def index
#ab = Ab.find_by_title("XXXXXX")
#cds = #ab.cds
for cd in #cds
logger.info cd.title
end
I am using this in the view.
display view
<%= #ab.title %>
I don't know how to display the title and Dscr of different sub-groups belonging to the group = "XXXXXX"
Thanks in advance
What I think you're asking for is this in the view:
<% #ab.cds.each do |cd| %>
<h1><%= cd.title %></h1>
<p><%= cd.description %></p>
<% end %>
and this in the controller:
#ab = Ab.find_by_title("XXXXXX")
That way you will display all cds for the ab-model matching "XXXXXX".
Update:
For belongs_to and has_many to work the model with belongs_to needs to have a column for the one that has has_many. In this case Cd needs to have a column named ab_id.
rails g migration add_ab_id_to_cds ab_id:integer
cd.ab_id needs to be the id of the corresponding Ab model.
cds = Cd.where(<something>)
cds.each do |cd|
cd.ab_id = #ab.id
cd.save
end
This maybe should be set upon creation of a Cd object, but just to test it out you can do like this.
Do I have to create a join table (ab_cd_join_table)
No in this case you don't need a join table, instead you need to add
ab_id column in cds table.(the foreign key column should be present in
table of model defining belongs_to assiciation)
Displaying subgroup title in view
<% #ab.cds.each |sub_group| %>
<%= sub_group.title -%>
<%= sub_group.description -%>
<%end%>
Also, if you alwas need sub_groups with group then load them in one query by using include option in find like
#ab = Ab.find_by_title("XXXXXX",:include=> :cds)
now you don't need to calculate cds explicitly just use the view code mentioned above

How to add more than one resource to this feed structure

I followed how to build a feed in Michael Hartl's tutorial. I have this method in my user model:
def feed
Video.from_users_followed_by(self)
end
and this in my video model:
scope :from_users_followed_by, lambda { |user| followed_by(user) }
def self.followed_by(user)
followed_ids = %(SELECT followed_id FROM relationships
WHERE follower_id = :user_id)
where("user_id IN (#{followed_ids})", { :user_id => user })
end
in order to retrieve all the videos created by the users followed by the current_user.
Then I have this method in my videos_controller:
def feed_display
#videos = current_user.feed.page(params[:page]).per(15)
end
and then I render the partial in my videos/feed_display.html.erb view:
<div id ='video_div'>
<%= render #videos, :collection => #videos %>
</div>
in order to display all the videos that are created by the users the user follows.
Now I want to add another resource to the feed so that it contains both videos and video_votes from the users that the user follows.
I'm thinking of replicating the model scope code that is my video model into my video_vote model and then adding this line:
VideoVote.from_users_followed_by(self)
to the feed method so that it looks like:
def feed
Video.from_users_followed_by(self)
VideoVote.from_users_followed_by(self)
end
My question is how should I modify my feed_display method and feed_display view so that it not only displays the videos but also the votes?
You could modify your SQL query to include the second model with the UNION statement.

Resources