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.
Related
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
I've got these three bottom routes below which are very error-prone because of other routes declared normally:
# normal routes
resources :documents, :except => [:show, :edit, :update]
resources :photos, :except => [:show, :index]
...
# error-prone routes
get ":client_code" => "share#index", :as => :shares, :format => false
get ":client_code/:id" => "share#show", :as => :share, :format => false
get ":client_code/:document_id/more/:component_id" => "share#more", :as => :more, :format => false
I've got a few methods in the ShareController to deal with the requests like so:
def show
get_user_by_parameter
if get_document_by_user_or_issue and #document.is_showable? and #parameter_user == #document.user
...
end
private
def get_user_by_parameter
#parameter_user = User.where(:client_code => params[:client_code]).first
end
def get_document_by_user_or_issue
if params[:id].match(/\D/)
#document = Document.where(:user_id => #user.id, :issue => params[:id]).first
else
#document = Document.find(params[:id])
end
end
I need the routes to be that minimal, but not only is this ugly and un-RESTful but it's very error prone.
The :client_code will always be the owner of the #document being viewed. It's kinda like a safety check/ownership kinda function. But, because of all the reasons listed above: is there a better way to write this? There's gotta be a better way than that.
Thanks.
Controller Based Check:
before_filter :find_document
def find_document
Document.find(params[:id])
end
def is_owner?(document)
redirect_to root_path if current_user.id != document.owner_id
end
Isn't a check like this much easier? I'm not sure why you have a share controller, so I don't want to be presumptuous here.
Which will allow you to do:
resources :shares, only: [:index, :show]
Also:
User.where(:client_code => params[:client_code]).first
Can be refactored to:
User.find_by(client_code: params[:client_code])
Assuming you are on latest rails version, else:
User.find_by_client_code(params[:client_code])
Let me know what the shares are for, I'm not sure I provided the full solution for you.
Cheers.
EDIT
if you are using shares to provide a different view, i suggest doing this:
Within the controller,
def index
if params[:shares]
render 'shares'
end
end
Unless you really wish to have a different route for it. This allows you to not have a shares controller for essentially what is the document model.
i have this controller:
gods_controller.rb
class GodsController < ApplicationController
def notifications
#reviews= Review.where("flag = ?", true)
respond_to do |format|
format.html # index.html.erb
end
end
end
routes.rb
resources :gods, :only => [:index] do
get :notifications
end
That route gives me this: http://example.com/gods/1/notifications
but what I want is this: http://example.com/gods/notifications
Thanks in advance!
Try:
resources :gods, :only => [:index] do
collection do
get :notifications
end
end
You can read more details about this here: http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
One thing you could do is, change your current route to this:
match "/gods/:id/notifications" => "gods#notifications"
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
class CommentsController < ApplicationController
def create
#commentable= context_object()
#comment = #commentable.comments.build(params[:comment].merge(:user_id => current_user.id))
if #comment.save
respond_to do |format|
format.js
end
else
render :action => 'new'
end
end
private
def context_object
params[:constraint][:context_type].singularize.classify.constantize.find( context_id )
end
def context_id
params["#{ params[:constraint][:context_type].singularize }_id"]
end
end
This commenting module has served me well but I ran into a hitch this morning, possibly because of my use of nested resources. Essentially, I now have a URL like:
/projects/3/albums/6/attachments/84
When I comment on that page, I get the error:
ActiveRecord::RecordNotFound (Couldn't find Project without an ID):
app/controllers/comments_controller.rb:102:in `context_object'
app/controllers/comments_controller.rb:14:in `create'
My routes file looks like:
resources :projects do
resources : albums do
resources :attachments
end
end
resources :attachments do
resources :comments, :only => [:create, :update,:destroy],
:constraint => {:context_type => "conversations"}
end
Any ideas on how I can get the commenting module to play nicely with commenting on project>Album>Attachment ?
Thanks for the input,
Posting this as an answer in order not to clutter the comments to the original question.
Since you don't have the requirement to keep attachments available via /attachments - making the second resources block useless, do something like this:
resources :projects do
resources :albums do
resources :attachments do
resources :comments, :only => [:create, :update,:destroy],
:constraint => {:context_type => "conversations"}
end
end
end
That's going to change your routes helpers (_path and _url), go through your controller(s) and view(s) and change them to reflect your new helpers.
Specifically, attachment_comments_path becomes project_album_attachment_comments_path.
The full list of routes for those models can be viewed by running rake routes in a console. I'd also recommend you take a closer look to the Rails routing guide.