I have an app with products - each product has things like notes/FAQs/attachments.
I can delete the notes & FAQs successfully, but not the Active Storage attachments.
Could somebody please assist? I've tried using a separate method in the Products controller but that didn't work, so my current line is using an Uploads controller.
The current error I am getting is:
NameError in UploadsController#destroy
uninitialized constant Upload
Uploads controller:
class UploadsController < ApplicationController
load_and_authorize_resource :nested => :product
def destroy
#product = Product.find(params[:id])
#upload = #product.ActiveStorage::Attachment.find(params[:id])
#upload.purge
redirect_back(fallback_location: products_path)
end
end
Products view:
<% #product.uploads.each do |upload| %>
<% if can? :destroy, upload %>
<td><%= link_to t('X', :default => t("X")),
product_upload_path(#product, upload),
:method => :delete,
:data => { :confirm => t('.confirm', :default => 'Are you sure you want to delete this attachment?') },
:id =>'delete-faq' %></td>
<% end %>
<% if upload.variable? %>
<span><%= image_tag upload.variant(resize: "100x100"), class: "other-image" %></span>
<% elsif upload.previewable? %>
<span><%= link_to image_tag(upload.preview(resize: "100x100"), class: "other-image"), rails_blob_path(upload), target: "_blank" %></span>
<% else %>
<span><%= link_to image_tag("paper.jpg", size: "100x100", class: "other-image"), rails_blob_path(upload), target: "_blank" %></span>
<% end %>
<% end %>
routes:
resources :products do
resources :notes
resources :faqs
resources :uploads
end
I know this is not the most RESTful solution but I got it working, so am adding this as an answer in case it helps anybody else tearing their hair out.
I have checked the logs and it does remove both the attached image and the blob.
In my case, a product has_many uploads.
routes.rb:
resources :products do
resources :uploads do
match '/remove', to: 'products#remove', via: 'delete'
end
end
products controller (yes, I know, putting it here is not ideal)
def remove
#product = Product.find(params[:product_id])
#upload = #product.uploads.find(params[:upload_id])
#upload.purge
redirect_to product_path(#product), notice: "Upload was successfully removed."
end
products show view:
<% #product.uploads.each do |upload| %>
<%= link_to "X", product_upload_remove_path(#product, upload),
:method => :delete,
:data => { :confirm => t('.confirm', :default => 'Are you sure you want to delete this upload?') },
:id =>'delete-faq', :class => 'delete' %></td>
<% end %>
Hopefully this will help others or give a foundation for a more RESTful solution.
Related
I’m trying to update an attribute on a show page, keeping it in the show page, that is connected through a different table. For example: manager logs into admin site and sees several resources, clicks on one and can see all users that have access to the resource (either approved or pending). They can currently delete a user’s access. What I’m trying to do is allow the manager to change the status of the request say from pending to approved.
So for my models I have the following:
class TrainingResource
has_many :user_training_resources, dependent: :destroy
end
class UserTrainingResource
belongs_to :user
belongs_to :training_resource
enum status: [:pending, :confirmed, :rejected]
end
class Users
has_many :user_training_resources, dependent: :destroy
has_many :training_resources, through: :user_training_resources
end
TrainingResourcesController
class Admin::TrainingResourcesController < Admin::ApplicationController
belongs_to_app :training_resources
add_breadcrumb 'Training Resources', :admin_training_resources_path
before_action :load_training_resource, only: [:show, :edit, :update, :destroy]
respond_to :html, :json
def index
#training_resources = TrainingResource.paginate(page: params[:page])
#training_resources = #training_resources.search(params[:search]) if params[:search]
respond_with(#training_resources)
end
def show
respond_with #training_resource
end
def new
#training_resource = TrainingResource.new
respond_with(#training_resource)
end
def create
#training_resource = TrainingResource.new(training_resource_params)
flash[:notice] = 'TrainingResource created successfully' if #training_resource.save
respond_with #training_resource, location: admin_training_resources_path
end
def edit
respond_with(#training_resource)
end
def update
flash[:notice] = 'TrainingResource updated successfully' if #training_resource.update(training_resource_params)
respond_with #training_resource, location: admin_training_resources_path
end
def destroy
flash[:notice] = 'TrainingResource deleted successfully' if #training_resource.destroy
respond_with #training_resource, location: admin_training_resources_path
end
private
def load_training_resource
#training_resource = TrainingResource.find_by!(id: params[:id])
end
def training_resource_params
params.require(:training_resource).permit(:name, :description, :total_subscriptions, :url)
end
end
UserTrainingResourcesController, which is pointing to the TrainingResourcesController
class Admin::UserTrainingResourcesController < Admin::ApplicationController
belongs_to_app :training_resources
add_breadcrumb 'Training Resources', :admin_training_resources_path
before_action :load_training_resource
respond_to :html, :json
def edit
respond_with #user_training_resource
end
def update
flash[:notice] = 'UserTrainingResource updated successfully' if #user_training_resource.update(user_training_resource_params)
respond_with #user_training_resource, location: admin_training_resources_path
end
def destroy
flash[:notice] = 'UserTrainingResource deleted successfully' if #user_training_resource.destroy
respond_with #user_training_resource, location: admin_training_resources_path
end
private
def load_training_resource
#user_training_resource = UserTrainingResource.find_by!(id: params[:id])
end
def user_training_resource_params
params.require(:user_training_resources).permit(
:training_resources_id, :status).merge(user_id: current_user_id)
end
end
Training Resource Show
<tbody>
<% #training_resource.users.each do |training| %>
<tr>
<td><%= training.full_name %></td>
<% utr = training.user_training_resources.where(training_resource: #training_resource).first %>
<td><%= utr.status.capitalize %>
<%= form_tag '/user_training_resource/edit', :method => :get do %>
<%= select_tag( :user_training_resources_id, options_for_select(['pending', 'confirmed', 'rejected']))%>
<%= submit_tag 'Edit Status', class: 'btn btn-default btn-sm' %>
<% end %>
<%= tb_form_for [:admin, #training_resource], :remote => true, :data => {:errors => :inline, :success => admin_user_training_resources_path} do |f| %>
<%= tb_form_errors(f.object, :base) %>
<%= f.tb_select :name, options_for_select(holder, :status) %>
<%= f.tb_save_buttons('User Training Resource', admin_user_training_resources_path) %>
<% end %>
</td>
<td class="table-actions">
<%= link_to 'Edit', edit_admin_user_training_resource_path(training), :class => 'btn btn-default btn-sm' %>
<%= link_to 'Delete', admin_user_training_resource_path(training), :method => :delete, :data => {:confirm => 'Are you sure you want to delete this?'}, :class => 'btn btn-danger btn-sm' %>
</td>
</tr>
<% end %>
</tbody>
User Training Resource Helper, Holder Method
def holder
TrainingResource.all.each(&:id)
end
The link to Edit I’ll end up taking out because it pushes the user to the UserTrainingResource edit page and I want to keep it on the current page. The top form with the select_tag isn’t actually reading in the current status and then the submit_tag is redirecting the page.
My latest attempt is the bottom form_for. This is pulling in the TrainingResource and not the UserTrainingResource data. When I change the tb_select to :status, options_for_select(holder, :status) I end up with undefined method `status’. It looks like it’s only aiming at TrainingResource. I’ve also tried:
Thought here that it would pull in the default status and then allow the options to change. Still have an issue with undefined method of ‘status’ and even then there’s the issues with save.
I’ve also tried:
In this case I end up with undefined method ‘map’. Which I've tried to pinpoint down with attempting pluralization on #training_resource and #user_training_resource but that turns up First argument in form cannot contain nil or be empty.
Edit:
Tried in the UserTrainingResourcesController:
def set_to_confirmed
#user_training_resource = UserTrainingResource.find(params[:user_training_resource])
end
Then in the show
<%= link_to 'Confirmed', {:controller => 'user_training_resources', :action => 'set_to_confirmed', :status => #training_resource.user_training_resource }, :class => 'btn btn-default btn-sm'%>
Although no errors on the page load when clicking on the link I get:
Requested URL: https://localhost:3000/admin/set_to_confirmed?status=%23%3CUserTrainingResource%3A%3AActiveRecord_Associations_CollectionProxy%3A0x00007f93ee8b0f90%3E
Latest attempt using the following:
<% #training_resource.spud_users.each do |training| %>
<tr>
<td><%= training.full_name %></td>
<% utr = training.user_training_resources.where(training_resource: #training_resource).first %>
<td>
<%= tb_form_for [:admin, #training_resource], :remote => true, :data => {:errors => :inline, :success => admin_training_resources_path} do |f| %>
<%= f.select(:user_training_resource_id, options_for_select(['pending', 'confirmed', 'rejected']), :selected => utr.status)%>
<%= f.tb_save_buttons('', admin_training_resources_path) %>
<% end %>
</td>
This will show only the options, which is good, but I need it to default to what's current in the database and the save isn't sticking.
If I get the point, you need to update the joining table UserTrainingResource changing the status column.
I probably would create three actions in Admin::UserTrainingResourcesController:
def set_to_pending
end
def set_to_confirmed
end
def set_to_rejected
end
Then I would add three link to each row in the table in TrainingResource#Show passing the necessary parameters to complete the action, like explained in this post: pass parameter by link_to ruby on rails
I have made my form:
<tbody>
<% #player.bases.each do |basis| %>
<td><%= basis.id %></td>
<td><%= image_tag(basis.image_url(:thumb), class: 'thumbnail') %></td>
<td><%= link_to basis.name, basis %></td>
<td><%= basis.cost %></td>
<td><%= basis.short_info %></td>
<td>
<%= form_for #player, url: {:controller => 'players', :action => :remove_detail} do |f| %>
<%= f.hidden_field :type, :value => 'basis' %>
<%= f.hidden_field :detail_id, :value => basis.id %>
<%= f.submit 'Remove',class: 'btn btn-danger' %>
<% end %>
</td>
<% end %>
</tbody>
In my routes, I have added this:
resources :players do
collection do
get 'search'
post 'remove_detail'
end
end
I have remove_detail in my players_controller.rb, and I have added this action to before_action to get current player. However when I press on my Remove button, it throws me error and tries to run update action of my controller. Why?
My before_action:
before_action :set_player, only: [:show, :edit, :update, :destroy, :remove_detail]
My remove_detail:
def remove_detail
type = params['type']
id = params['detail_id']
if type == 'basis'
basis = Basis.find(id)
name = basis.name
#player.bases.delete(basis)
end
redirect_to #player, notice: "#{name} detail is removed"
end
To fix that, try as follows:
First of all, I'd redefine your routes as follows:
resources :players do
member do
delete 'remove_detail'
end
collection do
get 'search'
end
end
This will generate proper url for deleting a detail for a "single Player":
/players/:id/remove_detail
Because of REST-y nature of Rails, we defined the url to be accessible by performing delete request.
Your form change accordingly:
<%= form_for #player, { url: { action: "remove_detail" }, method: :delete } do |f| %>
Changing your routes to use delete method is more to keep the convention of Rails. Post would make your application work too, but - its just Rails-y way.
Good luck!
I've been fighting with this error for a while now and I'm sure that it is something simple that I am missing. I'm working through the Blogger 2 tutorial by jumpstartlabs(http://tutorials.jumpstartlab.com/projects/blogger.html#i3:-tagging).
I'm attempting to implement a way to delete existing tags through the UI. I'm stuck trying to resolve this error:
No route matches [POST] "/tags/3"
My routes.rb is:
Blogger::Application.routes.draw do
root to: 'articles#index'
resources :articles do
resources :comments
end
resources :tags
end
and here is my tags controller:
class TagsController < ApplicationController
def show
#tag = Tag.find(params[:id])
end
def index
#tags = Tag.all
end
def destroy
#tag = Tag.find(params[:id])
#tag.destroy
flash.notice = "Tag '#{#tag.name}' Deleted!"
redirect_to action: 'index'
end
end
Finally, here is the page that I'm displaying the deletion link on:
<h1>All Tags</h1>
<ul id="tags">
<% #tags.each do |tag| %>
<li>
<%= link_to tag.name, tag_path(tag), class: 'tag_title' %>
<%= link_to "Delete", tag_path(tag), method: :destroy, data: {confirm: "Really delete the tag?"} %>
</li>
<% end %>
<%= link_to "<< Back to Articles List", articles_path %>
</ul>
As #BillTurner mentioned, try changing method from :destroy to :delete. Make sure you save the change, reboot the server, and let me know if that works.
that answer didn't work out for me, but I found elsewhere that => fixed it
<%= link_to "delete", article_path(#article), :method => :delete, data: {confirm: "Really delete the article?"} %>
(that's in the view app\views\articles\show.html.erb)
I'm doing a Rails tutorial, and trying to figure out why this is happening.
I'm making a to-do list, and everytime I try and insert a record into my Todo model, I get the following:
Here is the new.html.erb view that this is from:
<h1>Add new item to your todo listM</h1>
<%= form_for #todo, :url=>todo_path(#todo) do |f| %>
<%= f.label :name %> <%= f.text_field :name%>
<%= f.hidden_field :done, :value => false %>
<%= f.submit "Add to todo list" %>
<% end %>
Here is index.html.erb from where the user is linked to new.html.erb
<h1>TASKS</h1>
<h3> TO DO </h3>
<ul>
<% #todos.each do |t| %>
<li>
<strong><%= t.name %></strong>
<small><%= link_to "Mark as Done", todo_path(t), :method => :put %></small>
</li>
<% end %>
</ul>
<h3> DONE </h3>
<ul>
<% #todones.each do |t| %>
<li>
<strong><%= t.name %></strong>
<small><%= link_to "Remove", t, :confirm => "You sure?", :method => :delete %></small>
</li>
<% end %>
</ul>
<%= link_to "Add new task", new_todo_path %>
Here is the TodoController I have managing these actions:
class TodoController < ApplicationController
def index
#todos = Todo.where(done:false)
#todones = Todo.where(done:true)
end
def new
#todo = Todo.new
end
def todo_params
params.require(:todo).permit(:name, :done)
end
def create
#todo = Todo.new(todo_params)
if #todo.save
redirect_to todo_index_path, :notice => "Your todo item was created!"
else
render "new"
end
end
def update
#todo = Todo.find(params[:id])
if #todo.update_attribute(:done, true)
redirect_to todo_index_path, :notice => "Your todo item was marked done!"
else
redirect_to todo_index_path, :notice => "Couldn't update your task"
end
end
def destroy
#todo = Todo.find(params[:id])
#todo.destroy
redirect_to todo_index_path, :notice => "Your todo item was deleted"
end
end
And finally the routes.rb
Oneday::Application.routes.draw do
devise_for :users
root 'home#index'
resources :todo
end
Any input as to why this is happening and how to rectify it would be great.
You do not comply with the rails convention. Use plural form for resources. Then, your action is correct.
TodosController, todos_controller.rb, resources :todos
( Rails use singular/plural format to support RESTful links and to recognize named actions )
This
<%= form_for #todo, :url=>todo_path(#todo) do |f| %>
will set (or leave) the form http method to get. You could change it to:
<%= form_for #todo, :url=>todo_path(#todo), method: :post do |f| %>
or even shorter, leave it to Rails to find out what method is needed:
<%= form_for #todo do |f| %>
I found a fix to this exact issue if anyone is still curious, i know its an old issue and an easy one at that, but still figured id solve it. the original route todo_path leads to todo#show. todo_index however is assigned to todo#index and todo#create so its what we want. the line should look like this:
<%= form_for #todo, :url => todo_index_url(#todo), method: :post do |f| %>
I encountered a similar issue with one of my applications and stumbled across this post as a fix. None of the suggestions worked for me, but i was able to fix it with a little tinkering on the routes.
What is causing this error when I click on the edit button for a note? The delete button works fine. I created the note object with a scaffold.
index.html.erb
<% #notes.each do |note| %>
<%= note.detail %>
<%= button_to 'Delete', note, :confirm => 'Are you sure?', :method => :delete %>
<%= button_to 'Edit', edit_note_path(note) %>
<% end %>
notes_controller.rb
before_filter :check_ownership, :except => [:new, :create, :index, :edit]
def edit
#note = Note.find(params[:id])
end
Error
ActiveRecord::RecordNotFound in NotesController#192
Couldn't find Note with ID=edit
../app/controllers/notes_controller.rb:248:in `check_ownership'
Parameters:
{"id"=>"edit"}
EDIT
config/routes.rb
map.resources :notes
All of the other routes for notes work fine.
Thanks for reading
Scaffold controller's 'edit' action, and defult routing support 'edit' as GET rather than POST request.
If you use link_to rather than button_to, things should work.
Alternatively, change the line containing button_to -
<%= button_to 'Edit', edit_note_path(note), :method => :get %>