Rails 4 and Devise - routing issue with sign-up page - ruby-on-rails

I have a routing question related to Devise.
I have model - Member (rather than User) and I have some custom routes
devise_for :members, controllers: {registrations 'members/registrations',
omniauth_callbacks: 'members/omniauth_callbacks',
sessions: 'members/sessions' }
devise_scope :member do
authenticated :member do
root :to => 'home#index', as: :authenticated_root
end
unauthenticated :member do
root :to => 'devise/sessions#new', as: :unauthenticated_root
end
end
My issue relates to my sign-up page when it fails (due to a validation failing).
The displayed URL is
http://localhost:3000/members/sign_up
But if validation fails it redirects to
http://localhost:3000/members
(this page does show the registration/new form and this form works fine if the input validates)
In contrast, on my sign-in page
http://localhost:3000/members/sign_in
if failing validation redirects back to itself
http://localhost:3000/members/sign_in
Now you may wonder why I am asking this. I have an implementation of growly flash messages to indicate validation failure. They work on all the different devise views except the sign-up one. This routing difference is the only difference I can see between them. I am guessing that if the sign-up page redirected to itself on failure, like the sign-in page then the flash messages will work.
Its an odd question but some insights on this routing/URL behaviour in devise would be really helpful.
Registrations_controller.rb
class Members::RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
build_resource({})
self.resource.user = User.new
respond_with self.resource
end
# POST /resource
def create
super
resource.user.ip_address = request.remote_ip
unless resource.email.nil? || resource.email.empty?
resource.user.email = resource.email
resource.user.save
end
end
private
def sign_up_params
allow = [:provider, :uid, :email, :password, :password_confirmation, user_attributes: [:member_id, :email, :first_name, :last_name, :institution, :city, :country, :job_title, :about, :tag_list, :picture, :ip_address]]
params.require(resource_name).permit(allow)
end
end

Related

Single table inheritance and Devise single Login

I am working on a Rails project using single table inheritance and devise.
I have User, Client and Guide models. Client and Guide inherit from the User model. They have different paths for registration but I want them both to be able to log in on the same page.
Currently, neither can log in because I believe the resource is being set to User.
Does anyone knew how I can test a User login to see what type it is (either Client or Guide) and then set it to be that resource and direct it to the correct page?
This is my route:
Rails.application.routes.draw do
get 'pages/index'
devise_for :clients, controllers: {registrations: "clients/registrations", confirmations: "clients/confirmations", omniauth: "clients/omniauth", unlocks: "clients/unlocks"}, :skip => [:sessions, :passwords]
devise_for :guides, controllers: {registrations: "guides/registrations", confirmations: "guides/confirmations", omniauth: "guides/omniauth", unlocks: "guides/unlocks"}, skip: [:sessions, :passwords]
# devise_for :users, controllers: {passwords: "devise/passwords", sessions: "devise/sessions"}, skip: [:registrations, :confirmations, :omniauth, :unlocks]
devise_for :users, controllers: {passwords: "devise/passwords"}, skip: [:sessions, :registrations]
devise_for :admin_users, ActiveAdmin::Devise.config
ActiveAdmin.routes(self)
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
# You can have the root of your site routed with "root"
root 'pages#index'
Here is my User model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :type, inclusion: { in: %w(Client Guide)}
####################################################################################
# make sure that guide information is verfied as only being for a guide
validate :only_a_guide_has_bio
validate :only_a_guide_has_availability
validate :only_a_guide_has_years_in_business
validate :only_a_guide_has_certified
validate :only_a_guide_has_location
def only_a_guide_has_bio
if self.bio
self.errors.add(:bio, "Only a guide can have a home.") unless self.guide?
end
end
def only_a_guide_has_availability
if self.availability
self.errors.add(:availability, "Only a guide can have availability.") unless self.guide?
end
end
def only_a_guide_has_years_in_business
if self.years_in_business
self.errors.add(:years_in_business, "Only a guide can have years in business.") unless self.guide?
end
end
def only_a_guide_has_certified
if self.certified
self.errors.add(:certified, "Only a guide can be certified.") unless self.guide?
end
end
def only_a_guide_has_location
if self.location
self.errors.add(:location, "Only a guide can have location.") unless self.guide?
end
end
def first_name
self.first_name
end
####################################################################################
def guide?
self.type == 'Guide'
end
def client?
self.type == 'Client'
end
end
Here is my Guide model:
class Guide < User
has_many :trips
end
Here is my Client model:
class Client < User
has_and_belongs_to_many :trips
def new
super
end
end
Here is the User sessions controller:
class Users::SessionsController < Devise::SessionsController
include ApplicationHelper
def create
super
end
def new
super
end
def destroy
super
end
end
The super for devise create is:
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
end
(more here https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb)
And this is the new session view:
.page-header
%h1= t ".title", default: "Login"
= form_for(resource, url: new_user_session_path, html: {class: "form-horizontal form-user"}) do |f|
.form-group
= f.label :email, class: "control-label col-md-4"
.col-md-8
= f.email_field :email, class: "text-field form-control", html: {spellcheck: "false"}
.form-group
= f.label :password, class: "control-label col-md-4"
.col-md-8
= f.password_field :password, class: "text-field form-control", html: {autocomplete: "off"}
- if devise_mapping.rememberable?
.form-group
= f.label :remember_me, "Remember Me", class: "control-label col-md-4"
.col-md-8
= f.check_box :remember_me
.form-group
.col-md-offset-4.col-md-8
= f.submit "Login", class: "btn btn-primary"
.form-group
.col-md-offset-4.col-md-8
= render "users/shared/links"
Changing SessionsController#create to the following works for me:
def create
# We need to determine exactly which subclass the signed in user is and put
# it in the auth_options hash. Also, we cannot modify auth_options, so we
# make a copy of it, alter it, and pass it to warden.autenticate!. (You could
# also merge the new scope into auth_options.)
u = User.where(email: request.params[:user][:email]).take
user_type = u.type.downcase.to_sym
ao = auth_options
ao[:scope] = user_type
self.resource = warden.authenticate!(ao)
set_flash_message(:success, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
end
This assumes User is the superclass and that instances of both subclasses log in using their e-mail addresses. If they use different attributes, you can check for them by looking at request.params and appropriately modifying #create.
EDIT: I also needed to add controllers: { sessions: 'sessions' } to the relevant routes:
devise_for :users, controllers: { sessions: 'sessions' }
devise_for :admins, controllers: { sessions: 'sessions' }
# etc...
If this doesn't work, you may want to check out this thread for some ideas.

update_resource_params gives Unpermitted parameters error - Devise Invitable

I am trying to implement invitation on existing users in my app, using Devise Invitable.
At first glance this fails, because Devise Invitable is best used on new users - i.e. non-registered.
But this is what my User::InvitationsController looks like (truncated for brevity):
class Users::InvitationsController < Devise::InvitationsController
include ApplicationHelper
before_filter :configure_permitted_parameters, if: :devise_controller?
before_filter :update_sanitized_params, only: :update
# PUT /resource/invitation
def create
invited_user = User.where(email: params[:user][:email])
if !invited_user.empty?
invitation_token = Devise.token_generator.digest(resource_class, :invitation_token, update_resource_params[:invitation_token])
self.resource = resource_class.where(invitation_token: invitation_token).first
family_tree = self.resource.invited_by.family_tree
family_tree.memberships.create(:user_id => user.id, relation: update_resource_params[:relation])
resource.create_membership_both_ways(params[:user][:invitation_token], params[:user][:relation])
resource.skip_password = true
resource.update_attributes update_resource_params.except(:invitation_token)
redirect_to my_tree_path
else
super
end
end
protected
def update_sanitized_params
devise_parameter_sanitizer.for(:accept_invitation) do |u|
u.permit(:name, :password, :password_confirmation, :invitation_token, :invitation_relation,:avatar, :avatar_cache, :relation)
end
end
def update_resource_params
devise_parameter_sanitizer.sanitize(:accept_invitation) do |u|
u.permit(:email)
end
end
end
When I use pry for debugging, this is what happens when I poke around invitation_token:
[1] pry(#<Users::InvitationsController>)> invitation_token
=> false
[2] pry(#<Users::InvitationsController>)> update_resource_params
Unpermitted parameters: email
=> {"name"=>"", "invitation_relation"=>"uncle"}
Thoughts on what may be causing this, or how I can get rid of this unpermitted paramters :email problem?
Edit 1
These are the relevant routes:
devise_for :users, :controllers => { :invitations => 'users/invitations', :confirmations => 'confirmations' }
devise_scope :user do
post "users/invitation/sign_in" => "users/invitations#invite_sign_in"
end
Edit 2
In my application_controller.rb I have a method that I added :email and it seems to have stopped that error:
def configure_permitted_parameters
# Once I added :email to this method, it stopped throwing the unpermitted error
devise_parameter_sanitizer.for(:accept_invitation) do |u|
u.permit(:name, :email, :last_name, :invitation_relation)
end
end
When using Devise and configuring permitted_params it is best to do this in the application controller,
you can do it one of two ways
def configure_permitted_parameters
devise_parameter_sanitizer.for(:accept_invitation) do |u|
u.permit(:name, :email, :last_name, :invitation_relation)
end
end
OR
def configure_permitted_params
devise_parameter_sanitizer.for(:accept_invitation) << [:name, :email, :last_name, :invitation_relation]
end

Devise - Insert data into HAS_ONE related model during sign up

I have a User model, that has_one Profile.
Profile is the place where all the user stuff is saved (name, phone, address, state, etc).
During sign up I need to let user fill in those fields.
Tried to do nested fields but it doesn't really work and I don't really understand why.
Does anyone have similar code examples? Can't find anything in Internet.
Candidate has_one :profile
Profile belongs_to :user
Registration form:
= simple_form_for(:candidate,
as: Candidate,
url: candidate_registration_path) do |f|
= f.simple_fields_for :profile do |profile|
= profile.input :first_name
= profile.input :last_name
= f.input :email
= f.input :password
= f.input :password_confirmation
= f.submit 'Start Building', class: 'btn btn-primary'
Didn't do anything with controllers except this:
def configure_devise_params
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:email, :password, :password_confirmation,
profile_attributes: [:first_name, :last_name])
end
end
When you say it doesn't work do you mean that it doesn't save? Or it doesn't show the fields?
In the latter case, you would have to build the blank profile in the registrations controller before the action is hit. So basically override the devise controller and do something like this:
class RegistrationsController < Devise::RegistrationsController
def new
user = build_resource({})
user.build_profile if user.profile.blank?
respond_with self.resource
end
end
routes.rb
devise_for :candidates, :controllers => {:registrations => "registrations"}
Candidate.rb
has_one :profile
accepts_nested_attributes_for :profile
and make sure the code you have written above for strong parameters is in your application_controller.
This is also assuming your devise model is called "Candidate"
Check the params in your log. Try creating a user in console using those params. Do you have accepts_nested_attributes_for :profile on your user model?

How do I test with rspec a create action when my model belongs_to another model and uses devise for authentication

In my rails app I use devise for authenticating a user. I need to create rspec tests for a controller Arts which belongs_to User.
My Art model is the following:
class Art < ActiveRecord::Base
belongs_to :user
attr_accessible :description, :title, :image
has_attached_file :image, :styles => { :medium => "620x620>", :thumb => "200x200>" }
validates :title, :presence => true
validates :description, :presence => true
validates :image, :presence => true
end
In my ArtsController I have the following code:
class ArtsController < ApplicationController
before_filter :authenticate_user!
def create
#user = current_user
#art = #user.arts.create(params[:art])
end
end
I am trying to create a test to check if user is redirected to the sign in page when it tries to create an art and it is not logged in. So my test looks like this:
describe ArtsController do
describe "When user is not logged in" do
it "should be redirected to sign in page if creating new art" do
post :create
response.should redirect_to '/users/sign_in'
end
end
end
But I get the following error:
1) ArtsController When user is not logged in should be redirected to sign in page if creating new art
Failure/Error: post :create
ActionController::RoutingError:
No route matches {:controller=>"arts", :action=>"create"}
# ./spec/controllers/arts_controller_spec.rb:11:in `block (3 levels) in <top (required)>'
My routes.rb is like this:
Capuccino::Application.routes.draw do
devise_for :users
match "home" => "users#home", :as => :user_home
resources :users do
resources :arts
end
match "home/art/:id" => "arts#show", :as => :art
match "home/arts" => "arts#index", :as => :arts
end
How does my rspec test should be to perform this test?
You don't have any routes for arts#create that will take only the controller and action as parameters.
If you want to use your current nested route, you're gonna have to pass along the :user_id parameter in your request:
it "should be redirected to sign in page if creating new art" do
post :create, { :user_id => user_id }
response.should redirect_to '/users/sign_in'
end
But since you're trying to test the use case where you don't have a :user_id, you need a new non-nested route for that.

Rails 4/Devise/MongoDB: "Unpermitted parameters" using custom properties and strong parameters

Trying to add a nested custom attribute, Profile (a Mongoid document), to my devise User class. When the Devise registration form is submitted, it should create both a User and a corresponding Profile object as well.
I'd like the end-result to look something like this in my MongoDB:
User:
{
# Devise fields:
"email": "my#email.com",
...
# Custom field
"profile" : "<object_id>"
}
Profile:
{
"first_name": "Dave",
....
}
Unfortunately, I am receiving this in my console whenever I submit my registration. It successfully creates a User but fails to create an associated Profile.
Started POST "/" for 127.0.0.1 at 2013-04-20 23:37:10 -0400
Processing by Users::RegistrationsController#create as HTML
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"awN2GU8EYEfisU0",
"user"=>
{"profile_attributes"=>
{"first_name"=>"Dave",
"birthday(2i)"=>"4",
"birthday(3i)"=>"21",
"birthday(1i)"=>"1933",
"occupation_title"=>"Software Developer"},
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]",
"email"=>"my#email.com"}}
Unpermitted parameters: profile_attributes
I have setup:
Rails 4.0.0beta1, Ruby 2.0.0-p0
Devise ('rails4' branch), Mongoid (from git)
A custom Devise registrations controller to add a definition for strong parameters.
models/user.rb:
class User
include Mongoid::Document
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:token_authenticatable, :confirmable, :lockable, :timeoutable
field :email, type: String, default: ''
...
has_one :profile
accepts_nested_attributes_for :profile
end
models/profile.rb:
class Profile
include Mongoid::Document
include Mongoid::Timestamps
# Attributes
# ----------
field :slug, type: String, default: '' # Acts as user-'friendlier' slug
field :birthday, type: DateTime, default: DateTime.now
field :first_name, type: String, default: ''
field :occupation_title, type: String, default: ''
belongs_to :user
embeds_many :photos
has_one :occupation_industry, :as => :industry
end
controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
def resource_params
params.require(:user).permit(:email, :password, :password_confirmation, :profile_attributes)
end
private :resource_params
end
routes.rb
devise_for :users,
:path => '',
:path_names => {
:sign_in => 'login',
:sign_out => 'logout',
:sign_up => 'register'
},
:controllers => {
:registrations => "users/registrations",
:passwords => "users/passwords"
}
I have already looked at these related posts, they didn't seem to help:
Rails 4 Nested Attributes Unpermitted Parameters
https://gist.github.com/kazpsp/3350730
EDIT:
Looks like Devise does actually support strong parameters in its 'rails4' branch (which is supposed to be merged into master in a few days.) Looking through the code, it appears you can override a params function for each action on devise controllers. For creating new users, its sign_up_params instead of resource_params in my example.
Despite changing this name to the proper one, it still didn't work... only whitelisting all parameters using this bang seemed to work:
def sign_up_params
params.require(:user).permit!
end
Obviously, this kind of defeats the purpose of strong parameters... so now the question is how do I permit my nested attributes profile_attributes (as seen in my original question)?
I had the exact same issue and overriding sign_up_params did work for me
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation, :other, :etc)
end
of course, the difference is in that mine are just scalar values, while you're trying to mass assign a relation... I guess that's where you should look for.
By the way, the documentations is still inexistint in this topic (too new), and code commnents suggest to override devise_parameter_sanitizer, which isn't necessary.
I found a different method that allows all the devise overriding logic and code to reside in the application controller. This allows any and all custom params to be passed through for each devise action (sign in, sign up, update). I also add a parameter sanitizer for devise_invitable and handle that logic here (invite, accept_invitation). I've got custom params like avatar, avatar_cache, etc:
#application_controller.rb
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
# There are just three actions in Devise that allows any set of parameters to be passed down to the model,
# therefore requiring sanitization. Their names and the permited parameters by default are:
# sign_in (Devise::SessionsController#new) - Permits only the authentication keys (like email)
# sign_up (Devise::RegistrationsController#create) - Permits authentication keys plus password and password_confirmation
# account_update (Devise::RegistrationsController#update) - Permits authentication keys plus password, password_confirmation
# and current_password. More at https://github.com/plataformatec/devise#strong-parameters
def configure_permitted_parameters
devise_parameter_sanitizer.for(:accept_invitation) do |u|
u.permit(:username,:validate_username, :password,:password_confirmation, :invitation_token)
end
devise_parameter_sanitizer.for(:invite) do |u|
u.permit(:name,:comments)
end
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:username,:password,:password_confirmation)
end
devise_parameter_sanitizer.for(:sign_in) do |u|
u.permit(:username,:email,:password,:password_confirmation,:phone, :validate_username, :avatar_cache, :remove_avatar, :current_password,:remember_me)
end
devise_parameter_sanitizer.for(:account_update) do |u|
u.permit(:username,:email,:password,:password_confirmation,:phone, :validate_username,:avatar, :avatar_cache, :remove_avatar, :current_password)
end
end
Find and read more at https://github.com/plataformatec/devise#strong-parameters
I had the same issue when login, it says: Unpermitted parameters: password, remember_me.
and because i have any controller that inheriting Devise::SessionsController, so i use my own parameter sanitizer.
here is what i do:
Create a file in '#{Rails.root}/lib' fold, my is hzsapa_parameter_sanitizer.rb and required in config/application.rb, then override devise_parameter_sanitizer method in application_controller.rb
lib/hzsapa_parameter_sanitizer.rb
class HzsapaParameterSanitizer < Devise::ParameterSanitizer
def sign_in
default_params.permit(auth_keys + [:password, :remember_me])
end
end
You can override those method depends on your issue:
def sign_in
default_params.permit(auth_keys)
end
def sign_up
default_params.permit(auth_keys + [:password, :password_confirmation])
end
def account_update
default_params.permit(auth_keys + [:password, :password_confirmation, :current_password])
end
config/application.rb
require "hzsapa_parameter_sanitizer"
app/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def devise_parameter_sanitizer
#devise_parameter_sanitizer ||= if defined?(ActionController::StrongParameters)
HzsapaParameterSanitizer.new(resource_class, resource_name, params)
else
Devise::BaseSanitizer.new(resource_class, resource_name, params)
end
end
end
Edit: i just found the solution in devise README, you can follow it here
I used your code and it worked for me!
Here is what I did
class RegistrationsController < Devise::RegistrationsController
skip_before_filter :verify_authenticity_token, :only => :create #, :if => Proc.new { |c| c.request.format == 'application/json' }
respond_to :json, :html, :xml
def create
user = User.new(devise_registrations_permitted_parameters)
if user.save
render :json=> user.as_json(:auth_token=>user.authentication_token, :email=>user.email,:name => user.name), :status=>201
return
else
warden.custom_failure!
render :json=> user.errors, :status=>422
end
end
protected
def devise_registrations_permitted_parameters
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end

Resources