I have some problems with destroy action in my Rails app.
In my app I have model UserVotes, which allows users vote for each other. For example: John votes for other users in the order:
Vote for User_1
Vote for User_2
Vote for User_3
When John wants to delete his vote for User_3 he is deleting vote for User_1, after retrying he's deleting vote for User_2, and only after two attempts he's deleting vote for User_3
user_votes_controller:
class UserVotesController < ApplicationController
def destroy
#user_vote = UserVote.find_by(params[:recipient_uid])
#user_vote.destroy
redirect_to root_url
flash[:warning] = 'Deleted'
end
end
view:
= link_to('Delete vote', user_vote, author_uid: current_user.uid, method: :delete)
I can see couple of issues in the code you have provided.
You seem to be giving recipient_uid in find_by for UserVote, it should be uservote_id.
You are finding the vote to destroy by passing the author id, which cant be the unique key in votes. It will just return you the first entry in votes table.
Due to point 2, you are getting the vote to user 1 first, vote to user 2 next and so on. You will have to explain the structure of user,vote,user_vote a little more, if you want me to provide correct solution.
The Models:
class User < ActiveRecord::Base
has_many :user_votes
has_many :votes, :through => :user_votes
end
class Vote < ActiveRecord::Base
has_many :user_votes
has_many :users, :through => :user_votes
end
class UserVote < ActiveRecord::Base
belongs_to :author, class_name: "User", :foreign_key => :author_uid
belongs_to :recipient, class_name: "User", :foreign_key => :recipient_uid
belongs_to :vote
end
The view
= link_to('Delete vote', user_vote_path(user_vote), method: :delete)
The Controller
class UserVotesController < ApplicationController
def destroy
#user_vote = UserVote.find_by(params[:id])
#vote = #user_vote.vote
#vote.destroy
#user_vote.destroy
redirect_to root_url
flash[:warning] = 'Deleted'
end
end
Solution was quite simple. I just rewrote action destroy in users_votes controller:
class UserVotesController < ApplicationController
def destroy
#user_vote = UserVote.find(params[:id])
#user_vote.destroy
redirect_to root_url
flash[:warning] = 'Deleted'
end
end
Anyway, thanks for participation!
Related
I have a Store object that has an email_address attribute. Using the logic from and How To Build A Form and Handling Inbound Email Parsing with Rails, I'm trying to figure out how to structure a Conversation where a visitor can email the Store, and the Store can reply through email - their replies would post a Message to the Conversation.
When a visitor inquires to the store (via form), I create a Reservation record with their name and email, and start a Conversation like this:
#conversation = Conversation.create(sender_id: self.id, recipient_id: self.store_id)
I wanted to model the notifications similar to this, where everyone but the sender receives an email, but I'm stumped on how to map the User, since it's two different objects (Reservation and Store):
def send_notifications!
(forum_thread.users.uniq - [user]).each do |user|
UserMailer.new_post(user, self).deliver_now
end
end
The Conversation model looks like this, may be wrong, any guidance on what I could use to make the messages unique and structure the notifications?
class Conversation < ApplicationRecord
belongs_to :sender, :foreign_key => :sender_id, class_name: "Reservation"
belongs_to :recipient, :foreign_key => :recipient_id, class_name: "Store"
belongs_to :reservation
has_many :messages, dependent: :destroy
end
The simplest and most flexible way would be to set this up as a many-to-many association:
class User < ApplicationRecord
has_many :messages
has_many :conversations, through: :messages
end
class Message < ApplicationRecord
belongs_to :user
belongs_to :conversation
end
class Conversation < ApplicationRecord
has_many :messages
has_many :users, through: :messages
end
Here Message actually works as the join table that ties it together. Conversion is the recipient. When sending an initial message to a user you would POST to /users/:user_id/messages:
<%= form_with(model: [#user, #message || Message.new]) do |f| %>
# ...
<% end %>
module Users
class MessagesController < ApplicationController
# POST /users/:user_id/messages
def create
#user = User.find(params[:user_id])
#conversation = Conversation.joins(:users)
.where(users: { id: [current_user, #user]})
.first_or_create
#message = #conversation.messages.new(message_params.merge(user: current_user))
if #message.save
redirect_to #coversation
else
render :new
end
end
end
end
And then you would handle the views and controllers (such as a chat window) for conversations in a separate controller:
<%= form_with(model: [#conversation, #message || #conversation.messages.new]) do |f| %>
# ...
<% end %>
module Conversations
class MessagesController < ApplicationController
# POST /conversations/:conversation_id/messages
def create
#conversation = Conversation.find(params[:conversation_id])
#message = #conversation.messages.new(message_params.merge(user: current_user))
if #message.save
redirect_to #coversation
else
render :new
end
end
end
end
I'm attempting to set limits on the amount of commenting users can do on particular post during the day. I have implemented the following (successfully) in my Post model to limit the amount of Posts they can create.
class Post < ActiveRecord::Base
validate :daily_limit, :on => :create
def daily_limit
# Small limit for users who just sign up
if author.created_at >= 14.days.ago
if author.created_posts.today.count >= 4
errors.add(:base, "Exceeds Your Daily Trial Period Limit(4)")
end
else
if author.created_posts.today.count >= author.post_limit_day
errors.add(:base, "Exceeds Your Daily Limit")
end
end
end
end
But, when I attempt to add similar restrictions to my Comment model
class PostComment < ActiveRecord::Base
validate :daily_limit, :on => :create
belongs_to :post, :counter_cache => true
belongs_to :user
def daily_limit
# Small limit for users who just sign up
if user.posted_comments.today.count >= 2
errors.add(:base, "Exceeds Your Daily Trial Period Limit(4)")
end
end
end
I am greeted with a undefined method 'posted_comments' for nil:NilClass error. I don't believe my user_id is being passed into my model correctly in order to access it with something like user.posted_comments.today.count>=2
My create action in my post_comments controller is as follows:
class PostCommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#post_comment = #post.post_comments.create(post_comment_params)
#post_comment.user = current_user
if #post_comment.save
redirect_to #post
else
flash[:alert] = "Comment Not Added"
redirect_to #post
end
end
end
and the my hacked down User model is as follows:
class User < ActiveRecord::Base
has_many :created_posts, class_name: 'Post', :foreign_key => "author_id",
dependent: :destroy
has_many :posted_comments, class_name: 'PostComment', :foreign_key =>"user_id", dependent: :destroy
end
Thanks.
You are assigning the user after "create" in your controller
#post_comment = #post.post_comments.create(post_comment_params)
#post_comment.user = current_user
Try this:
#post_comment = #post.post_comments.build(post_comment_params)
#post_comment.user = current_user
I'm having a bit of trouble understanding how to setup the contributions controller and the form in the view. I've set some forms in the view so i know the join tables work.
As of right now a post belongs_to user && a user has_many posts
Objective:
1. user1 creates post - which belongs to user1
2. user2 requesting to join the user1_post as a contributor
3. user1 accepts or declines request
4. user2 is now a contributor to user1_post
5. user1 can remove user2 as a contributor
Got the has_many :through setup properly and have tested it in the console
contribution.rb
class Contribution < ActiveRecord::Base
belongs_to :post
belongs_to :user
def accept
self.accepted = true
end
end
post.rb
class Post < ActiveRecord::Base
belongs_to :author, class_name: 'User'
has_many :contribution_requests, -> { where(accepted: false) }, class_name: 'Contribution'
has_many :contributions, -> { where(accepted: true) }
has_many :contributors, through: :contributions, source: :user
end
user.rb
class User < ActiveRecord::Base
has_many :posts, foreign_key: 'author_id'
has_many :contribution_requests, -> { where(accepted: false) }, class_name: 'Contribution'
has_many :contributions, -> { where(accepted: true) }
has_many :contributed_posts, through: :contributions, source: :post
end
contributions_controller.rb
class ContributionsController < ApplicationController
def create
#contribution = current_user.contributions.build(:user_id => params[:id])
if #contribution.save
flash[:notice] = "Added contributor."
redirect_to posts_path(#post)
else
flash[:error] = "Unable to add contributor."
redirect_to posts_path(#post)
end
end
def destroy
#contribution = current_user.contributions.find(params[:id])
#contribution.destroy
flash[:notice] = "Removed contributor."
redirect_to root_url
end
end
Without much context, this is what I'd do:
#config/routes.rb
resources :posts do
resources :contributions, only: [:create, :destroy] #-> can use posts#edit to add extra contributions
end
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def edit
#post = Post.find params[:id]
end
end
#app/views/contributions/edit.html.erb
<%= form_for #post do |f| %>
# #post form
<% end %>
## contributor add / remove form (select boxes)
#app/controllers/contributions_controller.rb
class ContributionsController < ApplicationController
def create
#post = Post.find params[:post_id]
#contribution = current_user.contributions.new contribution_params
#contribution.post = #post
notice = #contribution.save ? "Added Contributor" : "Unable to add contributor"
redirect_to #post, notice: notice
end
def destroy
#contribution = current_user.contributions.find params[:id]
#contribution.destroy
redirect_to root_url, notice: "Removed Contributor"
end
private
def contribution_params
params.require(:contribution).permit(:user, :post, :accepted)
end
end
As an aside, you should look at an ActiveRecordExtension to give you some methods for your conbtributions association (instead of having multiple associations):
#app/models/post.rb
class Post < ActiveRecord::Base
has_many :contributions, -> { extending ContributionExtension }
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :contributions, -> { extending ContributionExtension }
end
#app/models/concerns/contribution_extension.rb
class ContributionExtension
def requests(status=false)
where accepted: status
end
def accepted(status=true)
where accepted: status
end
end
#post.contirbutions.requets
#post.contributions.accepted
#user.contributions.requests
#user.contributions.accepted
--
And also, you should look at implementing a state_machine for your Contribution model:
#app/models/contribution.rb
class Contribution < ActiveRecord::Base
state_machine :accepted, initial: :pending do
event :accept do
transition [:pending, :denied] => :accepted
end
event :deny do
transition [:pending, :accepted] => :denied
end
end
end
Great article about it here.
This will allow you to call:
#contribution = current_user.contributions.find params[:id]
#contribution.accept
It will also give you several other cool methods:
#contribution.accepted?
#contribution.state
i'm really new to Rails and i'm wondering, how the following could be done:
After an user has written a comment for a sin ( =article), the author ( =user) should get 20 points (for example) added to his score (= user.score). score is a column in my Users table.
My models look like this:
class User < ActiveRecord::Base
has_many :comments, :dependent => :destroy
has_many :absolutions, :dependent => :destroy
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :sin
end
class Sin < ActiveRecord::Base
has_many :comments, :dependent => :destroy
end
My comments controller looks like this:
class CommentsController < ApplicationController
def new
#comment = Comment.new
end
def create
#sin = Sin.find(params[:sin_id])
#comment = current_user.comments.build(params[:comment])
#comment.sin_id = #sin.id
if #comment.save
flash[:success] = "Comment created!"
redirect_to sin_path(#sin)
else
flash[:error] = "Comment was not created."
redirect_to sin_path(#sin)
end
end
end
After spending several hours to get this on my own, i'm a little confused. After creating a comment, i would like change a specific value of the associated object User.
What would be the best way to do this?
Thanks for your help!
You could just add it after save :
if #comment.save
flash[:success] = "Comment created!"
current_user.score += 20
current_user.save
redirect_to sin_path(#sin)
else
BUT, it's always better to do it in your model. So i would create an add_score instance method in your user model and update the score there. Then, i would just call that method in the controller, in the same spot.
Define an after_save callback in your comment model:
class Comment < ActiveRecord::Base
[...]
after_save :add_score
private
def add_score
self.user.score += 20
self.user.save
end
end
You could use an after_create callback in your comment model that makes the change in the corresponding user?
This kind of logic doesn't belong in the controller.
class Job < ActiveRecord::Base
has_many :employments, :dependent => :destroy
has_many :users, :through => :employments
class User < ActiveRecord::Base
has_many :employments
has_many :jobs, :through => :employments
class Employment < ActiveRecord::Base
belongs_to :job
belongs_to :user # Employment has an extra attribute of confirmed ( values are 1 or 0)
In my view i am trying to update the confirmed fied from 0 to 1 on user click.
<%= link_to "Confirm Job", :action => :confirmjob, :id => job.id %>
In my job Controller I have
def confirmjob
#job = Job.find(params[:id])
#job.employments.update_attributes(:confirmed, 1)
flash[:notice] = "Job Confirmed"
redirect_to :dashboard
end
I am sure this is all wrong but I seem to be guessing when it comes to has_many: through.
How would I do update the confirmed field in a joined table?
I think that a job is assigned to a user by the employment. Thus, updating all employments is not a good idea, as Joel suggests. I would recommend this:
class Employment
def self.confirm!(job)
employment = Employment.find(:first, :conditions => { :job_id => job.id } )
employment.update_attribute(:confirmed, true)
end
end
from your controller
#job = Job.find(params[:id])
Employment.confirm!(#job)
This implies that one job can only be taken by one user.
Here is a stab at it (not tested):
def confirmjob
#job = Job.find(params[:id])
#jobs.employments.each do |e|
e.update_attributes({:confirmed => 1})
end
flash[:notice] = "Job Confirmed"
redirect_to :dashboard
end