Skip to content

How to manage users with a standard Rails controller

Shane Becker edited this page Jan 20, 2018 · 4 revisions

Using a somewhat crude hack that works!

Basically you might want a standard user controller so the admin can edit and add users. Doing that in a straightforward way doesn't work. Devise creates routes inside the gem that are not explicit in routes.rb that interfere. You can see them with rake routes though.

There is another article with this title. Unfortunately it doesn't work. I tried it and still got password missing validation errors, even using the exact code in that other article. I also got other funky errors coming from the hidden Devise model validations. Various examples and instructions found through Google also didn'work. Some are very dense and it's difficult to see what the author's intention is. Some go back to 2012.

What follows is an end-run around Rails and around Devise. So it's not very Railsy. And not Devisey at all.

I would love to see a Devise config option because it's not a rare requirement.

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 these routes

post 'savenew', to: 'users#savenew'

Add this action to the user controller. Note that connect.quote prevents sql injection; sanitize itself just calls quote.

def savenew
  sql = "insert into users (name,email, created_at,updated_at) values( 
        #{ActiveRecord::Base.connection.quote(user_params[:name])}, 
        #{ActiveRecord::Base.connection.quote(user_params[:email])},now(), now())"
  ActiveRecord::Base.connection.execute(sql)
  redirect_to action: 'index'
end

View: change the form_for so that submit will go to the new route and action, not the default Rails one. new.html.erb

<%= form_for User, :url => {:action => "savenew"} do |f| %>

Using nested routes

Using nested resources one can handle routing without stumbling on the routes generated by devise.

Generate a standard scaffolding under admin/users, then add a nested resource to routes.rb

  devise_for :users

  namespace :admin do
    resources :users
  end

Add skip_notifications to your User if you want to prevent admin actions sending out emails.

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :confirmable, :lockable, :timeoutable, :omniauthable
  @skip = false

  def skip_notifications!()
    skip_confirmation_notification!
    @skip = true
  end

  def email_changed?
    return false if @skip
    super
  end

  def encrypted_password_changed?
    return false if @skip
    super
  end
end

In your new controller, you can remove the need for providing a password when an admin is updating a record with

  before_action :allow_without_password, only: [:update]

  # ...

  private
    def allow_without_password
      if params[:user][:password].blank? && params[:user][:password_confirmation].blank?
          params[:user].delete(:password)
          params[:user].delete(:password_confirmation)
      end
    end

This won't work for creating users though as the validation is on the user model. I would advise against creating users with no password at all, instead consider generating a password with Devise.friendly_token.first(16) or similar methods and just leave it in.

Now if you want to skip notifications make sure to call it in the controllers that should not trigger change notifications:

  def create
    @user = User.new(user_params)
    # don't send email notifications from admin interface
    @user.skip_notifications!
    # ...
  end
  def update
    # don't send email notifications from admin interface
    @user.skip_notifications!
    # ...
  end
Clone this wiki locally