Devise: path helper for a resource within two scopes - ruby-on-rails

I'm trying to achieve the following:
I have two entry points to the application (/admin and /sales)
I have generated a resource (rails generate scaffold) which is called customers. Admin and sales users will need to access this resource from both routes. eg. /admin/customers and /sales/customers.
This is working so far, here comes the actual problem. Although I'm logged in as a sales user, the path helpers (for example new_customer_path) all point to /admin/*
My routes.rb file:
authenticated :user, ->(u) { u.role == 'admin' } do
scope '/admin' do
resources :customers
root 'default#index', as: :admin_authenticated_root
end
end
authenticated :user, ->(u) { u.role.start_with? 'sales' } do
scope '/sales' do
resources :customers
root 'default#index', as: :sales_authenticated_root
end
end
With this I end up with the following routes.
customers GET /admin/customers(.:format) customers#index
POST /admin/customers(.:format) customers#create
new_customer GET /admin/customers/new(.:format) customers#new
edit_customer GET /admin/customers/:id/edit(.:format) customers#edit
customer GET /admin/customers/:id(.:format) customers#show
PATCH /admin/customers/:id(.:format) customers#update
PUT /admin/customers/:id(.:format) customers#update
DELETE /admin/customers/:id(.:format) customers#destroy
GET /sales/customers(.:format) customers#index
POST /sales/customers(.:format) customers#create
GET /sales/customers/new(.:format) customers#new
GET /sales/customers/:id/edit(.:format) customers#edit
GET /sales/customers/:id(.:format) customers#show
PATCH /sales/customers/:id(.:format) customers#update
PUT /sales/customers/:id(.:format) customers#update
DELETE /sales/customers/:id(.:format) customers#destroy
I really need to find a proper solution, since updating all the code generated by the scaffold generator doesn't seem feasible to me.
There must be a better way than different path helpers for each scope. I don't want to do something like this (in every generated file):
`send("#{current_user.role}_customers_path")`
I was under the impression, that the admin routes would not even be loaded when I'm logged in as a sales user, but I just started out with working with devise, so I have only very little knowledge about it.
Edit:
I guess I could just create a helper and override the path helpers rails is providing:
def customers_path
if current_user.role == 'sales'
sales_customers_path
end
end

Try this:
scope '/admin', as: :admin do
resources :customers
root 'default#index', as: :authenticated_root
end
and the same for /sales scope.
UPDATE: Oh, I see it now, you actually don't want 2 different paths, but instead generic helper that would give different values depending on current_user value. Sorry, routes don't work this way, they get loaded (all of them!) at the application boot time (that's why you can't "delete" some routes depending on your constraint during the request) and first matched one from top to bottom is taken.
Yes, your solution in EDIT section is one way to go, but I'd rather used my custom helper for that, because otherwise it would be great surprise for newcomer in your project why the same url_helper produces different urls, until he finally stumbles to your overriden helper.

Related

Why does order matter in Rails routes.rb when using resources?

I receive an error when my route is listed as such:
resources :coupons
get 'coupons/redeem_coupon', to: 'coupons#redeem_coupon', as: 'redeem_coupon'
The error is:
ActiveRecord::RecordNotFound - Couldn't find Coupon with 'id'=redeem_coupon:
When I reverse the order to:
get 'coupons/redeem_coupon', to: 'coupons#redeem_coupon', as: 'redeem_coupon'
resources :coupons
It works fine. I understand that resources creates these routes
GET /coupons
GET /coupons/new
POST /coupons
GET /coupons/:id
GET /coupons/:id/edit
PATCH/PUT /coupons/:id
DELETE /coupons/:id
Is listing my custom route first, more specific or overriding the other route? Why does the order matter?
The error you're getting is because rails tries to match routes starting from the top down. If you're trying to add a custom route to an existing resource, the easier way is to do this. collection is if you want to use it on the group, member is if you want to add a custom route to an individual resource.
resources :coupons do
collection do
get 'redeem_coupon'
end
end
By listing your custom route first, you are overriding the other route. When rails gets a request, it simply starts from the top of your routes.rb file and goes with whichever route matches first.

Accessing a Module Resource with Rails 4.x

I am using rails 4.1 with Casein CMS: https://github.com/russellquinn/casein
I have setup a Post Model, view and controllers within casein, but I would like to access the Posts outside of casein, possibly under another route called blog
I have tried and tried reworking my routes and controllers, and have an array of errors to list. Someone here might know just the trick to get this working, and was hoping some could help me, or at least explain to me what should be happening or what I might be doing wrong.
What Casein adds to the routes is this:
#Casein routes
namespace :casein do
resources :posts
end
And I'd like to match the index and show actions to => /blog. How might I write this correctly in my routes.rb.
My controller, I have basically extracted the actions from the Casein's PostsController, and along with including the Casein Module have tried to simple list all the posts.
Here is what my blogs_controller's index action looks like:
class BlogsController < ApplicationController
module Casein
def index
#casein_page_title = 'Posts'
#posts = Post.order(sort_order(:title)).paginate :page => params[:page]
end
end
end
By the end I'd also like to take blogs to blog, but I think can take it from there, but if anyone has any suggestions, that would be much appreciated.
You might be asking for this, but your question is not very clear.
If you want to have the following routes and use the same controller for each.
Prefix Verb URI Pattern Controller#Action
casein_posts GET /casein/posts(.:format) casein/posts#index
POST /casein/posts(.:format) casein/posts#create
new_casein_post GET /casein/posts/new(.:format) casein/posts#new
edit_casein_post GET /casein/posts/:id/edit(.:format) casein/posts#edit
casein_post GET /casein/posts/:id(.:format) casein/posts#show
PATCH /casein/posts/:id(.:format) casein/posts#update
PUT /casein/posts/:id(.:format) casein/posts#update
DELETE /casein/posts/:id(.:format) casein/posts#destroy
blog GET /blog(.:format) casein/posts#index
GET /blog/:id(.:format) casein/posts#show
then your config/routes.rb file should contain
namespace :casein do
resources :posts
end
get '/blog', to: 'casein/posts#index'
get '/blog/:id', to: 'casein/posts#show'
And you need your controller to be app/controllers/casein/posts_controller.rb
But I'd really strongly encourage you to use 2 different controllers, and a concern for the shared methods

Rails route parent ID name and CanCan issue

I'm trying to get a simple route working
/agenda_items/5/feed
To do this, I have the following route setup
resources :agenda_items do
member do
get "/feed", to: "comments#feed"
end
end
In each of my controllers, I'm using CanCan to handle the authentication and it works fine, however on this one action I'm having an issue, which I'm pretty sure is down to railsnaming generation. When I runrake routes`, the route above is produced as
feed_agenda_item /agenda_items/:id/feed(.:format) agenda_items/:id#feed
As far as I can tell, CanCan is expecting the :id parameter, to actually be :agenda_item_id so as a result, my parent resource isn't being loaded.
Is there any way I can get rails to change this so that CanCan will work without me having to manually load and authorize the resource, or is there a way I can get CanCan to change what it's looking for on certain actions?
The problem is that your routes are wrong. You try to create a member action for agenda items which routes to the comments controller. If you want a feed of all the commments from a single agenda item you should do something like this:
resources :agenda_items do
resources :comments do
collection do
get :feed
end
end
end
You should now get the following when running rake routes:
feed_agenda_item_comments /agenda_items/:agenda_item_id/feed(.:format) comments#feed

How to test controllers with nested routes using Rspec?

I have 2 controllers that I created using scaffold generator of rails. I wanted them to be nested in a folder called "demo" and so ran
rails g scaffold demo/flows
rails g scaffold demo/nodes
Then I decided to nest nodes inside flows, and changed my routes file like so:
namespace :demo do
resources :flows do
resources :nodes
end
end
But this change resulted on the rspec tests for nodes breaking with ActionController::Routing errors.
15) Demo::NodesController DELETE destroy redirects to the demo_nodes list
Failure/Error: delete :destroy, :id => "1"
ActionController::RoutingError:
No route matches {:id=>"1", :controller=>"demo/nodes", :action=>"destroy"}
The problem is rspec is looking at the wrong route. It's supposed to look for "demo/flows/1/nodes". It also needs a mock model for flow, but I am not sure how to provide that. Here is my sample code from generated rspec file:
def mock_node(stubs={})
#mock_node ||= mock_model(Demo::Node, stubs).as_null_object
end
describe "GET index" do
it "assigns all demo_nodes as #demo_nodes" do
Demo::Node.stub(:all) { [mock_node] }
get :index
assigns(:demo_nodes).should eq([mock_node])
end
end
Can someone help me understand how I need to provide the flow model?
You have two different questions going on here, so you may want to split them up since your second question has nothing to do with the title of this post. I would recommend using FactoryGirl for creating mock models https://github.com/thoughtbot/factory_girl
Your route error is coming from the fact that your nested routes require id's after each one of them like this:
/demo/flows/:flow_id/nodes/:id
When you do the delete on the object, you need to pass in the flow ID or else it won't know what route you are talking about.
delete :destroy, :id => "1", :flow_id => "1"
In the future, the easiest way to check what it expects is to run rake routes and compare the output for that route with what you params you are passing in.
demo_flow_node /demo/flows/:flow_id/nodes/:id(.:format) {:action=>"destroy", :controller=>"demo/flows"}

Rails 3 Routing - specifying an exact controller from within a namespace

I'm having a bit of a problem with route namespaces that I've not encountered before. This is actually a part of some gem development I'm doing - but i've reworked the problem to fit with a more generic rails situation.
Basically, I have a namespaced route, but I want it to direct to a generic (top-level) controller.
My controller is PublishController, which handles publishing of many different types of Models - which all conform to the same interface, but can be under different namespaces. My routes look like this:
# config/routes.rb
namespace :manage do
resources :projects do
get 'publish' => 'publish#create'
get 'unpublish' => 'publish#destroy'
end
end
The problem is that this creates the following routes:
manage_project_publish GET /manage/projects/:project_id/publish(.:format) {:controller=>"manage/publish", :action=>"create"}
manage_project_unpublish GET /manage/projects/:project_id/unpublish(.:format) {:controller=>"manage/publish", :action=>"destroy"}
Which is the routes I want, just not mapping to the correct controller. I've tried everything I can think of try and allow for the controller not to carry the namespace, but I'm stumped.
I know that I could do the following:
get 'manage/features/:feature_id/publish' => "publish#create", :as => "manage_project_publish"
which produces:
manage_project_publish GET /manage/projects/:project_id/publish(.:format) {:controller=>"publish", :action=>"create"}
but ideally, I'd prefer to use the nested declaration (for readability) - if it's even possible; which I'm starting to think it isn't.
resource takes an optional hash where you can specify the controller so
resource :projects do
would be written as
resource :projects, :controller=>:publish do
Use scope rather than namespace when you want a scoped route but not a controller within a module of the same name.
If I understand you correct, you want this:
scope :manage do
resources :projects, :only => [] do
get 'publish' => 'publish#create'
get 'unpublish' => 'publish#destroy'
end
end
to poduce these routes:
project_publish GET /projects/:project_id/publish(.:format) {:action=>"create", :controller=>"publish"}
project_unpublish GET /projects/:project_id/unpublish(.:format) {:action=>"destroy", :controller=>"publish"}
Am I understanding your need correctly? If so, this is what Ryan is explaining.
I think what you want is this:
namespace :manage, module: nil do
resources :projects do
get 'publish' => 'publish#create'
get 'unpublish' => 'publish#destroy'
end
end
This does create the named routes as you wish(manage_projects...) but still call the controller ::Publish
It's a bit late but for anyone still struggling with this, you can add a leading slash to the controller name to pull it out of any existing namespace.
concern :lockable do
resource :locks, only: [] do
post "lock" => "/locks#create"
post "unlock" => "/locks#destroy"
end
end
Now if you include that concern anywhere (either namespaced or not) you will always hit the LocksController.

Resources