Right now I have a like button that allows you to like foods. When you try to unlike the food, I get this error:
The action 'destroy' could not be found for UsersController
I'm not sure why it is looking for the destroy action in the users controller. My only guess is because the button is on the user show page, so I assume it defaults to that controller, but how would I access the delete method from my votes controller?
Shared like form
<% unless current_user.votes.empty? || current_user.votes.pluck(:food_id).include?(food.id) %>
<%= form_for #vote do |f| %>
<%= f.hidden_field 'food_id', food.id %>
<%= f.hidden_field 'user_id', food.user.id %>
<%= f.submit "Vote", :class => "like_button" %>
<% end %>
<% else %>
<% vote = food.votes.where(user_id: current_user.id).first %>
<div class="unlike_button">
<%= button_to "Unlike", vote, method: :delete %>
</div>
<% end %>
class VotesController < ApplicationController
def index
#votes = Vote.all
end
def new
#vote = Vote.new
end
def create
#vote = Vote.new(vote_params)
if #vote.save
puts #vote
flash[:notice] = "Thanks for voting!"
redirect_back(fallback_location: root_path)
else
puts "No"
flash[:notice] = "Something went wrong"
redirect_back(fallback_location: root_path)
end
end
def destroy
#vote = Vote.find(params[:id])
if #vote.destroy!
flash[:notice] = "Unvoted!"
redirect_back(fallback_location: root_path)
end
end
private
def vote_params
params.require(:vote).permit(:food_id, :user_id)
end
end
class Vote < ApplicationRecord
belongs_to :user
belongs_to :food
end
Related
I'm trying to make a twitter clone where a tweet displays the username of the user next to it. however getting the above error message and highlighting the first line of my create method. any ideas on how to solve.
I've done the association already.
thanks
class TweetsController < ApplicationController
def index
#tweets = Tweet.all.order("created_at DESC")
#tweet = Tweet.new
end
def show
#tweet = Tweet.find(params[:id])
end
def new
# #tweet = Tweet.new
end
def create
#user = User.find(params[:id])
#tweet = Tweet.new(tweet_params)
#tweet.user = #user
if #tweet.save
redirect_to tweets_path
end
end
private
def tweet_params
params.require(:tweet).permit(:content, :user_id)
end
end
tweets
<%= simple_form_for #tweet, id: "form-submit" do |f| %>
<%= f.input :content %>
<%= f.button :submit, class: "btn btn-danger" %>
<% end %>
<% #tweets.each do |tweet| %>
<ul>
<li>
<%= tweet.content %>
<%= tweet.user.username %>
</li>
</ul>
<% end %>
Your form doesn't have id field, so params[:id] is nil.
params hash does not contain id, because of that you are getting this error.
Just modify your create action as
def create
#user = current_user
#tweet = Tweet.new(tweet_params)
#tweet.user = current_user
if #tweet.save
redirect_to tweets_path
end
end
Or
instead of above you can also pass an user_id from the form.
<%= simple_form_for #tweet, id: "form-submit" do |f| %>
<%= f.input :content %>
<%= f.hidden_field :user_id, value: current_user.try(:id) %>
<%= f.button :submit, class: "btn btn-danger" %>
<% end %>
then modify create action as
def create
#tweet = Tweet.new(tweet_params)
if #tweet.save
redirect_to tweets_path
end
end
NOTE: Assuming that you are using devise gem for authentication
You are defining tweet.users after using tweet_params, since for tweet_params the user is not defined, that's why you are getting this error.
By answer accepted here, most appropriate change could be:
def new
#tweet = current_user.tweets.new
end
def create
#tweet = Tweet.new(tweet_params)
if #tweet.save
redirect_to tweets_path
end
end
nothing else was needed to be done.
I'm trying to create a comment on a video show page. When I submit the form, rails gives me a flash notice: "User must exist, Video must exist". Not sure why my strong params aren't going through to the create method.
comments_controller.rb
def create
#user = current_user
#video = Video.find(params[:video_id])
#comment = Comment.new(comment_params)
#post = #video.post
if #comment.save
flash[:notice] = "Comment successfully created"
redirect_to post_video_path(#post, #video)
else
#errors = #comment.errors.full_messages.join(', ')
flash[:notice] = #errors
render :'videos/show'
end
private
def comment_params
params.require(:comment).permit(
:body,
:user,
:video
)
end
models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :video
validates :body, presence: true
end
models/video.rb
class Video < ActiveRecord::Base
belongs_to :user
belongs_to :post
has_many :comments
end
views/videos/show.html.erb
<%= #video.title %>
<%= content_tag(:iframe, nil, src: "//www.youtube.com/embed/#{#video.embed_id}") %>
<%= link_to "Delete Video", post_video_path(#post, #video), method: :delete %>
<%= link_to('Back', user_post_path(#user, #post)) %>
<h3>Comments</h3>
<%= form_for [#video, #comment] do |f| %>
<%= f.label(:body, "Comment") %>
<%= f.text_area(:body) %>
<%= f.submit("Submit Comment") %>
<% end %>
<% unless #comments.nil? %>
<% #comments.each do |comment| %>
<%= comment.body %>
<%= comment.user %>
<% end %>
<% end %>
I tried adding this to the create method...
#comment.user = current_user
#comment.video = #video
That allowed the comment to save but instead of displaying the comment.body, it displayed the comment object. It still doesn't explain why the strong params aren't being passed.
This is probably a nested params issue and how you have the strong params defined. Check this answer for more information.
If you need to see what is in the params, insert a pry statement into the controller and inspect it there.
Good luck!
There are multiple things you should look at. I have made multiple changes to your code. Go through them.
In your videos/show.html.erb
<%= form_for Comment.new do |f| %>
<%= f.label(:body, "Comment") %>
<%= f.text_area(:body) %>
<%= f.hidden_field :video_id, :value => #video.id %>
<%= f.submit("Submit Comment") %>
<% end %>
Send video_id using hidden_field. Do not send current user id for security reasons. If you take current user id from form end user can easily edit your form in html and pass someone else's user id and this will be one of the easiest and major vulnerability.
In comments_controller.rb
def create
#comment = Comment.new(comment_params)
#comment.user = current_user # we are making sure that current_user is set to comment.
if #comment.save
flash[:notice] = "Comment successfully created"
redirect_to post_video_path(#comment.video.post, #comment.video)
else
#errors = #comment.errors.full_messages.join(', ')
flash[:notice] = #errors
render :'videos/show'
end
end
private
def comment_params
params.require(:comment).permit(:body, :user, :video_id)
# We are permitting video_id instead of video
end
I got this to work but I'm not sure if this addresses the security concerns that #Dinesh raised.
comments_controller.rb
def create
#user = current_user
#video = Video.find(params[:video_id])
#comment = Comment.new(comment_params)
#comment.user = #user
#comment.video = #video
#post = #video.post
if current_user == #video.user || current_user.admin
if #comment.save
flash[:notice] = "Comment successfully created"
redirect_to post_video_path(#post, #video)
else
#errors = #comment.errors.full_messages.join(", ")
flash[:notice] = #errors
render :"videos/show"
end
else
flash[:notice] = "Only OP or admin may comment"
render :"videos/show"
end
end
and
private
def comment_params
params.require(:comment).permit(
:body,
)
end
class PhootosController < ApplicationController
before_action :logged_in_user
def index
#phootos = Phooto.all.sample(1)
end
def new
end
def show
#phooto = Phooto.find(params[:id])
end
def create
#phooto = current_user.phootos.build(phooto_params)
if #phooto.save
flash[:success] = "Photo created!"
redirect_to uploads_url
else
redirect_to root_url
end
end
def favorite
#phooto = Phooto.find params[:id]
if request.put?
current_user.favorites << #phooto
redirect_to :back, notice: 'You successfully favorited #{#phooto.name}'
elsif request.delete?
current_user.favorites.delete(#phooto)
redirect_to :back, notice: 'You successfully unfavorited #{#phooto.name}'
else
redirect_to :back, notice: 'Nothing happened.'
end
end
def feed
#favorites = current_user.favorites.all
end
def uploaded
#phootos = current_user.phootos.all
end
private
def phooto_params
params.require(:phooto).permit(:picture)
end
end
show.html.erb
<p><strong>Picture:</strong></p>
<%= image_tag(#phooto.picture) %>
<%= link_to("< Previous", #phooto.previous) if #phooto.previous %>
<%= link_to("Next >", #phooto.next) if #phooto.next %>
<% if current_user %>
<%= link_to "favorite", favorite_phooto_path(#phooto), method: :put %>
<%= link_to "unfavorite", favorite_phooto_path(#phooto), method: :delete %>
<% end %>
www.example.com/photos/1
Professional websites have the homepage www.example.com load with a photo already displayed. Then when you click next/previous, you are routed to www.example.com/photos/#{photo_id}
How do I get my website to do this? I also want it setup so that each day a different random photo is displayed in the homepage.
If you want to show a random photo on the homepage, you need the following:
#config/routes.rb
resources :photos, only: [:index, :show]
#app/controllers/photos_controller.rb
class PhotosController < ApplicationController
def index
random = ActiveRecord::Base.connection.adapter_name == 'MySQL' ? "RAND()" : "RANDOM()"
#phooto = Photo.order(random).first
end
def show
#phooto = Photo.find params[:id]
end
end
Refs:
https://stackoverflow.com/a/2536743/1143732 (adapter)
https://stackoverflow.com/a/25577054/1143732 (random)
--
This will allow you to populate the #phooto variable in both the index and show actions;
#app/views/photos/index.html.erb
<%= render #phooto %>
#app/views/photos/show.html.erb
<%= render #phooto %>
#app/views/photos/_photo.html.erb
<p><strong>Picture:</strong></p>
<%= image_tag(photo.picture) %>
<%= link_to("< Previous", photo.previous) if #phooto.previous %>
<%= link_to("Next >", photo.next) if #phooto.next %>
<% if current_user %>
<%= link_to "favorite", favorite_phooto_path(phooto), method: :put %>
<%= link_to "unfavorite", favorite_phooto_path(phooto), method: :delete %>
<% end %>
You'll be able to work out the rest.
In regards rendering a new random image each day, you'll have to store the "random" value each time the day changes.
This would be best done with a cron job, invoking a rake task:
#lib/tasks/new_random.rb
namespace :random do
task :generate => :environment do
# save random for the day (maybe in a model or redis)
end
end
notifications/index has <%= render partial: "notifications/notification", collection: #notifications %>, which contains:
<%= link_to "", notifications_habit_path(notification.id), method: :delete, class: "glyphicon glyphicon-remove" %>
<%= link_to Comment.find_by(notification.comment_id).user.name, user_path(Comment.find_by(notification.comment_id).user.id) %>
commented on <%= link_to "your habit", habit_path(notification) %>
which shows:
This is problematic because it should say 3x ".com commented on your habit" and 2x ".com commented on your value".
We need to create two separate partials notifications/_habits & notifications/_values.
My confusion is how to make the code know when to direct to the habit partial or the value partial based on whether it's a habit or value.
notifications_controller
def index
#habits = current_user.habits
#valuations = current_user.valuations #aka values
#notifications = current_user.notifications
#notifications.each do |notification|
notification.update_attribute(:read, true)
end
The notifications are based on if a user comments on one of your habits or values:
comment.rb
class Comment < ActiveRecord::Base
after_save :create_notification
has_many :notifications
belongs_to :commentable, polymorphic: true
belongs_to :user
validates :user, presence: true
private
def create_notification
Notification.create(
user_id: self.user_id,
comment_id: self.id,
read: false
)
end
end
I followed this tutorial but it is based on using just one model: http://evanamccullough.com/2014/11/ruby-on-rails-simple-notifications-system-tutorial/
UPDATE FOR VALADAN
class CommentsController < ApplicationController
before_action :load_commentable
before_action :set_comment, only: [:show, :edit, :update, :destroy, :like]
before_action :logged_in_user, only: [:create, :destroy]
def index
#comments = #commentable.comments
end
def new
#comment = #commentable.comments.new
end
def create
#comment = #commentable.comments.new(comment_params)
if #comment.save
redirect_to #commentable, notice: "comment created."
else
render :new
end
end
def edit
#comment = current_user.comments.find(params[:id])
end
def update
#comment = current_user.comments.find(params[:id])
if #comment.update_attributes(comment_params)
redirect_to #commentable, notice: "Comment was updated."
else
render :edit
end
end
def destroy
#comment = current_user.comments.find(params[:id])
#comment.destroy
redirect_to #commentable, notice: "comment destroyed."
end
def like
#comment = Comment.find(params[:id])
#comment_like = current_user.comment_likes.build(comment: #comment)
if #comment_like.save
#comment.increment!(:likes)
flash[:success] = 'Thanks for liking!'
else
flash[:error] = 'Two many likes'
end
redirect_to(:back)
end
private
def set_comment
#comment = Comment.find(params[:id])
end
def load_commentable
resource, id = request.path.split('/')[1, 2]
#commentable = resource.singularize.classify.constantize.find(id)
end
def comment_params
params[:comment][:user_id] = current_user.id
params.require(:comment).permit(:content, :commentable, :user_id, :like)
end
end
Your notification is associated with comment, and comment can have commentable of type Habit or Value (you havent show those two model, so lets call them Habit and Value models).
So you can check if notification is for Habit or Value by checking commentable type like this:
Comment.find_by(notification.comment_id).commentable.class == Habit
or check if its value notification:
Comment.find_by(notification.comment_id).commentable.class == Value
Similar way is checking polymorphic type on the comment, like:
Comment.find_by(notification.comment_id).commentable_type == 'Habit'
So on the end, you dont actualy need two partials just IF and two different link_to, one for value and one for habit.
<%= link_to "", notifications_habit_path(notification.id), method: :delete, class: "glyphicon glyphicon-remove" %>
<%= link_to Comment.find_by(notification.comment_id).user.name, user_path(Comment.find_by(notification.comment_id).user.id) %> commented on
<% if Comment.find_by(notification.comment_id).commentable.class == Habit %>
<%= link_to "your habit", habit_path(notification) %>
<% else %>
<%= link_to "your value", value_path(notification) %>
<% end %>
I needed
<% if notification.habit_id %>
<%= link_to "your habit", habit_path(notification) %>
<% elsif notification.valuation_id %>
<%= link_to "your value", valuation_path(notification) %>
<% elsif notification.quantified_id %>
<%= link_to "your stat", quantified_path(notification) %>
<% elsif notification.goal_id %>
<%= link_to "your goal", goal_path(notification) %>
<% end %>
and in the comment model:
def create_notification
Notification.create(
habit_id: self.habit_id,
valuation_id: self.valuation_id,
quantified_id: self.quantified_id,
goal_id: self.goal_id,
user_id: self.user_id,
comment_id: self.id,
read: false
)
end
I have a table of venues and offers. Each venue can have have many offers.
I would like to be able to add the offers to the venues from the venues edit page. So far I have this (code below) but its giving a "NoMethodError in Venues#edit, undefined method `model_name' for NilClass:Class" error.
venues edit page
(the div id="tabs-3" is a container in an accordion)
<div id="tabs-3">
<%= form_for [#venue, #offer] do |f| %>
<h2 class="venue_show_orange">Offers</h2>
<%= f.fields_for :offers do |offer| %>
<div class="add_offer">
<%= offer.text_field :title %><br>
</div>
<div class="button"><%= submit_tag %></div>
<% end %>
<% end %>
</div>
offers controller
class OffersController < ApplicationController
def new
#offer = Offer.new
end
def create
#offer = #venue.offers.create!(params[:offer])
#offer.venue = #venue
if #offer.save
flash[:notice] = 'Offer added'
redirect_to offers_path
else
render :action => :new
end
end
def edit
#offer = Offer.find(params[:id])
end
def update
#offer = Offer.find(params[:id])
#offer.attributes = params[:offer]
if #offer.save!
flash[:notice] = 'Offer updated successfully'
redirect_to offers_path(#offer)
end
end
end
venues controller
(nothing offer related in here - is this where I'm going wrong?)
class VenuesController < ApplicationController
protect_from_forgery :only => [:update, :delete, :create]
load_and_authorize_resource
def new
#venue = Venue.new
5.times { #venue.venuephotos.build }
end
def create
#venue = Venue.new params[:venue]
if #venue.save
flash[:notice] = 'Venue added'
redirect_to venues_path
else
render :action => :new
end
end
def edit
#venue = Venue.find(params[:id])
5.times { #venue.venuephotos.build }
end
def update
#venue = Venue.find(params[:id])
#venue.attributes = params[:venue]
if #venue.save!
flash[:notice] = 'Venue updated successfully'
redirect_to :back
end
end
end
Any help is much appreciated thanks very much!
edit
venues edit page
<div id="tabs-3">
<%= form_for #venue do |f| %>
<div class="edit_venue_details">
<h2 class="venue_show_orange">Offers</h2>
<%= render :partial => 'offers/offer', :collection => #venue.offers %>
<div class="clearall"></div>
<h2 class="edit_venue_sub_header">Add a new offer</h2>
<%= f.fields_for :offers do |offer| %>
<% if offer.object.new_record? %>
<p class="edit_venue">title: <br>
<%= offer.text_field :title, :class => "edit_venue_input" %></p>
<% end %>
<% end %>
</div>
<button class="submit_button" type="submit"> Save changes</button>
<% end %>
</div>
whats being displayed
however if I add a new offer, that will display correctly:
Some remarks:
1) Replace:
<%= form_for [#venue, #offer] do |f| %>
with:
<%= form_for #venue do |f| %>
Because offers data will be updated through the related venue, only one controller action will handle the form.
2) If you want to add some unexisting offers in this form, you shoud instantiate them the way you did with venuephotos
3) Show your Venue model. You should have at least:
accepts_nested_attributes_for :offers
Fixed with:
<%= f.fields_for :offers, #venue.offers.build do |offer| %>