Rails /new path interpreted as ID - ruby-on-rails

I have a controller whose routes are defined as:
resources :items, except: [:new, :edit]
and the new and edit actions are not defined in the controller.
When I browse to /items/new I get an error from the database saying that the item was not found.
And the parameters contain {"id"=>"new"} from which I understand that the new part of the path is interpreted as the id.
How could I get the /items/new to fail routing?

You can use constraints on the :id segment of your routes.
If you know your id will always be a number for instance, try using:
resources :items, except: [:new, :edit], constraints: { id: /\d+/ }
This will prevent anything that does not match the /\d+/ regex (ie. one or several digits) to be considered as an id value, thus preventing the route to be matched for /items/new

You can simply do it in your controller to rescue it from failing by rendering a view
class ItemsController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
def record_not_found
render 'record_not_found'
#do the other stuff
true
end
end

Related

Rails Polymorphic Redirect Nested Resources

Bumping into a redirect problem with nested resources and polymorphic associations. I think I can find a way forward however I'd like to find out what can be considered as best practice.
I have the following nested resources:
namespace :navigate do
resources :boks, :only => [:show] do
resources :groups, :only => [:show]
resources :categories, :only => [:show]
resources :tools, :only => [:show, :index]
resources :artifact_types, :only => [:show]
resources :artifacts, :only => [:show, :index]
resources :processus do
resources :notes, module: :processus
end
end
end
Notes being a polymorphic association (that I will also later use with the Tool model).
Most of my code is inspired but the excellent gorails.com episode: https://gorails.com/episodes/comments-with-polymorphic-associations
The controller that I use to manage notes is:
class Navigate::NotesController < Navigate::NavigateController
before_action :authenticate_user!
def create
#note = #noteable.notes.new note_params
#note.user = current_user
#note.save
redirect_to [:navigate, #bok, #noteable], notice: "Your note was succesfully created."
end
def update
#note = #noteable.notes.where(user: current_user).first
if #note.update(note_params)
redirect_to polymorphic_url([:navigate,*** HOW TO REFERENCE BOK ***, #noteable]), notice: "Your note has been updated."
else
flash.now[:alert] = "Unable to update your note."
render :edit
end
end
private
def note_params
params.require(:note).permit(:content, :public)
end
end
Note the * HOW TO REFERENCE BOK * part. This is where my problem is. Once I update the "Note" I want to redirect to #noteable related controller (here Processus) but in order to construct the URL I need to have an #bok object which I don't have in this instance because I actually don't need it.
I can also retrieve the correct #bok model but I wonder if there is another way to deal with this redirect?
My URL for redirection should be http://localhost:3000/navigate/boks/1/processus/2 but in order to construct it I would need a Bok object which I haven't go in my controller above (as I don't need it).
Any ideas? Thanks!
You can't reference a route for a nested object without the id of the object it is nested into.

Rails - route index to first module's object

Say I have a module name Server that was created with a scaffold. I want the url 'www.example.com/server/' to be redirected to the first Server object that exists. So for example to be redirected to 'www.example.com/server/2'.
How could this be done with routes.rb (or any other way)?
route.rb:
Rails.application.routes.draw do
resources :servers
end
Server controller:
class ServersController < ApplicationController
before_action :set_server, only: [:show, :edit, :update, :destroy]
# GET /servers
# GET /servers.json
def index
#servers = Server.all
end
....
your can put
redirect_to server_path(Server.first) and return
inside your index method it'll redirect you when ever index action is called.
and just to extent #richfisher's answer (which might be a more appropriate way to do it.)
resources :servers, except: [:index] # this won't generate redundant routes
get '/servers/' => 'servers#first' #note this is now accessible via "server_path" instead of "servers_path" helper.
For what it's worth, I'd do this:
#config/routes.rb
resources :servers, except: :index do
get "", action: :show, id: Server.first.id, on: :collection
end
This will allow you to use the show action in place of index in a super efficient setup:
#app/controllers/servers_controller.rb
class ServersController < ApplicationController
def show
#server = Server.find params[:id]
end
end

Adding member or alias to existing path in Rails 3

For the jobs -> apply action I want to "alias" the "create" action.
In rake routes it would look something like this:
https://gist.github.com/YOUConsulting/19b404759757898a6f4f#file-rake_routes-rb
I've tried to do it like this but I think this is not exactly what I'm looking for:
resources :jobs, :only => [:show, :index, :create] do
resources :apply, :only => [:index, :create] do
member do
post :completed
end
end
resources :share, :only => [:index, :create]
end
In human words:
When the user submits the page located at "/jobs/<job_id_here>/apply" (the index view) the result page (the create view) should be "/jobs/<job_id_here>/apply/completed" instead of "/jobs/<job_id_here>/apply/" .
Reason:
When we track users via Google Analytics we can't see if they submitted the form successfully since there is no difference between "/apply" and "/apply" .
Hm, what about using a named route as path
Add a line at the end of your routes.rb
post '/jobs/:job_id/apply/completed', to: apply#create as: apply_job
And then in your new.html.haml
form_for :job, apply_job_path
This should call the create action of your apply controller.
Instead of apply you can use of course any name you want.

Rails cache sweeper fails on create

I have a controller where:
caches_action :show
cache_sweeper :the_model_sweeper, :only => [:update, :destroy]
and sweeper:
observe TheModel
def after_save(the_model)
expire_cache(the_model)
end
def after_destroy(the_model)
expire_cache(the_model)
end
def expire_cache(the_model)
expire_action :controller => '/the_model', :action => 'show'
end
and am getting:
ActionController::RoutingError (No route matches {:controller=>"/the_model", :action=>"show"}):
The problem I'm guessing is becuase the sweeper is called after_save, when on a new record there will be nothing to destroy, even though I have specifically said for it only to sweep on update or delete.
(I have obviously renamed the model to "The Model" for example purposes)
The problem was due to using ActiveAdmin, and forgetting (doh...) to add :only => [:update, :destroy] to the active admin config for that model

Rails/Devise - Creating new users via json request

I would like to do a new user signup via JSON but I get an invalid authenticity token error.
I would like to not turn the forgery check for all controller. Any suggestions on how to override the registrationcontroller to do this?
Here is my code:
class Api::MobileRegistrationsController < Devise::RegistrationsController
skip_before_filter :verify_authenticity_token
respond_to :json
def create
super
end
end
Routes:
Whitney::Application.routes.draw do
resources :apps
devise_for :users
namespace :api do
resources :tokens, :only => [:create, :destroy]
resources :MobileRegistrations, :only => [:create]
end
I get an error:
Routing Error
uninitialized constant Api::MobileRegistrationsController
I can't encourage you in this way, because your app will be vulnerable to CSRF attacks.
A good resource to understand CSRF : Understanding the Rails Authenticity Token
You should rather include the authenticity_token in your POST request. This is discussed in some questions on SO, like there (read all the answers) : rails - InvalidAuthenticityToken for json/xml requests
The idea :
Retrieve the token with <%= form_authenticity_token %>
Add a authenticity_token POST param to your request with the token.
If you pass the param by URI, don't forget to encoded the token value :
url += "&authenticity_token=" + encodeURIComponent( <%= form_authenticity_token %> );
You could buil your own controller that does not derive from a devise controller.
def UserSignupApiController < ApplicationController
skip_before_filter :authenticate_user!
respond_to :json
def create
#user = User.create(params[user])
respond_with(#user)
end
end
I think you get the idea. You just instantiate your User just like you would do in Rails console. I do not recommend this kind of practice though
For your error
Routing Error uninitialized constant
Api::MobileRegistrationsController
it indicates your controller is not in the correct folder.
Because you are using
namespace :api do
resources :tokens, :only => [:create, :destroy]
resources :MobileRegistrations, :only => [:create]
end
You need to put your MobileRegistrations into controllers/api folder. or you can use
scope "/api" do
resources :MobileRegistrations, :only => [:create]
end

Resources