RoutingError with Nested Resources and Destroy - ruby-on-rails

I'm in a sort of weird situation where I'm getting a strange error with nested resources.
I have a nested resource defined as below:
resources :users do
resources :comments, :only => [:create, :destroy]
end
My end point for comments is json only so its controller is defined as follows. Take note that I am using cancan and actsAsApi gems.
class CommentsController < ApplicationController
load_and_authorize_resource
self.responder = ActsAsApi::Responder
respond_to :json
# POST /comments.json
def create
flash[:notice] = 'Comment was successfully created.' if #comment.save
respond_with(#comment, :api_template => :default)
end
# DELETE /comments/1.json
def destroy
#comment.destroy
respond_with(#comment, :api_template => :default)
end
I can then send a post request to '/users/1/comments.json' with some request parameters and the comment will get created like expected. Unfortunately I am getting an error where it tries to locate the destroy action:
Completed 404 Not Found in 169ms
ActionController::RoutingError (No route matches {:action=>"destroy", :controller=>"comments", :id=>#<Comment id: 34, user_id: 1, text: "test test test", created_at: "2012-02-28 06:45:49", updated_at: "2012-02-28 06:45:49">}):
app/controllers/comments_controller.rb:12:in `create'
As extra information, if I modify routes.rb to this:
resources :comments, :only => [:destroy]
resources :users do
resources :comments, :only => [:create]
end
I don't see any error.

Because you are using nested resources you need to tell cancan to load both the users and the comments for actions on comments to work.
See as follows:
class CommentsController < ApplicationController
load_and_authorize_resource :user
load_and_authorize_resource :comment, :through => :user
end
See more details on the cancan nested resource page

I have been able to figure this out. Basically it is required that when you nest resources you use respond_with as follows:
respond_with(#comment.note, #comment, :api_template => :default)

Related

session.destroy in controller

I am trying to create a log out for my app and in my controller this piece of code is not working. Any solution would be helpful.
def destroy
#user = user.find(params[:id])
#user.destroy
end
This is my destroy method in my sessions controller(the fact that i am destroying my user in my sessions controller might be the problem)
class UsersController < ApplicationController
def new
end
def create
#user = User.create(password: params[:password],
email: params[:email],
firstname: params[:firstname],
lastname: params[:lastname])
redirect_to user_path(#user)
end
def show
#user = User.find(params[:id])
end
end
routes.rb:
Rails.application.routes.draw do
# Fubyonrails.org/routing.html
root :to => 'static#welcome'
resources :users, only: [:new, :create, :show]
resources :session, :only => [:new, :create, :destroy]
resources :studios
end
Routes file
uninitialized constant SessionController
is the error im getting
First, don't destroy your user. It deletes it from the database, you want them to be able to login again without creating a new user right?
You need to update your routes.rb file to be:
Rails.application.routes.draw do
# Fubyonrails.org/routing.html
root :to => 'static#welcome'
resources :users, only: [:new, :create, :show]
resources :sessions, :only => [:new, :create, :destroy] # changed to sessions
resources :studios
get 'logout' => 'sessions#destroy'
end
If you don't already, you need to define a SessionsController.
class SessionsController < ApplicationController
def destroy
# insert logic that actually logs them out, the below may already be enough
# for this purpose though
redirect_to root_url, notice: "Logged Out Successfully"
end
end
When the user clicks "Logout" it gets routed to the SessionsController#destroy action which redirects them to the root URL ('static#welcome')
There is more to it then that (i.e. the views and all that jazz) but this should be helpful enough

Why after validation of post title, when I have got validation error, browser passes to Rails server the previous value of post title?

I get posts by :title instead :id
routes.rb:
Rails.application.routes.draw do
#RSS
get 'feed' => 'posts#feed'
get 'archive' => 'posts#archive'
devise_for :users, controllers: { omniauth_callbacks: "callbacks" }
root to: "posts#index"
resources :posts, param: :title do
resources :comments, only: [:new, :create, :destroy]
resources :images do
resources :comments, only: [:new, :create]
end
resources :links do
resources :comments, only: [:new, :create]
end
resources :photos, only: [:new, :create,:destroy]
resources :songs, only: [:new, :create, :destroy]
end
post_controller:
class PostsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create, :show]
respond_to :html, :json, :rss, :atom
def index
if params[:search].blank?
#posts = Post.includes(:comments, :photos).all
else
#search = Post.search do
fulltext params[:search]
end
#posts = #search.results
end
respond_with(#posts)
end
def show
set_post
end
def new
#post = Post.new
end
def edit
#post = Post.find_by(title: params[:title])
end
def create
#post = Post.new(post_params)
#post.errors.add(:base, :invalid) unless #post.save
respond_with(#post)
end
def update
set_post # this #post dont get post becouse browser pass wrong title and rails dont find it
if #post.valid? # here error becouse #post hasnt find
#post.update(post_params)
else
#post.errors.add(:base, :invalid)
end
respond_with(#post)
end
def destroy
set_post
#post.destroy
respond_with(#post)
end
def feed
#posts = Post.all.reverse
respond_with(#posts)
end
def archive
#posts_by_year = Post.limit(300).all.order("created_at DESC").
group_by {|post| post.created_at.beginning_of_year}
end
private
def set_post
#fix N+1 queries and find by posts title
#post = Post.includes(:comments, :photos, :links).find_by(title: params[:title])
end
def post_params
params.require(:post).permit(:title, :content)
end
end
When I create new post, if I include in my post title a dot, I get from Rails error. Therefore I use validation format: method for this case.
post.rb:
class Post < ActiveRecord::Base
#overwrite the to_param for changing routes from posts/id to posts/title
def to_param
title
end
has_many :comments, dependent: :destroy, as: :commentable
has_many :images, dependent: :destroy
has_many :links, dependent: :destroy
has_many :photos, dependent: :destroy
has_many :songs, dependent: :destroy
validates :content, presence: true
validates :title, presence: true, length: { maximum: 100 }
validates :title, format: { without: /\./,
message: "must be without dot" }
searchable do
text :title, :content
end
end
After this, when I update the post,my validation format method works and I get my validation message 'must be without dot'. Well. I delete in my post title input field dots and submit form. Now browser send to server previous post title value with dot.
Started PATCH "/posts/my%20test%20post%20title%20with%20dot." for 127.0.0.1 at 2017-01-26 11:57:34 +0300
ActionController::RoutingError (No route matches [PATCH] "/posts/my%20test%20post%20title%20with%20dot."):
Therefore rails can't find post by title with dot an I get the error.
What can I solve this problem? Maybe :title instead :id in this case is bad idea?
I assume you're using form_for? Rails will set the title on the post object then run validation. When it fails it will display the edit view, form_for will then use the to_param method to set the url, which will use the posts updated title. When you try and update again it'll use the title from the url to try and find the post, but won't be able to because there isn't a post in the database with that title.
You should use something like friendly_id but if you really want to roll your own then a simple implementation would be to have a slug column which gets set based on the title after_validation but obviously you're going to have to make sure it's unique so personally I'd switch to friendly id or another gem that deals with slugs.

Nested routing and authorization using CanCanCan in Rails

There is the following routing:
resources :accounts, only: [:update] do
get 'search', on: :collection
resources :transactions, only: [:create]
end
Abilities:
can [:update, :search], Account
can [:create, :index], Transaction
Controller:
# Web API controller for actions on Transaction
class Api::V1::Web::TransactionsController < Api::V1::Web::ApplicationController
load_and_authorize_resource :account
load_and_authorize_resource :transaction, through: :account
def create
render json: params and return
end
end
When I try to create a new transaction I get an error:
CanCan::AccessDenied
in Api::V1::Web::TransactionsController#create
What am I doing wrong? How can I fix it? Thanks in advance.

AbstractController::ActionNotFound for destroy action

I am facing a weird problem
when I am trying to destroy active record it is giving this error.
AbstractController::ActionNotFound at /photos/6
The action 'destroy' could not be found for PhotosController
here is what my controller look like
class PhotosController < ApplicationController
before_filter :authenticate_user!
....
....
def delete
#photo = current_user.photos.find(params[:id])
#photo.destroy
redirect_to photos_path
end
and if I perform the same actions using console it is working fine(the record is getting deleted successfully ).
This is my routes file look like.
resources :photos, :only => [:index, :show, :new, :create] do
post 'upload', :on => :collection
end
I know I have not included :destroy in resources please suggest me how to insert :destroy action to delete photos
if you have resource of photo then action name should be destroy, not delete. and if not then please check your routes.
Seven default action that are generated by scaffolding are follow
index
new
edit
create
update
show
destroy # not delete
I am assuming you are using resources :photo.
The action should be destroy and not delete, as per the Rais Guide.
The seven default actions are: index, new, create, show, edit, update and destroy
def destroy
#photo = current_user.photos.find(params[:id])
#photo.destroy
redirect_to photos_path
end
You can also see the available routes by:
rake routes
EDIT
The problem is here: :only => [:index, :show, :new, :create], which is saying to rails: do not create destroy, edit or update routes.
To solve the problem, you can add destroy to it :only => [:index, :show, :new, :create, :destroy] or use :except instead: :except => [:edit, :update]
If you don't want to limitate the resources:
resources :photos do
post 'upload', :on => :collection
end
EDIT 2 - I don't really understand why you are trying to use delete instead of destroy, However, if you have a good reason for it:
resources :photos :only => [:index, :show, :new, :create] do
post 'upload', :on => :collection
get 'delete', :on => :member
end
In this way you will have the delete_photo_path, which can be used in your show view:
<%= link_to 'Delete', delete_photo_path(#photo) %>
Finally, the action delete should looks like:
def delete
#photo = Photo.find(params[:id])
#photo.destroy
redirect_to photos_path
end

How to share a controller action for nested routes in Rails?

In Rails, when I need:
/comments
and
/posts/1/comments
How do I best organize CommentsController? E.g. let the routes share the index actions, or work with 2 controllers?
You can work with only one controller.
I would go with a before_filter to check if the post_id param is present:
class CommentsController < ApplicationController
before_filter :find_post, only: [:index]
def index
if #post.present?
## Some stuff
else
## Other stuff
end
end
private
def find_post
#post = Post.find(params[:post_id]) unless params[:post_id].nil?
end
end
And have in your routes (with the constraints of your choice) :
resources :posts do
resources :comments
end
resources :comments
I believe you want /comments only for show and index actions, right? Otherwise the post params will be lost when creating or updating a comment.
In your routes.rb you can have something like:
resources : posts do
resources :comments
end
resources :comments, :only => [:index, :show]
In your form:
form_for([#post, #comment]) do |f|
And in your controller, make sure you find the post before dealing with the comments (for new, edit, create and update, such as:
#post = Post.find(params[:post_id])
#comment = #post...
You can do almost anything you want with rails routes.
routes.rb
match 'posts/:id/comments', :controller => 'posts', :action => 'comments'}
resources :posts do
member do
get "comments"
end
end

Resources