Rails, Devise: Same resource, different controller

2019-06-05 21:25发布

问题:

I have a resource, say Product, which can be accessed by two different user classes: say Customer and Admin. There is no inheritance between these two.

I am using Devise for authentication:

# config/routes.rb

devises_for :customers
deviser_for :admins

I have these two controllers:

# app/controllers/customers/products_controller.rb

class Customers::ProductsController < ApplicationController

and

# app/controllers/admins/products_controller.rb

class Admins::ProductsController < ApplicationController

Now depending on who logs in (Customer or Admin), I want products_path to point to the corresponding controller. And I want to avoid having customers_products_path and admins_products_path, that's messy.

So I have setup my routes as such

# config/routes.rb

devise_scope :admin do
  resources :products, module: 'admins'
end

devise_scope :customer do
  resources :products, module: 'customers'
end

This doesn't work. When I login as a Customer, products_path still points to Admins::ProductsController#index as it is the first defined.

Any clue? What I want to do might simply be impossible without hacking.

UPDATE According to the code, it is not doable.

回答1:

It turns out the best way to achieve that is to use routing constraints as such:

# config/routes.rb

resources :products, module: 'customers', constraints: CustomersConstraint.new
resources :products, module: 'admins', constraints: AdminsConstraint.new


# app/helpers/customers_constraint.rb

class CustomersConstraint
  def matches? request
    !!request.env["warden"].user(:customer)
  end
end


# app/helpers/admins_constraint.rb

class AdminsConstraint
  def matches? request
    !!request.env["warden"].user(:admin)
  end
end

I stored the constraint objects in the helper folder because I don't really know the best place to put them.

Thanks to @crackofdusk for the tip.



回答2:

For this you will need to override the existing after_sign_in_path_for method of devise. Put this in your app/controllers/application_controller.rb

def after_sign_in_path_for(resource)

      if resource.class == Admin   

          '/your/path/for/admins'

      elsif resource.class == Customer

        '/your/path/for/customers'

      end 
end

Note: If you want to implement previous_url that was requested by user then you can use it like this:

  session[:previous_url] || 'your/path/for/admins'