Implement "add to favorites" - ruby-on-rails

I am creating an app where a user can favorite a room. I started with a has_and_belongs_to_many association. But then I just noticed that it is very tricky to implement a remove button with drestroy. So I decided to do it this time with a has_many through association. I have a users who should add rooms to favorite or wishlist. Unfortunately when I click on the favorite button I am getting this error:
What I am missing how can I make this work?
If you need further information just let me know. I have used this as a direction.
Implement "Add to favorites" in Rails 3 & 4
favorite_room.rb
class FavoriteRoom < ApplicationRecord
belongs_to :room
belongs_to :user
end
room.rb
belongs_to :user
has_many :favorite_rooms
has_many :favorited_by, through: :favorite_rooms, source: :user
user.rb
has_many :rooms
has_many :favorite_rooms
has_many :favorites, through: :favorite_rooms, source: :room
routes.rb
resources :rooms do
put :favorite, on: :member
end
rooms_controller.rb
before_action :set_room, only: [:show, :favorite]
...
...
def favorite
type = params[:type]
if type == "favorite"
current_user.favorites << #room
redirect_to wishlist_path, notice: 'You favorited #{#room.listing_name}'
elsif type == "unfavorite"
current_user.favorites.delete(#room)
redirect_to wishlist_path, notice: 'Unfavorited #{#room.listing_name}'
else
# Type missing, nothing happens
redirect_to wishlist_path, notice: 'Nothing happened.'
end
end
private
def set_room
#room = Room.find(params[:id])
end
show.html.erb
<% if current_user %>
<%= link_to "favorite", favorite_room_path(#room, type: "favorite"), method: :put %>
<%= link_to "unfavorite", favorite_room_path(#room, type: "unfavorite"), method: :put %>
<% end %>
create_favorite_rooms.rb (migration file)
class CreateFavoriteRooms < ActiveRecord::Migration[5.0]
def change
create_table :favorite_rooms do |t|
t.integer :room_id
t.integer :user_id
t.timestamps
end
end
end

Related

Attributes of referenced user are all nil

I have a post that can accept comments and those comments belong to the user and the post. I can successfully create a comment and it will be associated with the post and the user (checked that by going into console, the user_id and post_id is there), but when I try to call a user's attribute (such as user.username) it does not work. If I call a post's attribute (such as post.body) it works. When making the comments model I have referenced the user and post in my migration. In my user.rb I have also tried using has_many :comments, through: :posts but that did not work.
user.rb
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
end
comment.rb
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
end
post.rb
class Post < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
end
comments_controller.rb
class CommentsController < ApplicationController
before_action :findpost
def create
#comment = #post.comments.build(comment_params)
#comment.user_id = current_user.id
if #comment.save
redirect_to post_path(#post)
else
flash[:alert] = "Check the comment form"
end
end
def comment_params
params.require(:comment).permit(:body)
end
private
def findpost
#post = Post.find(params[:post_id])
end
end
The migration
class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
t.string :body
t.references :user, null: false, foreign_key: true
t.references :post, null: false, foreign_key: true
t.timestamps
end
end
end
The specific error I get
undefined method `username' for nil:NilClass
Where comment.user.username is being called
<%= render partial: 'comments/form' %>
<% if #post.comments.count > 0 %>
<%= #post.comments.each do |comment| %>
<div>
<%= link_to author_path(id: comment.user.username) do %>
<%= comment.user.username %>
<p><%= comment.body %></p>
</div>
<% end %>
<% end %>
This error is at comment.user.username. The main problem is that whatever attribute that I call for comment.user it always returns nil (even something like id).

Rails 4: "wrong number of arguments (2 for 1)"

I know this is a pretty standard error, but I could not figure out a solution to this particular solution from other questions.
I am following this coderwall tutorial about Creating a Scoped Invitation System for Rails.
I have four models, as follows:
class User < ActiveRecord::Base
has_many :administrations
has_many :calendars, through: :administrations
has_many :invitations, :class_name => "Invite", :foreign_key => 'recipient_id'
has_many :sent_invites, :class_name => "Invite", :foreign_key => 'sender_id'
end
class Calendar < ActiveRecord::Base
has_many :administrations
has_many :users, through: :administrations
has_many :invites
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Invite < ActiveRecord::Base
belongs_to :calendar
belongs_to :sender, :class_name => 'User'
belongs_to :recipient, :class_name => 'User'
end
And this is the migration for the Invite model:
class CreateInvites < ActiveRecord::Migration
def change
create_table :invites do |t|
t.string :email
t.integer :calendar_id
t.integer :sender_id
t.integer :recipient_id
t.string :recipient_role
t.string :token
t.timestamps null: false
end
end
end
The goal of the Invite model is to allow Users to invite other Users to join a particular Calendar.
The create Invite form is embedded in the Calendar edit view, as follows:
<h2>Edit <%= #calendar.name %> calendar</h2>
<%= render 'form' %>
<h2>Invite new users to <%= #calendar.name %> calendar</h2>
<%= form_for #invite , :url => invites_path do |f| %>
<%= f.hidden_field :calendar_id, :value => #invite.calendar_id %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label "Role" %>
<%= f.radio_button(:recipient_role, "Editor") %>
<%= f.label "Editor" %>
<%= f.radio_button(:recipient_role, "Viewer") %>
<%= f.label "Viewer" %>
<%= f.submit 'Send' %>
<% end %>
<%= link_to 'Show', calendar_path %> |
<%= link_to 'Back', calendars_path %>
Here is the corresponding Calendars#Edit:
def edit
#user = current_user
#invite = #calendar.invites.build
authorize #calendar
end
And here is the InvitesController:
class InvitesController < ApplicationController
def create
#invite = Invite.new(invite_params) # Make a new Invite
#invite.sender_id = current_user.id # set the sender to the current user
if #invite.save
InviteMailer.invite(#invite, new_user_registration_path(:invite_token => #invite.token)).deliver #send the invite data to our mailer to deliver the email
else
format.html { render :edit, notice: 'Invitation could not be sent.' }
end
end
private
def invite_params
params.require(:invite).permit(:email)
end
end
Last but not least, here is the InviteMailer:
class InviteMailer < ApplicationMailer
def invite(invite)
#link = new_user_registration_path invite_token: invite.token
mail to: invite.email, subject: "Calendy Invitation"
end
end
When I visit http://localhost:3000/calendars/3/edit and submit the Invite create form, I get the following error:
ArgumentError in InvitesController#create
wrong number of arguments (2 for 1)
class InviteMailer < ApplicationMailer
def invite(invite)
#link = new_user_registration_path invite_token: invite.token
mail to: invite.email, subject: "Calendy Invitation"
end
My instinct would be to replace:
InviteMailer.invite(#invite, new_user_registration_path(:invite_token => #invite.token)).deliver
With:
InviteMailer.invite(#invite).deliver
But I am not sure this is actually the right solution.
Any idea about how to fix this error?
May be changing the invite to allow an extra argument like below should work too
class InviteMailer < ApplicationMailer
def invite(invite, link)
#link = link
mail to: invite.email, subject: "Calendy Invitation"
end
end
My instinct would be to replace:
InviteMailer.invite(#invite, new_user_registration_path(:invite_token => #invite.token)).deliver
With:
InviteMailer.invite(#invite).deliver
Yes, that would be fine, since you're doing the same - your InviteMailer#invite stores this data itself in #link variable:
class InviteMailer < ApplicationMailer
def invite(invite)
#link = new_user_registration_path invite_token: invite.token
mail to: invite.email, subject: "Calendy Invitation"
end
end

Comments on multiple models

Within my rails app, I currently have comments setup to work with my posts model, which is functioning properly. How do I add comments to my books model?
Here is what I have so far:
Here is what I have in my schema for the comments:
create_table "comments", force: true do |t|
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
t.integer "post_id"
t.integer "book_id"
end
In my user model:
class User < ActiveRecord::Base
has_many :comments
acts_as_voter
end
In my post model:
class Post < ActiveRecord::Base
has_many :comments
end
In my book model:
class Book < ActiveRecord::Base
has_many :comments
end
In my comment model:
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :book
belongs_to :user
acts_as_votable
end
In my comments controller:
class CommentsController < ApplicationController
def create
post.comments.create(new_comment_params) do |comment|
comment.user = current_user
end
respond_to do |format|
format.html {redirect_to post_path(post)}
end
end
def upvote
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
#comment.liked_by current_user
respond_to do |format|
format.html {redirect_to #post}
end
end
private
def new_comment_params
params.require(:comment).permit(:body)
end
def post
#post = Post.find(params[:post_id])
end
end
In my routes file:
resources :posts do
resources :comments do
member do
put "like", to: "comments#upvote"
end
end
end
In my view:
<% #post.comments.each do |comment| %>
<%= comment.body %>
<% if user_signed_in? && (current_user != comment.user) && !(current_user.voted_for? comment) %>
<%= link_to “up vote”, like_post_comment_path(#post, comment), method: :put %>
<%= comment.votes.size %>
<% else %>
<%= comment.votes.size %></a>
<% end %>
<% end %>
<br />
<%= form_for([#post, #post.comments.build]) do |f| %>
<p><%= f.text_area :body, :cols => "80", :rows => "10" %></p>
<p><%= f.submit “comment” %></p>
<% end %>
What do I add to my comments controller to get comments working on both posts and books? What do I add to my routes file?
Thanks in advance for any help.
You don't want to specify each type of object that can hold Comment objects. That creates a headache of if-elsif-else blocks all over the place. Instead, you want things to be Commentable, and they all will have .comments on them.
This is called a polymorphic association in Active Record. So you would have your models something like:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
class Post < ActiveRecord::Base
has_many :comments, as: :commentable
end
class Book < ActiveRecord::Base
has_many :comments, as: :commentable
end
And modify your database accordingly, it's all in the linked article. Now when you build a Comment object for a form, it will have pre-populated a commentable_id and commentable_type, which you can toss in hidden fields. Now it doesn't matter what the Comment is associated with, you always treat it the same.
I'd leave User as a separate association, since it's not really the same idea.

rails undefined method `reviewer' for #<Review

I'm creating review to my posts, all works but i dont know how to show who wrote the review
i'm trying this:
class User < ActiveRecord::Base
has_many :posts
has_many :reviewers, :class_name => 'Post', :foreign_key => 'reviewer_id'
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :category
has_many :reviews
belongs_to :reviewers, class_name: 'User', :foreign_key => 'reviewer_id'
default_scope -> { order('created_at DESC') }
end
class Review < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
class ReviewsController < ApplicationController
def new
#post = Post.find(params[:post_id])
#review = #post.reviews.new(post_id:params[:post_id])
end
def create
#post = Post.find(params[:post_id])
#review = #post.reviews.build(review_params)
if #review.save
flash[:success] = "Ваш отзыв добавлен"
redirect_to post_path #post
else
render 'new'
end
end
private
def review_params
params.require(:review).permit(:post_id, :body, :reviewer_id).merge(:reviewer_id => current_user.id)
end
end
and my view
<% #post.reviews.each do |review| %>
<p>
<strong>reviewer:</strong>
<%= review.reviewer.email %>
</p>
<p>
<strong>review:</strong>
<%= review.body %>
</p>
<% end %>
my migration
class CreateReviews < ActiveRecord::Migration
def change
create_table :reviews do |t|
t.text :body
t.references :post, index: true
t.references :reviewer, index: true
t.timestamps
end
end
end
but rails given error undefined method `reviewer' for #
Help please dsfsdf
I think that you have a pluralization issue:
A post have many reviews by many reviewers (one for each review). But you are storing the foreign key within the post so you written the problematic line:
belongs_to :reviewers, class_name: 'User', :foreign_key => 'reviewer_id'
The issue here is that it is a singular association with a plural name.
I think that you are trying to say here that a
class Post
have_many :reviewers, class_name: 'User', through: :reviews
end
But as you are trying to access the reviewers from the review what you really need is to add:
class Review
belongs_to :reviewer, class_name: 'User'
end
Then you can access the reviewers from the review as expected:
<% #post.reviews.each do |review| %>
<p>
<strong>reviewer:</strong>
<%= review.reviewer.email %>
</p>
<p>
<strong>review:</strong>
<%= review.body %>
</p>
<% end %>
There is also an error in User:
has_many :reviewers, :class_name => 'Post', :foreign_key => 'reviewer_id'
As it should be:
has_many :reviews, :foreign_key => 'reviewer_id'
You need to be using .user. Check the belongs_to in your model.
Review.first.user
As a previous poster pointed out, your user association for Review is :user, so your view should probably look like this:
<% #post.reviews.each do |review| %>
<p>
<strong>reviewer:</strong>
<%= review.user.name unless review.user.nil? %>
</p>
<p>
<strong>review:</strong>
<%= review.body %>
</p>
<% end %>

Rails Favorites Model Association, link_to not producing html

I am trying to implement a favorite relationship in my Ruby on Rails app. The route, controller, and relationship all appear to be working but link_to "favorite" is not working, by which I mean it is not even producing the html link, though it is throwing no error. I am following the example here Implement "Add to favorites" in Rails 3 & 4.
Here is the code:
routes.rb
resources :locations do
put :favorite, on: :member
end
locations_controller.rb
class LocationsController < ApplicationController
....
def favorite
type = params[:type]
if type == "favorite"
current_user.favorites << #location
redirect_to :back, notice: 'You favorited #{#location.name}'
elsif type == "unfavorite"
current_user.favorites.delete(#location)
redirect_to :back, notice: 'Unfavorited #{#location.name}'
else
# Type missing, nothing happens
redirect_to :back, notice: 'Nothing happened.'
end
end
end
user.rb
class User < ActiveRecord::Base
....
# Favorite locations of user
has_many :favorite_locations # just the 'relationships'
has_many :favorites, through: :favorite_locations # the actual recipes a user favorites
....
end
location.rb
class Location < ActiveRecord::Base
....
# Favorited by users
has_many :favorite_locations # just the 'relationships'
has_many :favorited_by, through: :favorite_locations, source: :user
end
view/locations/show.html.erb
<% provide(:title, #location.name) %>
....
<% link_to "favorite", favorite_location_path(#location, type: "favorite"), method: :put %>
<% link_to "unfavorite", favorite_location_path(#location, type: "unfavorite"), method: :put %>
Looks like you forgot the = in the <% %>, should be:
<%= link_to "favorite", favorite_location_path(#location, type: "favorite"), method: :put %>

Resources