Allowing admins to add users with Devise - ruby-on-rails

I'm trying to make it so only admins can add uses with devise. I've gotten it mostly working however now when I'm logged in as an admin and submit the sign up form it kicks me back with the error: You are already signed in.
I've tried to follow the instructions here: http://wiki.summercode.com/rails_authentication_with_devise_and_cancan but it doesn't seem to mention this situation.
Do I need to do further overriding in the editors_controller to allow this?
Here are my routes ("editors" is the name of my user model):
devise_for :admins, :skip => [:registrations]
as :admin do
get 'admin/editors' => 'editors#index', as: :admin_editors
get 'admin/editors/new' => 'editors#new', as: :new_editor
delete 'admin/editors/:id' => 'editors#destroy', as: :destroy_editor
end
devise_for :editors, :skip => [:registrations], :controllers => { :registrations => "editors" }
and my editors_controller in "app/controllers/"
class EditorsController < Devise::RegistrationsController
before_filter :check_permissions, :only => [:new, :create, :cancel]
skip_before_filter :require_no_authentication
def dashboard
render "editors/dashboard.html.haml"
end
def index
#editors = Editor.all
respond_to do |format|
format.html
end
end
private
def check_permissions
authorize! :create, resource
end
end
EDIT
I noticed this Processing by Devise::RegistrationsController#create as HTML in the logs when I submit the form. I had suspected that perhaps the skip_before_filter :require_no_authentication wasn't being called, but assumed that because the EditorsController was inheriting from RegistrationController that before filter would work properly. Is that not the case?

You'll want to implement your own create method on EditorsController instead of inheriting that action from Devise::RegistrationsController. As you're seeing, the method in Devise::RegistrationsController will first check to see if you're already logged in and kick you back if you are. If you're not logged in it will create a User account and then log you in as that user.
You're trying to get around that problem with skip_before_filter :require_no_authentication, but it's likely that your form is POSTing to /editors instead of /admin/editors. So, you'll need to add a route that allows you to get to create on the EditorsController :
as :admin do
post 'admin/editors' => 'editors#create'
# your other :admin routes here
end
Then you'd want to implement a scaled down version of create. You probably want something kind of like this :
class EditorsController < Devise::RegistrationsController
def create
build_resource(sign_up_params)
if resource.save
redirect_to admin_editors_path
else
clean_up_passwords resource
respond_with resource
end
end
# your other methods here
end
You'll also want to make sure that the admin/editors/new template is pointing the form to the correct route ('admin/editors').

None of the googleable solutions worked when I tried them. This works
What I did was create a new action in the controller and a new route for it, and connect the links on my views that normally connect to create to now call my route and action.
But that wasn't enough. Because Devise is listening and will grab any add you try to do and validate it through it's own code. So instead I just add the new user record with a sql insert.
Add this route
post 'savenew', to: 'users#savenew'
Add this action to the user controller:
def savenew
rawsql = "insert into users (email, created_at,updated_at) values ('#{user_params[:email]}',now(), now())"
sql = rawsql
ActiveRecord::Base.connection.execute(sql)
redirect_to action: 'index''
end
View: new.html.erb
change the form_for so that submit will go to the new route and action, not the default Rails one.
<%= form_for User, :url => {:action => "savenew"} do |f| %>

Using Rails 4.2.6 here (my model is User instead of Editor). The following solution bypasses (I think) any devise actions that may interfere with new User creation by the admin:
Add this action to the Users controller:
def savenew
User.create_new_user(user_params)
redirect_to action: 'index'
end
Add this private method to the Users controller if it does not exist:
private
def user_params
params.require(:user).permit(:email, :password,
:password_confirmation)
end
Add this to config/routes.rb:
match '/savenew', to: 'users#savenew', via: :post
Add this class method to the User model:
def self.create_new_user(params)
#user = User.create!(params)
end
I don't have a separate Admin class in my application. Instead, I defined an admin attribute for Users and check for it with a :validate_admin before_action filter in the UsersController.
I wanted to be able to create a new user from the :index view, so I added a button:
<%= button_to 'New User', '/new_user', class: 'btn btn-primary',
method: :get %>
You might have to tweak the above solution if you have any after_create actions in the User model (e.g. sending a welcome email).

Related

Redirecting to another view in rails

I'm trying to build a link shortener. The intended behavior is that on the first page (new) the user inserts his long link and presses a button, then he gets redirected to an another page called result, where a preset message will be waiting for him, along with both his short and long link.
I'm struggling with controllers, however, as no matter what I do something always comes wrong. Right now my controller looks like this:
class UrlsController < ApplicationController
def new
#short_url = Url.new
end
def create
#short_url = Url.new(url_params)
if #short_url.save
flash[:short_id] = #short_url.id
redirect_to "/urls/result"
else
render action: "new"
end
end
def show
Url.find(params[:id])
##short_url_yield =
redirect_to #short_url.url
end
def result
end
private
def url_params
params.require(:url).permit(:url)
end
end
And the routes.rb:
Rails.application.routes.draw do
resources :urls, :only => [:show, :new, :create, :result]
get 'urls/result' => 'urls#result'
root to: redirect('/urls/new')
end
When I submit the link, however, rails returns the following error:
Couldn't find Url with 'id'=result
Extracted source (around line #17):
def show
Url.find(params[:id])
##short_url_yield =
redirect_to #short_url.url
end
It seems I don't understand the logic behind it. What's going wrong? Isn't the show bit supposed to be a redirect that happens when I click the shortified link?
Rails routes have priority in the order they are defined. Since your SHOW route declaration is before get 'urls/result' => 'urls#result' the url gets matched as /urls/id=result.
Simply move your custom route above the resources block or use a collection block.
resources :urls, :only => [:show, :new, :create, :result] do
collection do
get 'result'
end
end
Using the collection and member blocks tells Rails to give priority to the routes inside over the normal CRUD actions such as show.

Creating custom routes where a model attribute is used instead of the ID in Rails

I have a model called Student, which has an attribute called University_ID in it.
I created a custom action and route which displays the details of a specific student via the following link :students/2/detailsi.e. students/:id/details
However, I want to be able to allow the user to use their university ID instead of the database ID so that the following would work for instance students/X1234521/details
i.e. students/:university_id/details
My route file looks like this at the moment:
resources :students
match 'students/:id/details' => 'students#details', :as => 'details'
However this uses the Student_ID as opposed to the University_ID, and I've tried doing
match 'students/:university_id/details' => 'students#details', :as => 'details', but that only corresponds to the Student_ID, not the University_ID.
My controller looks like this, if this helps in any way:
def details
#student = Student.find(params[:id])
end
I also tried doing #student = Student.find(params[:university_id]) but nope, nothing worked.
After chatting with #teenOmar to clarify the requirements, here's the solution we came up with, which allows for the existing students/:id/details route to accept either an id or a university_id (which starts with a w), and uses a before_filter to populate #student for use in various controller actions:
class StudentsController < ApplicationController
before_filter :find_student, only: [:show, :edit, :update, :details]
def show
# #student will already be loaded here
# do whatever
end
# same for edit, update, details
private
def find_student
if params[:id] =~ /^w/
#student = Student.find_by_university_id(params[:id])
else
#student = Student.find(params[:id])
end
end
end

Forgot password action, can I handle post with the same action name?

What is the rails way, to create seperate actions for get or post?
Is it possible to have the same name but decorate the action to only run if its a get or post?
e.g.
'get only'
def forgot_password
end
'post only'
def forgot_passord
end
Rails way is to have a resource -- Each method should have a responsibility.
resources :password_resets
And then you'll let a user reset their password by visiting the form:
link_to 'Lost your Password?', new_password_reset_path
And then the form will post to create a new password_reset... That will send an email with a link to show the password_reset.
form_tag(password_resets_path, :method=>:post) do
When the use enters their updated password, it will update the password_reset.
# in routes.rb
resources :password_resets
# in app/controllers/password_resets.rb
class PasswordResets < ApplicationController
def new
#user = current_user
# render new reset form
end
def create
#user = current_user
#new_password = User.generate_random_password
if #user.update_attributes(:password => #new_password)
UserMailer.password_reset(#new_password).deliver
flash[:notice] = 'Successfully reset your password, check your email!'
else
flash[:error] = 'Could not reset password'
end
redirect_to login_path
end
end
Name them differently and create two routes in config/routes.rb. If you really really want one action doing different things, which is not a great idea, check request.method or request.get?, request.post?, etc.
you can rename the actions in your controller
#get only
def get_forgot_password
end
#post only
def post_forgot_passord
end
and then on your routes.rb
match '/forgot_password' => 'pass_reset#get_forgot_password', :via => 'get'
match '/forgot_password' => 'pass_reset#post_forgot_password', :via => 'post'
the :via option do the trick.

How to add action 'current_user' to a restful 'user'?

I have a model 'User', it's a restful resource, and has the default methods like 'index, show, new, create' and others.
Now, I want to define a new action 'current_user', to show the information of current logged-in user, which is different from 'show'.
When I use:
link_to current_user.name, :controller=>'users', :action=>'current_user'
The generated url is http://localhost:3000/users/current_user, and error message is:
Couldn't find User with ID=current_user
Shall I have to modify the routes.rb? What should I do?
I have searched for some articles, and still have no idea.
Add
map.resources :users, :collection => {:current => :get}
Then, I use:
link_to 'current', current_users_path()
The generated url is:
http://localhost:3000/users/current
Now, everything is OK. Is this the best solution?
See my comment on the other answer for an explanation
map.current_user "users/current", :controller => :users, :action => :current
View:
link_to 'current', current_user_path
I would not add a new action for this. I would check the id passed to the show method.
class UsersController
def show
return show_current_user if params[:id] == "current"
# regular show code
end
private
def show_current_user
end
end
In the view use :current as the user id while generating path.
user_path(:current)

Rails route dependent on current user

I'd like to create a rails route for editing a user's profile.
Instead of having to use /users/:id/edit, I'd like to have a url like /edit_profile
Is it possible to create a dynamic route that turns /edit_profile into /users/{user's id}/edit, or should I do thing in a controller or?
You might want to create a separate controller for this task but you could also continue using users_controller and just check whether there is a params[:id] set:
def edit
if params[:id]
#user = User.find(params[:id])
else
#user = current_user
end
end
But you should note that /users normally routes to the index action and not show if you still have the map.resources :users route. But you could set up a differently called singular route for that:
map.resources :users
map.resource :profile, :controller => "users"
This way /users would list all the users, /users/:id would show any user and /profile would show the show the currently logged in users page. To edit you own profile you would call '/profile/edit'.
Since a route and controller serve two different purposes, you will need both.
For the controller, assuming you're storing the user id in a session, you could just have your edit method do something like:
def edit
#user = User.find(session[:user_id])
end
Then have a route that looks something like:
map.edit_profile "edit_profile", :controller => "users", :action => "edit"
This route would give you a named route called edit_profile_path
Tomas Markauskas's answer could work, but here's the answer to your question from the Rails Guide:
get 'edit_profile', to: 'users#edit'
So, when someone goes to www.yoursite.com/edit_profile, it will route to www.yoursite.com/users/edit.
Then, in your controller you can access the user with
#user = User.find(session[:current_user_id])
Assuming you set that session variable when someone logs in. Also, don't forget to check if they're logged in. This will work if your using Resourceful Routing (the Rails default) or not.
Source: http://guides.rubyonrails.org/routing.html
make the route as
get '/users/:id/edit', to: 'users#edit', as: 'edit_profile'
As explained in this link section 'The hard way' :
http://augustl.com/blog/2009/styling_rails_urls/
The url will be
/users/edit_profile
Because the ID is no longer in the URL, we have to change the code a bit.
class User < ActiveRecord::Base
before_create :create_slug
def to_param
slug
end
def create_slug
self.slug = self.title.parameterize
end
end
When a user is created, the URL friendly version of the title is stored in the database, in the slug column.
For better understanding read the link below
http://blog.teamtreehouse.com/creating-vanity-urls-in-rails
write it in any home controler.
def set_roots
if current_user
redirect_to dashboard_home_index_path
else
redirect_to home_index_path
end
end
in routes.rb file
root :to => 'home#set_roots'
match "/find_roots" => "home#set_roots"

Resources