Rails 4 - Using namespaced STI models with devise - ruby-on-rails

My rails app has 5 user models that extend User. All of which have the namespace User::UserTypeHere. When I attempt to logout of my application, devise tries to access the models as if they belong to the top level namespace, and I get the following error:
"The single-table inheritance mechanism failed to locate the subclass: 'SuperUser'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance..."
How do I set up my routes in a way that devise will recognize my namespaced models?
routes.rb snippet
...
devise_for :users, skip: [:sessions, :passwords, :confirmations, :registrations, :unlocks]
devise_scope :user do
# authentication
unauthenticated :user do
root to: 'users/devise/sessions#new', as: 'new_user_session'
end
authenticated :user do
root to: 'application#index'
end
get '/login', to: 'users/devise/sessions#new', as: 'user_login_view'
post '/login', to: 'users/devise/sessions#create', as: 'user_session'
get '/logout', to: 'users/devise/sessions#destroy', as: 'destroy_user_session'
# registrations
get '/join', to: 'users/registrations#new', as: 'new_user_registration'
post '/join', to: 'users/registrations#create', as: 'user_registration'
# user accounts
scope '/account' do
# confirmation
get '/verification', to: 'users/confirmations#verification_sent', as: 'user_verification_sent'
get '/confirm', to: 'users/confirmations#show', as: 'user_confirmation'
get '/confirm/resend', to: 'users/confirmations#new', as: 'new_user_confirmation'
post '/confirm', to: 'users/confirmations#create'
# passwords
get '/reset-password', to: 'users/passwords#new', as: 'new_user_password'
get '/reset-password/change', to: 'users/passwords#edit', as: 'edit_user_password'
put '/reset-password', to: 'users/passwords#update', as: 'user_password'
post '/reset-password', to: 'users/passwords#create'
# unlocks
post '/unlock', to: 'users/unlocks#create', as: 'user_unlock'
get '/unlock/new', to: 'users/unlocks#new', as: 'new_user_unlock'
get '/unlock', to: 'users/unlocks#show'
# settings & cancellation
# get '/cancel', to: 'users/registrations#cancel', as: 'cancel_user_registration'
# get '/settings', to: 'users/registrations#edit', as: 'edit_user_registration'
# put '/settings', to: 'users/registrations#update'
# account deletion
# delete '', to: 'users/registrations#destroy'
end
end
...
user.rb (model)
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
users/super_user.rb (model)
class Users::SuperUser < User
...
end
users/devise/sessions_controller.rb
class Users::Devise::SessionsController < Devise::SessionsController
...
end

The value of the type column for your users needs to be Users::SuperUser, instead of just SuperUser, so that Rails autoload can find the correct class path.
Also, remember to restart your rails console & server when changing classes around, and don't keep old user instances with wrong values for type, as that will force rails to trigger an error when finding them

Related

Custom Routing in Rails 5

I'm having some issues with custom routing. What I'm looking to do is remove the model from the route and dynamically use the record name.
so instead of:
site.com/events/my-event
I would like it to be:
site.com/my-event
I hacked this to work with the below code, only issue is I can't access my admin namespace as it's being treated as an event record (and any other route):
get('/:id', to: redirect do |params, request|
id = request.path.gsub("/", "")
"/events/#{id}"
end)
I know this redirect is not right, I'm just not well versed in routing options. How should this be done properly?
routes.rb
Rails.application.routes.draw do
resources :events, param: :id, path: "" do
get 'login', to: 'sessions#new', as: :login
get 'logout', to: 'sessions#destroy', as: :logout
post 'sessions', to: 'sessions#create', as: :session_create
end
namespace 'admin' do
root "events#index"
resources :sessions, only: [:create]
get 'login', to: 'sessions#new', as: :login
get 'logout', to: 'sessions#destroy', as: :logout
resources :events
end
end
Rails lets you specify what a routing URL should look like:
https://guides.rubyonrails.org/routing.html#translated-paths
You don't need redirect.
You need to match all requests to one controller/action.
get '/(*all)', to: 'home#app'
Inside this action check what is coming in params and render appropriate view.
PS: it will capture all requests, even for not found images, js, etc.
(1) To fix /admin to go to the right path, move the namespace line above the events resources so that it is matched first:
Rails routes are matched in the order they are specified 2.2 CRUD, Verbs, and Actions
# routes.rb
namespace :admin do
root "events#index"
#...
resources :events
end
resources :events, param: :name, path: ""
Use :param and :path to match for site.com/myevent instead of site.com/events/:id
Your events controller:
# events_controller.rb
class EventsController < ApplicationController
# ...
private
def set_event
#event = Event.find_by(name: params[:name])
end
end
Your admin events controller:
# admin/events_controller.rb
class Admin::EventsController < ApplicationController
# ...
private
def set_event
#event = Event.find params[:id]
end
end
TIP: To get a complete list of the available routes use rails routes in your terminal 5.1 Listing Existing Routes

Updating a database field when Creating a User using Devise gem

Explanation
I am wanting to set a user's security level when they register for an account. I don't want this to be a hidden field in the signup form as a person can then send in their own value to try and gain administrative privileges. So, it needs to be in the RegistrationController.
Versions
Ruby: 2.2.1p85 (2015-02-26 revision 49769) [x86_64-darwin14]
Rails: 4.2.0
Files
Here is what I have so far.
Added the following line to the app/config/routes.rb file:
devise_for :users, :skip => [:sessions, :registrations],
:controllers => { registrations: 'users/registrations' }
devise_scope :user do
get "login", to: "devise/sessions#new", as: :new_user_session
post "login", to: "devise/sessions#create", as: :user_session
delete "logout", to: "devise/sessions#destroy", as: :destroy_user_session
get "register", to: "devise/registrations#new", as: :new_user_registration
post "register", to: "devise/registrations#create", as: :user_registration
get "account/delete", to: "devise/registrations#cancel", as: :cancel_user_registration
get "user/profile/edit", to: "devise/registrations#edit", as: :edit_user_registration
patch "user", to: "devise/registrations#update"
put "user", to: "devise/registrations#update"
put "register", to: "devise/registrations#update"
delete "user/delete", to: "devise/registrations#destrony"
get "user/profile", to: 'devise/registrations#edit', as: :user_root
end
Here is my RegistrationController in the app/controllers/users/registration_controller.rb file:
class Devise::RegistrationsController < DeviseController
before_filter :configure_permitted_parameters, :only => [:create]
def create
super
resource.update_attribute(:security_level, '1')
end
end
My database does have a field called 'security_level' for all users.
Thank you in advance for any help that you can provide.
If you want to set security_level = 1, by default i would suggest you write it to the rails migration.
Code for migration
change_column_default :users, :security_level, '1'
If you want to do more by overriding the default devise crete function use the bellow code.
class RegistrationsController < Devise::RegistrationsController
def create
super do
resource.security_level = '1'
resource.save
end
end
end
You can face logic duplication problem in future (e.g. creating user from console will not force Rails to fill security level attribute, so you will need to control this in every place). Better to use callbacks in User model:
class User < ActiveRecord::Base
before_save :set_security_level
def set_security_level
self.security_level = 1
end
end
So, everytime User is going to be saved, :set_security_level will be executed.
Callbacks manual http://guides.rubyonrails.org/active_record_callbacks.html

Rails Devise Routes: If user signed in, point to index. Else, point to new user session

Pretty simple question, but I can't seem to find the answer with a good ole fashioned Google.
The error:
undefined method `user_signed_in?' for #<ActionDispatch::Routing::Mapper:0x007fc6369320e8> (NoMethodError)
Server won't even start.
My code:
Rails.application.routes.draw do
devise_for :users, :path_prefix => 'u'
resources :users
devise_scope :user do
get "login", to: "devise/sessions#new", as: :login
get 'logout', to: 'devise/sessions#destroy', as: :logout
get 'user/edit', to: 'devise/registrations#edit', as: :change_password
end
resources :apps do
resources :elements, controller: 'apps/elements'
end
resources :elements do
resources :features, except: [:index], controller: 'apps/elements/features'
end
if user_signed_in?
root to: 'apps#index'
else
devise_scope :user do
root to: 'devise/sessions#new'
end
end
end
What's the best way to go about making this work? I'm hesitant to try to work around it and make the site vulnerable, being a new RoR user. Thanks in advance.
In Rails access control is done on the controller layer - not on the routing layer.
Routes just define matchers for different sets of params and request urls. They are processed before rails even starts processing the request, they don't know anything about the session. Rails could have even used YML to define routes, except that Ruby is better at DSLs.
If you want to require that the user is signed in you would use:
class SomeController < ApplicationController
before_action :authenticate_user! # a Devise helper method
end

Rails Devise and devise_token_auth. Not working alongside

I have a Rails 4.1 Application running with Devise for authentication.
For access via mobile apps i would like to implement token auth with the recommended devise_token_auth gem. I do not use Omniauth
The functionality of the existing app should not be altered.
What i did:
Installed devise_token_auth via gemfile.
Used the generator: rails g devise_token_auth:install User auth
Changed the migration to add the required fields.
Migration failed due missing of Omniauth. So i also installed it.
Changed routes.rb
devise_for :users, :skip => [:sessions, :registrations, :omniauth_callbacks]
as :user do
get 'register' => 'users/registrations#new', :as => :new_user_registration
post 'register' => 'users/registrations#create', :as => :user_registration
get 'sign_in' => 'devise/sessions#new', :as => :new_user_session
post 'sign_in' => 'devise/sessions#create', :as => :user_session
delete '/' => 'users/sessions#destroy', :as => :destroy_user_session
end
added:
namespace :api do
scope :v1 do
mount_devise_token_auth_for 'User', at: 'auth', skip: [:omniauth_callbacks]
end
end
In User Model i have:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :timeoutable, :lockable
include DeviseTokenAuth::Concerns::User
Now when i try to sign_up a new user it gives me the validation error:
Uid can't be blank
Does someone had the same problem and resolved it?
What i find strange is that it needs to have Omniauth installed.
Update:
I overwrite the Devise registration controller create action:
build_resource(sign_up_params)
resource.uid = resource.email
resource.provider = ''
Now when i sign_in i get:
{"errors":["Authorized users only."]}
in Browser.
Adding the following to app/models/user.rb:
before_validation do
self.uid = email if uid.blank?
end
did it for me. Also make sure the provider is set to "email" for "provider".
Well I'm currently struggling with the same thing. Trying to add devise_token_auth to Devise, and it is not working so far for me.
As far as this goes, are you talking about "sign_up" for Devise, or devise_token_auth? If it is for Devise, I supposed setting uid=email before creating the record would solve this.
This error is raised by devise_token_auth, not by devise. So essentially, devise_token_auth is trying to authenticate your normal devise routes the same way it would normally authenticate an api request. Your normal devise routes are authenticating via session, not via token, so you'll get this error:
{"errors":["Authorized users only."]}
There are a couple of things that could be happening here. First, make sure that you're only looking for token validation on the actions of your API controllers. So make sure that this line is included in your BaseAPIController, and not in your ApplicationController.
include DeviseTokenAuth::Concerns::SetUserByToken
The other possibility is that you have some namespacing issues in your routes.rb. Make sure that you have something like this. You need to have devise_for first, and the token_auth properly namespaced or it will cause validations issues on your other routes.
Rails.application.routes.draw do
devise_for :admins
namespace :api do
scope :v1 do
mount_devise_token_auth_for 'user', at: 'auth'
end
end
end
Good luck!

Ruby on Rails Devise after_sign_in_path_for called on unrelated controller

struggling for a few hours on this one now. I have integrated the Devise gem into my Rails project after originally making my own auth system but I am facing an issue I can't understand.
When the user signs in the method:
def after_sign_in_path_for(resource_or_scope)
user = resource_or_scope
user_path(user.username)
end
Is triggered to redirect the user to their profile.
I have an edit user route which takes the user to a page in which they can edit their details and add a 'wanted item'. Two separate forms with two separate controllers and actions.
The 'add wanted item' method posts to a different controller that rendered the view called WantsController and adds a wanted item for the user through an association.
For some reason the after_sign_in_path_for method is called when submitting this form? It has nothing to do with signing in...
Here are my routes:
#users/auth
devise_for :users, :skip => [:sessions, :registrations]
devise_scope :user do
# registration
get "/signup", to: "users#new", as: :sign_up
post "/signup", to: "users#create", as: :sign_up_create
# account
get "/:username/account", to: "users#edit", as: :user_account
put "/users/:id", to: "users#update", as: :user_update
# shows
get "/:username", to: "users#show", as: :user
get "/:username/interests", to: "users#interests", as: :user_interests
get "/:username/offers", to: "users#offers", as: :user_offers
get "/:username/trades", to: "users#trades", as: :user_trades
# auth
post "/signin" => 'devise/sessions#create', as: :sign_in
delete "/signout", to: "devise/sessions#destroy", as: :sign_out
#wants
resources :wants, only: [:create, :destroy]
end
If I place the wants resource outside of the devise scope (which is where I expect it should go) I receive the following:
Could not find devise mapping for path "/wants"
What's happening here? Stumped!
Thanks.
Argh, silly mistake. Why is it after hours of struggling that when you post a question on Stack Overflow you figure it out after like 5 minutes?!
I had copied and pasted my RegistrationsController into the WantsController file to save typing the controller code but forgot to make it inherit from ApplicationController rather than Devise::RegistrationsController.
Lesson: Don't copy and paste!

Resources