ActiveAdmin Inherited Ressource override doesn't work with multiple namespaces? - ruby-on-rails

I'm using ActiveAdmin with no default namespace (config.default_namespace = false). I have a User Resource without namespace as well as an User Resource in the :admin namespace. Both use custom update methods to achieve different behavior (users can change their own data, while admins can reset the password of any user).
This is the "default" user:
ActiveAdmin.register User do
actions :show, :edit, :update
menu false
permit_params ...
controller do
def update
# change account data
...
end
end
form do |f|
...
end
end
And this is the admin User:
ActiveAdmin.register User, namespace: :admin do
actions :all
menu
permit_params ...
controller do
def create
# invitation code
...
end
def update
# password reset code
...
end
end
index do
...
end
filter ...
form partial: 'form'
end
Changing the user data works just fine, as well as inviting new users. The problem is the password reset. When submitting the corresponding form (route /admin/users/[id]/edit), the update of the non-namespaced users is called (same as when submitting /users/[id]/edit) instead of the update in my :admin namespace User resource.
Is this a bug or did I misconfigure something? I'm honestly stumped by this behavior, I don't even know how to proceed with debugging this.

The problem wasn't due to the namespaces, but because of my form...
I'm using semantic_form_for, which automatically configures everything based on the model. Since it receives an User, the form action will always send the data to the default User route instead of the :admin route. Manually setting the url fixed the problem.

Related

How to set routes for specific users in rails 7

I am having an issue with Rails_admin. rails_admin is successfully added to the app and working fine.
Issue is when I am trying to set the routes to a specific role user.
My app consists of several role like user, client, admin etc.
What I want here is only user with role 'admin' can access to rails_admin section by either using "link_to 'rails_admin_path'" or http://127.0.0.1:3000/admin.
Already I am having an admin section so I don't want to add any other login section for rails_admin, just need the features of rails_admin in my admin.
And I've a method called "check_admin" which will check the role of the current_user is admin or not
current_user.check_admin
#routes.rb
Rails.application.routes.draw do
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
end
Here what my requirement is, the given route can be only accessed by admin user
hints: check_admin or current_user.roles.admin.present?
Solution
routes.rb
authenticate :user, -> (u) { u.roles.admin.present? } do
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
end
Change route under a condition where it check for the particular role, in my case its "admin".
So the other users who are not an admin can't get an access to rails_admin in anyway
Upon request, expanding on my previous comment...
class AdminController < ApplicationController
before_action :reject_non_admins
def index
end
def show
end
# etc... all the admin CRUD actions
private
def reject_non_admins
unless current_user.check_admin
render "unauthorized.html" and return
end
end
end
so non-admin users are not prevented from accessing the sensitive admin pages, but they're just shown a page that tells them they're not allowed to see the content.
# app/views/admin/unauthorized.html
<p>Sorry, only admins can see this page</p>
The routes configuration is not the correct place to prevent the non-admin user from accessing the page. The routes configuration has no concept of current_user.
It should be done in the controller.
def show
unless current_user.roles.admin.present?
render "unauthorized"
end
# default "show.html will render
end

Using devise for user registration/login redirect user to a different form page based on their response

I am using devise for user registration/login, after the user has successfully signed up, I want to show a page/a dialog box and redirect to another page based on user response. How can I do that?
User Model (By devise)
username
password
Student Model
name
student_id
Teacher Model
name
grade
First_page:
signup signin links
Signup link will show the devise views/devise/registrations/new.html.erb page.
After successful signup, it takes the user to root page. I have defined the root page in routes.rb:
` Rails.application.routes.draw do
devise_for :users
resources :students, :teachers
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
root to: "students#index"
end `
At this point, the application doesn't have any idea who the user is.
So, I want to get the identity information(student/teacher) from the user.
How will I get this information?
Student/Teacher controller:
`class StudentsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
def index
#students = Student.all
end
def new
#student = Student.new
end
def create
current_user.create_student(student_params)
redirect_to root_path
end
private
def student_params
params.require(:student).permit(:name, :skypid)
end
end`
After the user has successfully signed in, I want to ask if the user is a student or teacher. Based on what they select, redirect them to a student form page or teacher form page.
How can I do that in rails?
Thank you
You can write a custom after_sign_in_path_for function in your ApplicationController assuming you're using all the default Devise controllers otherwise. Any named path helper or other route that it returns will be where the user is redirected, so you could do something simple like always redirect to a selection page that presents the options and handles the choice on a subsequent action:
def after_sign_in_path_for(resource)
user_type_selection_path # whatever route in your app manages the selection
end
Alternately, you could invoke a custom method on the user model in that function to make a choice right there:
def after_sign_in_path_for(resource)
resource.student? ? student_path : teacher_path
end
You could hybridize these of course as well to do the latter when the selection has already been made and redirect otherwise, with something similar to the following:
def after_sign_in_path_for(resource)
if resource.user_type_chosen?
resource.student? ? student_path : teacher_path
else
user_type_selection_path
end
Bear in mind that none of those functions or paths are real, since I can't be more specific on the information you've provided, but hopefully this gets you headed in the right direction. The after_sign_in_path_for hook is your primary tool here, unless you get into the world of overriding the default devise controllers and interrupting the usual workflow there to accommodate this step, which doesn't seem strictly necessary by your description.

How to define a route in rails hiding information like ids in urls

I had just started ruby on rails and i am not getting how to hide ids in urls, so that it won't be visible to user
For example for creating a new user, the route will be
users/new
And for editing an existing user, route should be -
users/:id/edit
But id is an unnecessary detail to user. So how we can hide it from users and what will be the new route.
There are at least two perspective to this problem.
The first perspective is that of a user. I'm assuming you're talking about a user editing his own profile. In this case, the ID is indeed redundant. I recommend you handle this use case by adding a resource named profile and the corresponding ProfilesController. In config/routes.rb add:
resource :profile, only: [:show, :update]
Note it reads resource, not resources.
The second perspective is that of an administrator. In this case, it's better to use resources (not resource) so that the administrator is able to edit any user he wishes. On top of that, the administrator may have some extra capabilities that regular users lack (for example: making someone an admin).
You will need to have some unique identifier in the url. However, this unique identifier does not have to be the auto incremented id generated by your database. If you have ensured uniqueness on another field (say username), you could use that as the part of your route.
users/:username/edit
Using This link, we can see that the resources definition in the routes can be done for all the routes in one line
resources :users, param: :username
Your UsersController would look something like this
class UsersController
...
def edit
#user = User.where(username: params[:username]).first
...
end
end
You can also set the to_param method on the model to the new identified username
class User
...
def to_param
username
end
...
end
This will allow you to do
#user = User.where(username: "test123")
edit_user_path(#user) #=> /users/test123/edit
thanks to Simple Lime for pointing that out.
EDIT
The new route would be unchanged by this update. It would still be
/users/new
You can define post route
post 'users/edit', to: user#edit
and in edit method you can get id from params

Rails 3, Cancan: defining ability on custom controller action acting on collection, not member

I have a RESTful controller for the model UserResource. I added a custom action called remote_update and I want to limit that action only if the user's id matches:
if user.has_role? :admin
can :manage, :all
elsif user.has_role? :regular
can [:remote_update], UserResource, :user_id => user.id
end
I am using load_and_authorize_resource in the controller.
The problem is that users are still able to use that action even if their user id does not match. (To test, I am using Firebug and changing the hidden value of the id).
My route is as follows:
resources :user_resources do
collection do
post 'remote_update'
end
end
According to https://github.com/ryanb/cancan/wiki/Authorizing-controller-actions, when we have custom actions, Cancan tries to load the resource using the id, from the link:
def discontinue
# Automatically does the following:
# #product = Product.find(params[:id])
# authorize! :discontinue, #product
end
I don't have an id defined because it is a POST, not a GET or PUT.
THoughts on how to construct the ability? Thank you.
It looks like you are trying to do an update ('remote_update') with a POST. A POST is supposed to create, and thus should not normally have a populated id. Thus I would not expect CanCan to do that lookup for you.
I suggest that you either:
Manually find the product and authorize it in your discontinue method,
or
Use a PUT
btw, The ability looks correct to me.

Rails 3.2 CanCan - Restrict access to projects url not Owned

I'm trying to restrict access to Projects that a user did not create. This seems to be working fine with:
if user.has_role?(:Student)
can :create, Project
can :manage, Project, :user_id => user.id
end
(Side Question: Is there a better way to write that? ^^)
However, I can still access the URL: /users/5/projects
I just can't see the projects as expected. I'd rather it tell me that I cannot access the page, and redirect. I do have this in my application controller:
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.message
end
But I don't receive a redirection or error message. Do I need to add something else to the abilities to make that work?
I do have load_and_authorize_resource in both the ProjectsController and UsersController.
For the record, my routes look like this:
resources :users do
resources :projects
end
Try this one
if user.has_role?(:Student)
can :create, Project
can :manage, Project do |P|
user && P.user == user
end
It will check whether current user owns the project or not. If he doesn't own the project then he won't be able to modify it.
First condition is take just to check whether user object exist or not, you can also use exception handler there. Here's an example of that:
comment.try(:user) == user
If you want to enable the redirect behavior if the user cannot read any project in the current collection, override the index action and add extra enforcement:
def index
# #projects is loaded by the CanCan before filter load_and_authorize_resource
unless #projects.any?{|project| can?(:read, project)}
raise CanCanAccessDenied.new("no project readable there", :read, Project)
end
end
In index-like (collection) controller actions, CanCan enforces ACL via authorize :read, ModelClass.
https://github.com/ryanb/cancan/wiki/Checking-Abilities see "Checking with Class" section.
As you can read, if there is the possibility that the user to :read any of the ModelClass instances (even if these instances do not yet exist) the query authorize :action, ModelClass will authorize.
Given your URL /users/5/projects and routes resources :users do resources :projects end I belive this action is an index on projects for a specific user. So the CanCan index action will authorize, given can :manage, Project, :user_id => user.id, so there can exist projects the user can :read as such => authorize. Later on in the view I belive you authorize each specific project instance can? :read, project, and there is where they get filtered, as such the page remains empty.

Resources