Rails: How to check if user has previously bookmarked pin? - ruby-on-rails

In my app, I have pins (posts), users and bookmarks. Each user can bookmark a post for later reading. This all works great.
However, I am stuck on how to show if the user has previously bookmarked the pin, on a list of all pins? For each pin in the list, I want to do a check in my view to see if they have bookmarked it.
pin.rb (model)
class Pin < ActiveRecord::Base
belongs_to :user
has_many :bookmarks, dependent: :destroy
end
user.rb (model)
class User < ActiveRecord::Base
has_many :pins, dependent: :destroy
has_many :bookmarks, through: :pins, dependent: :destroy
end
bookmark.rb (model)
class Bookmark < ActiveRecord::Base
belongs_to :user
belongs_to :pin
validates :pin_id, uniqueness: { scope: :user_id, :message => "has already been bookmarked" }
end
A user can bookmark a pin, which creates a single record in the bookmarks table. The bookmarks table stores the user_id and the pin_id.
I'm having a bit of a brain melt-down on how I would go about checking in the view, if a user has bookmarked a pin or not. I'd basically just like to show a yes or no flag to the user.
Any guidance or advise would be greatly appreciated.
Thanks,
Michael.
UPDATE:
I ended up modifying the anser by Lucas. In my Pin model I defined a boolean method:
def bookmarked_by?(user)
return true if bookmarks.any? {|b| b.user == user }
end
...that will return true if any bookmarks belong to the given user on this pin. Seems to work okay, but welcome any additional improvements.

Well I'd suggest that when you query Pins, include their Bookmarks that are associated with the current user. And then check if the Pin has any bookmarks.
pins = Pin.includes(:bookmarks).where("bookmarks.user_id" => current_user.id)
Now when looping these pins to view them, check whether the pin.bookmarks.length is 0 or more. And on that basis you can view yes or no.
<% pins.each do |p| %>
<%= "yes" if p.bookmarks.length > 0 %>
<% end %>

What I would do, to build slightly on #Tamer's answer is to create a method on the Pin model:
class Pin < ActiveRecord::Base
# ...
def bookmarked?
!bookmarks.empty?
end
end
Then you can simply do this in the view:
<% pins.each do |p| %>
<%= "yes" if p.bookmarked? %>
<% end %>
The main reason for this is it encapsulates the logic for understanding whether or not something is bookmarked to the thing that cares about that information, which is the pin. It also keeps logic out of the view, which is super important for clean and maintainable views.
What if, for example, you wanted to check and show pins as bookmarked only if bookmarked by the current user, you could do something like the following:
class Pin < ActiveRecord::Base
# ...
def bookmarked_by?(user)
return false unless bookmarked?
bookmarks.any? {|b| b.user == user }
end
end
I don't like the bookmarks.any? call as I think it will cause another db query, and I think there's a better way to do it, offhand, I'm unsure. I'll update once I find it.
However, in the view, you can now do:
<% pins.each do |p| %>
<%= "yes" if p.bookmarked_by? current_user %>
<% end %>
Update:
Was given an incredibly efficient way for distilling the query down into a COUNT query:
class Pin < ActiveRecord::Base
def bookmarked_by?(user)
bookmarks.for_user(user).any?
end
end
class Bookmark < ActiveRecord::Base
scope :for_user, ->(user) { where(user: user) }
end
pins = Pin.includes(:bookmarks) # ... any additional conditions
<% pins.each do |pin| %>
<%= 'yes' if pin.bookmarked_by? current_user %>
<% end %>
See the question I posted here with the brilliant answer from #pdobb: How does Rails handle a has_many when query uses includes?

Maybe this?
<%= "yes" if Bookmark.where(pin_id: pin.id, user_id: current_user.id).first %>
Probably cleaner to make a helper method, or maybe a Pin class method
class Pin
def bookmarked?(user)
!!Bookmark.where(pin_id: id, user_id: user.id).first
end
...
in the view...
<%= "yes" if pin.bookmarked?(current_user) %>

Related

Collection_check_box usage in RoR

I'm a relative novice to rails. I have a process that runs and finishes with a status (five possible different statuses). The process is run per-building. (We collect data from buildings.) I'm trying to allow users to configure themselves to receive notification email with fine-grained control: Per-building and complettion status. I'm trying to use collection_check_boxes to create a table of checkboxes for the user to select, but I'm not even sure collection_check_boxes was designed for such a case. I would be very happy to hear a yes or no on that question to start with.
I have the following modles:
class Building < ActiveRecord::Base
self.primary_key = 'building_id'
has_many :etl_status
has_many :email_notifications
end
class BuildingUserPair < ActiveRecord::Base
self.primary_key = "building_user_pairs_id"
belongs_to :building
belongs_to :user
has_many :email_notification_settings
end
class EmailNotificationSetting < ActiveRecord::Base
self.primary_key = "email_notification_settings_id"
belongs_to :building_user_pair
end
class EtlResult < ActiveRecord::Base
self.primary_key = 'etl_results_id'
# table has 5 rows, with values 1 -5 with the statuses "HUNKY DORY", "MO BETTA", "LIMPING ALONG", "DISMAIL FAILURE" and "UNEXPECTEDLY IDLE"
end
class EtlResultNotification < ActiveRecord::Base
belongs_to :building
belongs_to :user
end
class EtlStatus < ActiveRecord::Base
self.primary_key = 'etl_status_id'
belongs_to :building
has_many :users, :through => :email_notifications
end
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#buildings = Building.active
#bup = []
#email_notifications_settings = []
#buildings.each do |bldg|
bup = BuildingUserPair.where(user_id: params[:id], building_id: bldg.building_id).first_or_create
#email_notifications_settings[bldg.building_id] =
EmailNotificationSetting.where(building_user_pairs_id: bup.building_user_pairs_id).all
end
end
my users/show.html.erb contains this:
<%= form_for #user do |f| %>
<% #buildings.each do |bldg| %>
<div class="row">
<div class="col-md-4">
<%= bldg.name %>
</div>
<%= f.fields_for :email_notification_settings do |ens| %>
<%= ens.collection_check_boxes( #email_notifications_settings[bldg.building_id],
EtlResult.all, :etl_result_id, :result_name) %>
</div>
<% end %>
<% end %>
<div class="form-buttons">
<%= submit_tag("Update", data: { buildings: #buildings}) %>
</div>
The etl_result_notification table has just two columns, besides
it's primary key, a building-user-pair column and then a number,
1-5 that is a foreign key to the Etl Results table. Thus the idea is that a new line gets
created for a checkbox, and if a checkbox is newly unchecked, the row in the table is deleted.
Like I said, not even sure if form_for and collection_check_boxes was even designed to do this.
My problem is that the checkboxes are not being properly initialized. They all come up unchecked.
I'm guessing I need to pass other paraemters to collection_check_boxes, but I can't think
what they should be.
TIA
I think that you are over complicating your question,
what you want to do is save a list of id that march to the lest of emails the user want to get.
Rubyist posted https://stackoverflow.com/a/23340368/1380867 witch is a good example of what you want to do.
you should create a serialize :email_notification_ids
#MODEL
class User < ActiveRecord::Base
serialize :email_notification_ids
end
Hope this helps
Happy Codding

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 has_many relationship find name based on foreign key

I have two models:
class country < ActiveRecord::Base
has_many :companies
end
class company < ActiveRecord::Base
belongs_to :country
end
In my view index for company I can show which country the company belongs to by showing the following:
<%= company.country_id %>
This will show me the id number it's associated with, but I can't seem to work out how to resolve this back to the country name which is country.name, everything I seem to try crashes rails, I don't think I'm approaching the problem the correct way?
<%= company.country.try(:name) %>
really ought to do what you want.
Edited as comment suggested.
I wouldn't recommend using #try though. It is much better when you tell Rails to give you the filtered collection, that's what controllers are for.
In this case you'd have
class CompaniesController < ApplicationController
def index
#companies = Company.where('country_id IS NOT NULL') #if using the relational db
# or #companies = Company.all.select { |company| company.country.present? }
end
end
and in your view
<% #companies.each do |company| %>
<%= company.country.name %>
<% end %>
Update:
Even better aproach is to create a named scope in your model for such things.
class Company < ActiveRecord::Base
#...
scope :with_country, ->() { where('country_id IS NOT NULL') }
end
Now you can change your controller action
def index
#companies = Company.with_country
end
This will make your code much more consistent and readable.

Limiting a search to records from last_request_at

I am trying to figure out how to display a count for records that have been created in a table since the last_request_at of a user.
In my view I am counting the notes of a question with the following code:
<% unless #questions.empty? %>
<% #questions.each do |question| %>
<%= h(question.notes.count) %>
end
end
This is happening in the /views/users/show.html.erb file. Instead of counting all the notes for the question, I would only like to count the notes that have been created since the users last_request_at datetime. I don't neccessarily want to scope notes to display this 'new notes' count application wide, just simply in this one instance.
To accomplish I am assuming I need to create a variable in the User#show action and call it in the view but not really sure how to do that.
Other information you may need:
class Note < ActiveRecord::Base
belongs_to :user
belongs_to :question
end
class Question < ActiveRecord::Base
has_many :notes, :dependent => :destroy
belongs_to :user
end
Just create a named scope and then use it only when it applies:
class Note < ActiveRecord::Base
named_scope :added_since, lambda { |time| {
:conditions => time && [ 'created_at>=?', time ]
}}
end
This should only enforce conditions if a time is provided. If you submit a nil time, the default behavior is to scope all notes.
This way you can do something along the lines of:
#new_notes = #user.notes.added_since(#user.last_login_at)
Adding a named scope does not alter the default scope.

Resources