Rails: I want to add index to the resource :symbol route - ruby-on-rails

Users have subscriptions to feeds but when I change (as suggested elsewhere on stackO)
resource :subscriptions
to
resources: subscriptions
it breaks some ajax functionality I already have implemented regarding destroy.
I want to be able to link to /subscriptions/ and have users be able to view all of their subscriptions. The problem is that currently it's bringing me to subscriptions#show when I really want #index.
How should I do this?
Here's my AJAX:
<div id="subscribe" class="shadow">
<% if session[:read_random]
unless is_subscribed?(session[:read_random].last)%>
<%= link_to 'Subscribe', subscriptions_path(feed_id: session[:read_random].last), method: :post, remote: :true %>
<% else %>
<%= link_to 'Unsubscribe', subscriptions_path(feed_id: session[:read_random].last), method: :delete, remote: :true %>
<% end
end %>
</div>
Here's my destroy method
def destroy
if params[:feed_id]
#this is the ajax call
#subscription = Subscription.find_by_user_id_and_feed_id(session[:user_id], params[:feed_id])
else
#this is the index destroy call (unsubscribe)
#subscription = Subscription.find(params[:id])
end
#subscription.destroy
respond_to do |format|
format.html { redirect_to request.referer }
format.js
format.json { head :no_content }
end
end

Using the default resourceful routes for a plural resource, the proper destroy route would be subscription_path(id), method: :delete. Note that subscription is singular. For these routes, the destroy path is generally identical to the show and update path, with the exception of http method used.

Related

ID param not being sent to Controller when manually changing routes

I have looked through the other answers provided on StackOverflow, and none of them answered my question. Here is what is happening with my code.
Error
undefined method `update' for nil:NilClass
Problem:
It looks like the param id is not being sent to the controller from the form as they show up as nil in the console using byebug.
console readout:
(byebug) params[:id]
nil
(byebug) #support
nil
(byebug) params[:title]
nil
(byebug) params[:support]
<ActionController::Parameters {"title"=>"Test", "subtitle"=>"testing",
"website"=>"www.test.com", "type_of_support"=>"", "description"=>""}
permitted: false>
(byebug) params[:support][:id]
nil
(byebug) params[:support][:title]
"Test"
I do not believe that the problem is with the form as it is the same form partial used for the new/create action and the params are sent to the controller then and the object is created (though in that case there is no id, since it is generated when creating the object, not passed from the form).
You can see in my code below that the route for PATCH is just 'support' without the :id param. If I try to add that to the route, I get an error stating that there is no route matching 'support/'. So, I have to take away the :id param in the route for it to pass the information to the controller.
I am at a loss here. How do I pass the :id to the controller? How does rails do this? Before I manually change the routes, the automatic routes from resources :supports includes an :id param for the PATCH route and it works. What am I doing wrong that it won't allow me to add that to the route?
Code:
config/routes.rb
get 'support', as: 'supports', to: 'supports#index'
post 'support', to: 'supports#create'
get 'support/new', as: 'new_support', to: 'supports#new'
get 'support/:id/edit', as: 'edit_support', to: 'supports#edit'
get 'support/:title', as: 'support_page', to: 'supports#show'
patch 'support/', to: 'supports#update'
put 'support/:id', to: 'supports#update'
delete 'supports/:id', to: 'supports#destroy'
Results this for rake routes:
supports GET /support(.:format) supports#index
support POST /support(.:format) supports#create
new_support GET /support/new(.:format) supports#new
edit_support GET /support/:id/edit(.:format) supports#edit
support_page GET /support/:title(.:format) supports#show
PATCH /support(.:format) supports#update
PUT /support/:id(.:format) supports#update
DELETE /supports/:id(.:format) supports#destroy
app/controllers/supports_controllers.rb
class SupportsController < ApplicationController
before_action :set_support_by_title, only: [:show]
before_action :set_support_by_id, only: [:edit, :update, :destroy]
def index
#supports = Support.all
end
def show
end
def new
#support = Support.new
end
def edit
end
def create
#support = Support.new(support_params)
respond_to do |format|
if #support.save
format.html { redirect_to #support,
notice: 'Support was successfully created.' }
else
format.html { render :new }
end
end
end
def update
# byebug
respond_to do |format|
if #support.update(support_params)
format.html { redirect_to #support,
notice: 'Support was successfully updated.' }
else
format.html { render :edit }
end
end
end
def destroy
#support.destroy
respond_to do |format|
format.html { redirect_to supports_url,
notice: 'Support was successfully destroyed.' }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_support_by_title
#support = Support.find_by(title: params[:title])
# byebug
end
def set_support_by_id
#support = Support.find(params[:id])
# byebug
end
# Never trust parameters from the scary internet,
# only allow the white list through.
def support_params
params.require(:support).permit(:title,
:subtitle,
:website,
:type_of_support,
:description)
end
end
app/views/supports/edit.html.erb
<h1>Editing Support</h1>
<%= render 'form', support: #support %>
<%= link_to 'Show', support_page_path(#support.title) %> |
<%= link_to 'Back', supports_path %>
app/views/supports/_form.html.erb
<%= form_with(model: support, local: true) do |form| %>
<% if support.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(support.errors.count, "error") %>
prohibited this support from being saved:
</h2>
<ul>
<% support.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
Title:
<%= form.text_field :title, id: :support_title %>
</div>
<div class="field">
Subtitle:
<%= form.text_field :subtitle, id: :support_subtitle %>
</div>
<div class="field">
Website:
<%= form.text_field :website, id: :support_website %>
</div>
<div class="field">
Type of Support:
<%= form.text_field :type_of_support, id: :support_type %>
</div>
<div class="field">
Description:
<%= form.text_area :description, id: :support_description %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
While writing this question, I thought of something and tried it. And it worked. Instead of re-writing all of the routes myself, I wrote only 2 and used the resources :supports, except: [:index, :show] to have rails generate the others. This solved my issue.
Explanation
I knew that something was going on behind the scenes that I did not understand. The entire process worked fine before I started to change the routes. So, something in there was incorrect. (I still don't know what it is and how to change it.)
The only two routes that I really want to be changed are the two that users see. I don't care about how the routes look in the admin backend. So, that meant that I only needed to change the routes for index and show to be SEO friendly and look better in the browser. So, to do that I wrote the routes like this:
config/routes.rb
resources :supports, except: [:index, :show]
get 'support', as: 'support_index', to: 'supports#index'
get 'support/:title', as: 'support_page', to: 'supports#show'
This then created all of the new, create, edit, update, destroy routes for me. After doing this, this is how my routes now look:
supports POST /supports(.:format) supports#create
new_support GET /supports/new(.:format) supports#new
edit_support GET /supports/:id/edit(.:format) supports#edit
support PATCH /supports/:id(.:format) supports#update
PUT /supports/:id(.:format) supports#update
DELETE /supports/:id(.:format) supports#destroy
support_index GET /support(.:format) supports#index
support_page GET /support/:title(.:format) supports#show
As you can see, the PATCH route is now getting the param :id to be able to update the record.
Now, I just had to change a few of the redirects in the controller after create, update and destroy like this:
def create
#support = Support.new(support_params)
respond_to do |format|
if #support.save
format.html { redirect_to support_page_path(title: #support.title),
notice: 'Support was successfully created.' }
else
format.html { render :new }
end
end
end
def update
respond_to do |format|
if #support.update(support_params)
format.html { redirect_to support_page_path(title: #support.title),
notice: 'Support was successfully updated.' }
else
format.html { render :edit }
end
end
end
def destroy
#support.destroy
respond_to do |format|
format.html { redirect_to support_index_path,
notice: 'Support was successfully deleted.' }
end
end
These redirect_to statements now match the routes that I generated for index and show.
And now everything works.
So, problem solved, though I still don't know what I was doing wrong before. Any light that can be shed would be appreciated.

Delete method returning error after devise logout

I'm running into a problem that I'm not exactly sure how to fix.
I have a simple to do list application with AJAX functionality on methods such as 'new', 'create', 'complete', 'delete', as well as Devise authentication.
When I first enter a new session with a User, all of these methods work without a problem. Additionally, the tasks are saved to only the user account, which is perfect.
However, when I log out of an account, and then log back in, the delete method no longer works. I receive the following error:
ActiveRecord::RecordNotFound (Couldn't find Task with 'id'=)
My tasks_controller.rb is below:
class TasksController < ApplicationController
def index
#task = current_user.tasks.all
end
def new
#task = Task.new
respond_to do |format|
format.js
format.html
end
end
def create
#task = current_user.tasks.new(task_params)
#task.save
respond_to do |format|
format.html
format.js
end
end
def update
#task = current_user.tasks.find(params[:id])
#task.toggle :complete
#task.save
respond_to do |format|
format.html
format.js
end
end
def destroy
#task = Task.find(params[:id])
#task.destroy
respond_to do |format|
format.js
format.html
end
end
private
def task_params
params.require(:task).permit(:id, :title, :complete)
end
end
I'm not exactly sure how to fix this problem. Would anyone have an idea on what is going wrong here?
EDIT:
I noticed that on my index page, I have a link to destroy the user's session at the top:
<%= link_to "Log Out", destroy_user_session_path, :method => :delete %>
I'm wondering if rails is having some trouble with this as both the logout link and the delete link are referencing the same method. If so, how can I change the name of the delete method for Task?
<div class="delete"><%= link_to "X", task_path(#task), method: :delete, remote: true %></div>
What is #task referencing? It looks to me like you've set #task to a collection #task = current_user.tasks.all.
Which would be why your delete method can't find a specific record to delete.
-EDIT-
Change #task in your index controller to #tasks as it is a collection.
In your view, do something like:
<% #tasks.each do |task| %>
<div><%= task.title %><div class="delete"><%= link_to "X", task_path(task), method: :delete, remote: true %></div></div>
<% end %>
The key here is that you have task_path(task) which is referencing a specific task id as opposed to task_path(#task) which is referencing a collection of tasks.

How to allow only the author of a post edit or delete it in Ruby on Rails?

I have a user model and a question model.
In the user model:
has_many :questions
The question model:
belongs_to
in my questions/show.html.erb
<% if #question.user == current_user %>
<%= link_to 'Edit', edit_question_path(#question) %> | <%= link_to 'Destroy', #question, method: :delete, data: { confirm: 'Are you sure you want to delete this job?' } %>
<%= link_to 'Back', questions_path %>
<% else %>
<%= link_to 'Back', questions_path %>
<% end %>
How can only the user that authored the question edit and delete it?
Take a look at CanCan, the authorization gem by Ryan Bates of Railscasts. It's great for Rails authorization needs.
First, you'll create an Ability class that defines all of the abilities in the application.
class Ability
include CanCan::Ability
def initialize(user)
can :manage, Question, user_id: user.id
end
end
Then, you'll be able to easily integrate authorization into your controllers.
class QuestionsController < ApplicationController
def update
authorize! :manage, #question
...
end
def destroy
authorize! :manage, #question
...
end
end
And also customize your views.
<% if can? :manage, #question %>
<%= link_to 'Edit', edit_question_path(#question) %> | <%= link_to 'Destroy', #question, method: :delete, data: { confirm: 'Are you sure you want to delete this job?' } %>
<% end %>
All you need in your controller is:
def destroy
#question = current_user.questions.find(params[:id])
#question.destroy
render ... #anything you want to render
end
The previous code will ensure that an user can only delete his own questions. If the id of the question doesn't belongs to the user no question will be deleted and it would throw and ActiveRecord::RecordNotFound - Internal Server error. You can add a begin - rsecue block to catch this exception an handle it as you want.
def destroy
begin
#question = current_user.questions.find(params[:id])
#question.destroy
render or redirect_to ....
rescue Exception ActiveRecord::RecordNotFound
flash[:notice] = 'not allow to delete this question'
redirect_to ....
end
end
Other simple way is to add a before filter in your controller
before_filter :require_authorization, only: [:delete]
...
def destroy
#question = current_user.questions.find(params[:id])
#question.destroy
render or redirect_to ....
#With the before filter this code is only going to be executed if the question belongs to the user
end
...
private
def require_authorization
redirect_to :root unless current_user.questions.find_by_question_id(params[:id])
#Use the find_by to avoid the ActiveRecord::RecordNotFound and get a nil instead in case the question id doesn't belong to a question of the user
end
you can try changing your if to the following:
<% if current_user.questions.include?(#question) %>
Also you can take a look at :inverse_of
Then in your Edit and Delete actions in the controller you can again check for the right user before showing the edit form or deleting the question.

Why when I put delete object link in registrations/edit devise view it always delete account?

I'm using devise gem and showing user's objects on registrations/edit view.
I have such problem: when I'm clicking delete object link - it destroy user record, but should delete object.
User has many websites, so I show user his websited and want user available to delete it using this code:
<% #websites.each do |website| %>
<%unless current_user.websites.empty? %>
<%= link_to 'X', website_path(website), method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
<% end %>
Controller code:
class RegistrationsController < Devise::RegistrationsController
def edit
#websites = current_user.websites
#reports = current_user.financial_reports
end
end
class WebsitesController < ApplicationController
def destroy
#website = Website.find(params[:id])
if (current_user.id != #website.user_id)
redirect_to root_path
flash[:notice] = 'You are not owner!'
else
#website.destroy
respond_to do |format|
format.html { redirect_to websites_url }
format.js
end
end
end
I didn't change anything in standard behavior in Websites_controller.
Can someone suggest how to solve it ?
It would be worth checking your associations. If you have :dependent => :destroy on the wrong end of the association between users and websites then destroying the website would cause the associated user to be destroyed too.

Rails: Redirect to show only after form submission

I'm following Ryan Bates' guide on search functionality. I've left out the implementation of the search algorithm right now, only returning So far it's doing what it should, the only problem is that now when I visit /posts, I get automatically redirected to /posts/1.
In my Posts controller:
def show
end
def index
#post = Post.search params[:search]
puts ("----------------" + #post.to_s + "-----------")
respond_to do |format|
format.html { redirect_to #post }
end
end
In my index.html.erb:
<%= form_tag posts_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
In my Posts.rb
def self.search(search)
#insert search method here
return Post.find_by_id(1)
end
How can I get this so that I can visit /posts and search in my form without being automatically redirected?
Once you flush out your Posts.search to do something real, that won't be the case. Instead, you'll get #posts = [] or #posts = nil sometimes (depending on what you're after), and that will be its own problem. In the long run I think you'll have to have branch logic similar to
respond_to do |format|
format.html { #post.present? ? redirect_to(#post) : render(:index) }
end
Also, not sure how Ryan Bates does it exactly, but I always find value in having an ActiveModel-based search model. If it stays simple don't worry about it, but it's nice to have that in your bag of tricks if search starts to turn into its own beast, ex. special validations, multiple-model searching, etc.
The only way from your implementation would be to check for params[:search]. This would be nil if you just went to /posts
respond_to do |format|
format.html { redirect_to #post if !params[:search].nil? }
end

Resources