Delete object with user password - Rails - ruby-on-rails

I'm trying to create a form_for that will accept the current_users password in order to delete one of their projects. Basically the process of deletion doesn't need to know anything about that user. just the confirmation step of ensuring that they intend to delete the project. What should my form look like? It doesn't have to be form_for it can be form_tag too.
Current View:
<div class="modal">
<h2>Confirm Project Deletion</h2>
<%= form_for #user, url: new_account_path, method: :delete, do |f| %>
<%= f.submit %>
<% end %>
</div>
I also have access to #project which has the current_project attached to it as well as current_user as you can see. Any help would be great thanks.

A fairly common approach would be to use a delete tag on the index page of all projects, like this:
<% #projects.each do |project| %>
<%= project.title %>
...
<% if current_user.id == project.user_id %>
<%= link_to project, method: :delete, data: { confirm: 'This will delete this project.' } do %>
Delete!
<% end %>
<% end %>
If only the project user can view only his projects on the index page, then just use the link_to method: :delete because the user is already authenticated.

Assuming you have a local variable project in your page and have a route such as:
project DELETE /projects/:id(.:format) projects#destroy
then you can simply set up a form such as
<%= form_tag project_path(id: project.id), method: :delete do %>
<%= label_tag :password %>
<%= password_field_tag 'password', '' %>
<input value='Delete Project' type='submit' id='submit-form'/>
<% end %>
All you then need to do is add your authentication check in to the destroy method:
def destroy
project = Project.find(params[:id])
password = params[:password]
# Do my authentication check here
if #project and authenticated
#project.destroy
respond_to do |format|
format.html { redirect_to projects_url, notice: 'Project was successfully destroyed.' }
end
end
end
Personally though, I think it would be much better if you implemented a resource based authorisation mechanism, such as CanCanCan, which would work alongside your authentication system. This would enable you to set abilities (rules) that would ensure that the current_user can only view / delete / manage their own projects, without requiring them to authenticate every time they wish to destroy a resource.

Related

How to access current_user helper with Hotwire & Turbo.js?

I’m following this tutorial to implement a feature where user can submit Trivia/Interesting Facts.
I want to restrict (edit/delete) functionality to the admin or author of each item.

I’ve created a helper class in .application_controller
def author_of?(resource)
user_signed_in? && current_user.id == resource.user_id
end
But when I'm using this in Turbo-frame with Hotwire I’m getting this error
| ActionView::Template::Error (Devise could not find the Warden::Proxy instance on your request environment.
Here's my code for reference
index.html
<%= turbo_stream_from #stadium, :trivia %>
<%= tag.div id: "#{dom_id(#stadium)}_trivia" do %>
<%= render partial: "trivia/trivium", collection: #stadium.trivia %>
<% end %>
<% end %>
_trivium.html.erb
<%= turbo_frame_tag trivium do %>
<%= trivium.body %>
<% if author_of?(trivium) || admin? %>
<%= button_to "Delete", trivium_path(trivium), class: "btn btn-small btn-danger btn-link mr-2", method: :delete, data: { confirm: "Are you sure?" } %>
<% end %>
<% end %>
How can I access the current_user helper in the comment partial to check if the current_user is the author or admin (and should be allow to delete/edit)?
The reason this errors is, as mentioned here, partials with Turbo frames are rendered without any of your global helpers available.
That SO question points to a Hotwire forum here where they discuss possible solutions. The best one in my view is described here.
The gist is:
Pass in current_user as an argument to your partial, rather than accessing it from within your partial
Do your permission check within the partial, rather than the helper
<%= render partial: "trivia/trivium", collection: #stadium.trivia, user: current_user %>
You might consider turning your author_of method into a model method as well, and then you'll have the option to use it within your partial.

Create method wont Create New Relationship in databse even though success flash appears

I'm trying to create a relationship in the database, but cant get it to work.
why wont the create action work? why can't it find the if statement? I think i havent written the create action correctly in the relationships controller but don't know how to fix it
When a user clicks 'add relationship'. the app should create the new relationship.
At the moment a user clicks 'add relationship' and this flash success msg appears:
You are now connected !
this is the information that comes across to the view where the flash success msg appears:
relationships.html.erb
relationship: !ruby/hash:ActionController::Parameters
followed_id: '3'
commit: Add Relationship
action: create
controller: relationships
but! a relationship is not created in the database
Here is the flow users take with comments to help explain:
users/index.html.erb:
# 1. USER SEARCHES FOR ANOTHER USER IN SYSTEM:
<%= form_tag users_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
# 2. IF ANOTHER USER IS FOUND, THEIR NAME IS PRESENTED AND AN 'ADD RELATIONSHIP' BUTTON APPEARS:
<ul>
<% #users.each do |user| %>
<li>
<%= user.name %>
<%= render 'followed', followed: user %>
</li>
<% end %>
</ul>
_followed.html.erb:
# 3. ADD RELATIONSHIP BUTTON INFORMATION. THE USER CLICKS THE BUTTON, RELATIONSHIPS/CREATE IS THE NEXT PAGE. NO RELATIONSHIP IS CREATED
<%= form_for :relationship, url: relationships_path, method: :post do |f| %>
<div class="form form-actions">
<%= f.hidden_field :followed_id, value: followed.id %>
<%= f.submit "Add Relationship", class: "btn btn-primary" %>
</div>
<% end %>
relationships_controller:
def create
if params[:relationship] && params[:relationship].has_key?(:followed_id)
#followed = User.find_by(name: params[:active_relationship][:followed_id])
#active_relationship = current_user.active_relationships.new(followed: #followed)
#active_relationship.save
flash[:success] = "You are now connected !"
else
flash[:danger] = "Relationship required"
end
end
Looks like the error is in the second line in your create controller. In your if statement you have params[:relatioship] so I guess you would write
#followed = User.find_by(name: params[:relationship][:followed_id])
and I guess the followed_id shouldn't be a name
You should refactor the code like this:
if params[:relationship]
#followed = User.find(params[:relationship][:followed_id])
#active_relationship = current_user.active_relationships.new(followed: #followed)
if #active_relationship.save
flash[:success] = "You are now connected !"
else
flash[:danger] = "Relationship required"
end
else
flash[:danger] = "Something was wrong!!"
end
You might also use pry to debug codes. Links for reference http://pryrepl.org

Rails validating with two models

Ok, bit confused on how to solve this issue.
I have one form and two models. Here is my form:
<% if #booking.errors.any? %>
<% #booking.errors.full_messages.each do |msg| %>
<p class="error"><%= msg %></p>
<% end %>
<% end %>
<% if #guest.errors.any? %>
<% #guest.errors.full_messages.each do |msg| %>
<p class="error"><%= msg %></p>
<% end %>
<% end %>
<%= form_for :booking, url: bookings_path do |f| %>
<%= label_tag :email, "Guest's Email Address" %>
<%= text_field_tag :email %>
<%= f.label :nights, "Nights" %>
<%= f.text_field :nights %>
<%= f.label :nights, "People" %>
<%= f.text_field :people %>
<%= f.label :nights, "Arrival Date" %>
<%= f.text_field :arrival %>
<% end %>
As you can see, the email field isn't part of the form builder. The email address will be used to create a new Guest record if the email doesn't already exist. Once I have the ID of the guest then the booking record can be made also.
Here is the code for create action in my BookingController - where the form is submitted to...
...
def create
accommodation = current_user.accommodation
guest = Guest.find_or_create_by(:email => params[:email])
#booking = accommodation.bookings.new(post_params.merge(:guest_id => guest.id))
if #booking.save
flash[:success] = 'The booking has been added successfully.'
redirect_to :controller => 'bookings', :action => 'index'
else
render 'new'
end
end
...
I do realise this question isn't new but I can't find a good solution anywhere to my problem - I want to be able to set the form up properly (if necessary) and validate all fields using the two models. Then I need to display the error messages. At the moment, my email is ignored during validation and I'm not sure what to do next.
Any help much appreciated.
It seems to me that the easiest way to is to validate the email in the controller itself and add any validation error to the booking variable. Something like this:
def create
accommodation = current_user.accommodation
guest = Guest.find_or_create_by(:email => params[:email])
#booking = accommodation.bookings.new(post_params.merge(:guest_id => guest.id))
if #booking.save
flash[:success] = 'The booking has been added successfully.'
redirect_to :controller => 'bookings', :action => 'index'
else
<% if params[:email].blank> %>
#booking.errors.add(:email, "can't be blank.")
<% end %>
#You can do the same thing for whatever other validation errors you have
render 'new'
end
end
Note: I did not test the code
This is probably not the best way possible but it gets the job done and is easy. You could use accept_nested_attributes_for but it seems to me a little bit unnecessary considering that you are only validating an email. Nevertheless, if you want to do it the cleanest way, stick with accept_nested_attributes_for.
EDIT
Actually, your code is the right track. You just made a syntax error. The real reason your guests errors are not being shown is that you used a local variable instead of a instance variable. Try this:
#guest = Guest.find_or_create_by(:email => params[:email])
Your error messages should be displayed with the code you already have
<% if #guest.errors.any? %>
<% #guest.errors.full_messages.each do |msg| %>
<p class="error"><%= msg %></p>
<% end %>
<% end %>
EDIT 2
In order to avoid a booking instance from beings saved in case the guest email is invalid you can do something like this:
if !#guest.errors.any? && #booking.save
flash[:success] = 'The booking has been added successfully.'
redirect_to :controller => 'bookings', :action => 'index'
else
Therefore, if the guest has any errors, the if statement will terminate before the #booking.save statement is executed.
You can try to do a transaction. If one of them is invalid, rails will do a rollback and you can render the errors.
Follow this question code, see if it helps.

Repopulating form on failed validation

I have a form for a user to create a question (in additon to user model, there's a question model, with nested answers) on their profile page. It submits from the users profile page /views/users/show.html.erb to the create action of the questions_controller.rb. If it doesn't validate, I think the default for Rails is to render the form(with the invalid information in the form for the user to edit). However, since I'm submitting the form for the question model from the users profile page the prepopulation isn't happening upon failed validation; the user is forced to enter all the information in the form again. Is there a way in this context to get the form on the users show page to fill out with the information that was entered prior to submission?
questions_controller
def create
#question = current_user.questions.build(params[:kestion])
if #question.save
redirect_to current_user, :notice => "Successfully created question."
else
###render :action => 'show'
redirect_to current_user
end
end
Update
I've changed the end of the create method too
Redirect ( : back ), :notice => "something went wrong.try again"
But I still can't get the form to populate, and the validation error messages aren't showing either, only the flash notice.
Update
The show method of the users controller creates the new Question (along with the user)
def show
#user = User.find(params[:id])
#question = Question.new
3.times {#question.answers.build}
end
The /views/users/show.html.erb
<%= form_for #question do |f| %>
<% if #question.errors.any? %>
<h2><%= pluralize(#question.errors.count, "error") %> prohibited this question
from being saved: </h2>
<ul>
<% #question.errors.full_messages.each do |msg| %>
<li> <%= msg %></li>
<% end %>
</ul>
<% end %>
<p>
<%= f.label :content, "Question"%>
<%= f.text_area :content, :class => 'span4', :rows => 1 %>
</p>
<p>
<%= f.label :link, "QuoraLink" %>
<%= f.text_field :link, :class => 'span4', :rows => 1 %>
</p>
<%= f.fields_for :answers do |builder| %>
<p>
<%= render 'answer_fields', :f => builder %>
</p>
<% end %>
<p><%= link_to_add_fields "Add Answer", f, :answers %></p>
<p><%= f.submit %></p>
<% end %>
the answer_fields partial rendered from the questions partial
<p class="fields">
<%= f.label :content, "Answer..." %>
<%= f.text_field :content, :class => 'span3', :rows => 1%>
<%= f.label :correctanswer, "Correct" %>
<%= f.check_box :correctanswer, :class => 'span1' %>
<%= link_to_remove_fields "remove answer", f %>
</p>
Currently, in views/users/show.rb you do
#question = Question.new
that creates an empty new question. Then you populate the forms with this empty model.
What you could do instead is:
if session[:question]
#question = #user.questions.new(session[:question])
session[:question] = nil
#question.valid? # run validations to to populate the errors[]
else
#question = Question.new
end
Now all what's left to do is populating session[:question] in your questions_controller before redirecting to :controller=>"users", :action=>"show". Something like:
if #question.save
redirect_to current_user, :notice => "Successfully created question."
else
session[:question] = params[:question]
redirect_to current_user
end
You may need to work on serialization/deserialization additionally for populating/using session[:question]. I didn't try to compile, so am not sure.
All this is needed because when you do redirect_to your processing of the user request ends, the user browser gets a redirect status code from your server and goes for another page, sending you a new request (which lands on the path, and eventually controller/action, to which you redirected to). So, as soon as you return from the request processing, all your variables are lost. For the next request you start from scratch.
The render :action => "show" approach (that was in the original scaffold and that you commented out) worked because you didn't return back to user but simply rendered the template with a specific name using the variables you already had in place (including #question, on which 'save' was called and failed, and thus internally validations were called and populated the errors object).
Actually, that reminded me that you may want to use another approach. Instead of passing parameters through session[] and redirecting to UsersController, you may want to populate all required variables and just render the view from that controller. Like below:
if #question.save
redirect_to current_user, :notice => "Successfully created question."
else
#user = current_user
render "users/show"
end
Firstly, the reason that using redirect_to instead of render doesn't repopulate the form is that when you redirect_to, the controller logic for the action is run, whereas using render ignored the controller logic.
So when you render :action => 'show' (the "default" behaviour), it renders show.html.erb, with #question set like this:
#question = current_user.questions.build(params[:kestion])
When you redirect_to current_user, it renders show.html.erb with #question set using the code in your show action:
#question = Question.new
3.times {#question.answers.build}
This is why you get a new (blank) form, instead of a pre-populated one.
Is it really that important that you use redirect_to? If it is, you'll need to get your show method to do the validation. For example, you could rewrite your show method to something like:
def show
#user = User.find(params[:id])
if params.has_key?(:kestion)
#question = #user.questions.build(params[:kestion])
else
#question = Question.new
3.times {#question.answers.build}
end
end
and then make your form point at that page, with something like:
<%= form_for(#question, url: users_path(#question.user) do |f| %>
...
<% end %>
(depending on how your routes are set up and named). Of course, by that point the whole thing become horribly un-RESTful, a bit of a mess, and definitely not the Rails way of doing things. (The other, worse option would be to redirect back and pass the params through a get query.) In my opinion, you lose a lot for a minor gain, and I'm not sure that I'd really recommend it.

Making a user an admin user through a button without having the attribute accessible in Rails

From what I understand it would create a security flaw if I were to let my admin boolean in my users model be listed in attr_accessible. However I want a link on my show page that lets existing admin users grant admin privileges to other users. I was wondering how to go about doing this? My code in my show view for a user at the moment includes:
<% if current_user.admin? && #user.admin == false %>
<%= link_to "Make Administrator", '#',
data: { confirm: "Make this user an admin?" }, class: "btn btn-large btn-primary" %>
<% elsif current_user.admin? && #user.admin%>
<%= link_to "Remove Administrator", '#', class: "btn btn-large btn-danger" %>
<% end %>
I'm not entirely sure what to put instead of the '#'? #user.toggle!(:admin) doesn't seem to work so any pointers would be appreciated. Thank you in advance!
First, since this is an action this should be a button, not an link.
<%= button_to user_path(#user), :method => :put ... %>
controller code
def update
if params[:admin]
user.update_attribute(:admin, true)
redirect_to ...
end
...
end
or if you prefer to keep your logic in the model
def update
...
if params[:admin]
#user.make_admin
end
...
end
model code
def make_admin
self.update_column(:admin, true)
end

Resources