I'm working through the book APIs on Rails and am super stuck in chapter 5 trying to test the sessions controller. I'm getting the following error and can't seem to track it down. Is there a good method for hunting down these kinds of error? And what am I missing?
1) Api::V1::SessionsController POST #create when the credentials are correct returns the user record corresponding to the given credentials
Failure/Error: post :create, { session: credentials }
NoMethodError:
undefined method `user' for nil:NilClass
App is in Rails 4.0.2, Ruby 2.2.1
Here is my test:
require 'spec_helper'
describe Api::V1::SessionsController do
describe "POST #create" do
before(:each) do
#user = FactoryGirl.create :user
end
context "when the credentials are correct" do
puts #user
before(:each) do
credentials = { email: #user.email, password: "12345678" }
post :create, { session: credentials }
end
it "returns the user record corresponding to the given credentials" do
#user.reload
expect(json_response[:auth_token]).to eql #user.auth_token
end
it { should respond_with 200 }
end
end
end
Here is the Sessions Controller:
class Api::V1::SessionsController < ApplicationController
respond_to :json
def create
user_password = params[:session][:password]
user_email = params[:session][:email]
user = user_email.present? && User.find_by(email: user_email)
if user.valid_password? user_password
sign_in user, store: false
user.generate_authentication_token!
user.save
render json: user, status: 200, location: [:api, user]
else
render json: { errors: "Invalid email or password" }, status: 422
end
end
end
The User Controller:
class Api::V1::UsersController < ApplicationController
respond_to :json
def show
respond_with User.find(params[:id])
end
def create
user = User.new(user_params)
if user.save
render json: user, status: 201, location: [:api, user]
else
render json: { errors: user.errors }, status: 422
end
end
def update
user = User.find(params[:id])
if user.update(user_params)
render json: user, status: 200, location: [:api, user]
else
render json: { errors: user.errors }, status: 422
end
end
def destroy
user = User.find(params[:id])
user.destroy
head 204
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
The routes.rb:
require 'api_constraints'
MarketPlaceApi::Application.routes.draw do
mount SabisuRails::Engine => "/sabisu_rails"
devise_for :users
# Api definition
namespace :api, defaults: { format: :json }, constraints: { subdomain: 'api' }, path: '/' do
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
resources :users, :only => [:show, :create, :update, :destroy]
resources :sessions, :only => [:create, :destroy]
end
end
end
And the user model:
class User < ActiveRecord::Base
validates :auth_token, uniqueness: true
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
before_create :generate_authentication_token!
def generate_authentication_token!
begin
self.auth_token = Devise.friendly_token
end while self.class.exists?(auth_token: auth_token)
end
end
Have you added devise_helper to spec/rails_helper.rb ?
RSpec.configure do |config|
...
config.include Devise::TestHelpers, type: :controller
...
end
Change
if user.valid_password? user_password
to:
if user and valid_password? user_password
or:
if user && valid_password?(user_password)
in your sessions_controller.rb file.
Do you have a User factory?
FactoryGirl.define do
factory :user do
username user
email user#user.com
password '12345678'
password_confirmation { '12345678' }
end
end
You need to have it defined so that FactoryGirl can create a user
Related
I have a new app that I am trying to setup with devise and devise-jwt. For some reason my authenticate_user! call is causing an error because sessions have been disabled:
{"status":500,"error":"Internal Server Error","exception":"ActionDispatch::Request::Session::DisabledSessionError: Your application has sessions disabled. To write to the session you must first configure a session store"
Routes:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :clients, only: [:show]
devise_for :users,
controllers: {
sessions: 'users/sessions',
registrations: 'users/registrations'
}
end
end
end
User
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable,
:jwt_authenticatable,
jwt_revocation_strategy: JwtDenylist
end
Controllers:
class API::V1::Users::SessionsController < Devise::SessionsController
respond_to :json
private
def respond_with(resource, _opts = {})
render json: { message: 'Logged.' }, status: :ok
end
def respond_to_on_destroy
current_user ? log_out_success : log_out_failure
end
def log_out_success
render json: { message: "Logged out." }, status: :ok
end
def log_out_failure
render json: { message: "Logged out failure."}, status: :unauthorized
end
end
class Api::V1::ClientsController < ApplicationController
before_action :authenticate_api_v1_user!
def show
end
end
What else do I need to do? When I make a request to http://localhost:3000/api/v1/clients/ID I'd expect the response to be:
=> <html><body>You are being redirected.</body></html>%
rather than this 500 error.
I was able to find a solution on GitHub:
config.session_store :cookie_store, key: '_interslice_session'
config.middleware.use ActionDispatch::Cookies
config.middleware.use config.session_store, config.session_options
from this post
I have inherited an application built with Rails (v. 4.2.0) + AngularJS. Most of it works reasonably well, but I have hit a wall with Devise (v .3.5.1) that I am unable to overcome.
The application is structured this way: Angular.js handles the client side and is served from the back by the Rails app through an API. (The structure is very similar to the one from this tutorial: https://thinkster.io/angular-rails).
The problem if that for some reason, the app does not recognize helper methods of devise such as: current_user or user_signin? among others. Currently, anyone could make a request to /api/users and get a JSON with the information.
For instance, if we add this code to the /controllers/api/employees_controller.rb and you make a request in your browser to /api/employees/:id you get the JSON despite nos being logged
def show
#employee = Employee.find(params[:id])
respond_to do |format|
format.json{
if user_signed_in? then
render text: #employee.to_json, status: 200
else
render :json => nil, status: 422
end
}
end
end
Note: in this application the entity "Employee" is a type of "User"
I have tried solutions named in this post Rails devise: user_signed_in? not working but it didn't work for me
Here are all the parts of the code that I think could offer some light to this issue
/controllers/api/users/sessions_controller.rb
module Api
class Users::SessionsController < Devise::SessionsController
before_filter :configure_sign_in_params, only: [:create]
#Devise couldn't find "users_url" so just defining it here (500 error)
def users_url
:root_path
end
# GET /resource/sign_in
def new
super
end
# POST /resource/sign_in
def create
user = User.new
if ( params[:user][:email] == "" || params[:user][:password] == "") then
render json: {errors: "Insufficient data provided (user and/or password)"}.to_json, status: :bad_request
elsif User.find_by_email(params[:user][:email]).present?
user = User.find_by_email(params[:user][:email])
if user.valid_password?(params[:user][:password]) then
sign_in :user, user
render json: user.to_json(only: [:id, :email, :name, :last_name, :type, :company_id,:configuration,:phone]), status: :created
else
render json: {errors: "Wrong password."}.to_json, status: :unauthorized
end
else
render json: {errors: "Could not find user with that email."}.to_json, status: :unauthorized
end
end
# DELETE /resource/sign_out
def destroy
puts "HEEEEY"
end
# protected
# If you have extra params to permit, append them to the sanitizer.
def configure_sign_in_params
devise_parameter_sanitizer.for(:sign_in) << :attribute
end
end
end
models/user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable#, :confirmable
scope :admins, -> { where(admin: true) }
validates :email, presence: true
validates :name, presence: true, length: { in: 1..50 }
end
config/routes.rb
Rails.application.routes.draw do
namespace :api, defaults: {format: :json} do
resources :contacts
resources :job_application
# devise_for :admins, controllers: {sessions: 'admins/sessions'}
# devise_for :employees, controllers: {sessions: 'employees/sessions'}
devise_for :users, controllers: {
sessions: 'api/users/sessions',
registrations: 'api/users/registrations',
}
devise_for :employees, controllers: {
sessions: 'api/employees/sessions',
registrations: 'api/employees/registrations',
}
If you need any other part of the code, please just let me know.
Thank you very much!
I am using devise and devise_ldap for my rails authentication. I am trying to use the built in helper, current user to display the users email on the welcome page of my application.
This is the code that I have tried to use to:
<% if user_signed_in? %>
<div>Signed in as... <%= current_user.email %></div>
<% end %>
when I sign in to the application, I get the error;
undefined method `email' for nil:NilClass
Here is my routes.rb
Rails.application.routes.draw do
devise_for :users
resources :users
resources :systems do
member do
get :targets, :sources
end
root 'systems#index'
end
and my users controller:
class UsersController < ApplicationController
authorize_resource
before_action :set_user, only: [:show, :edit, :update, :destroy]
# GET /users
def index
#users = User.all.order("display_name asc")
end
# GET /users/1
def show
end
# GET /users/new
def new
#user = User.new
end
# GET /stories/1/edit
def edit
respond_to do |format|
format.html
format.js
end
end
# POST /stories
def create
#user = User.new(user_params)
respond_to do |format|
puts 'user controller'
if #user.save!
format.html { redirect_to user_path(#user), notice: 'User was successfully created.' }
else
format.html { render :new }
end
end
end
# PATCH/PUT /stories/1
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to user_path(#user), notice: 'User was successfully updated.' }
else
format.html { render :edit }
end
end
end
# DELETE /stories/1
def destroy
#user.destroy
respond_to do |format|
format.html { redirect_to users_path notice: 'User was successfully destroyed.' }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:display_name, :email, :username)
end
end
my users model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
before_create :rememberable_value
before_save :get_ldap_values
devise :ldap_authenticatable, :rememberable, :trackable, :validatable
def get_ldap_values
if self.username
self.email = Devise::LDAP::Adapter.get_ldap_param(self.username,"mail").first if Devise::LDAP::Adapter.get_ldap_param(self.username,"mail")
self.display_name = Devise::LDAP::Adapter.get_ldap_param(self.username,"displayName").first if Devise::LDAP::Adapter.get_ldap_param(self.username,"displayName")
end
end
# def role?(role)
# return !!self.roles.find_by_name(role.to_s.camelize)
# end
def email_required?
false
end
def email_changed?
false
end
def rememberable_value
self.remember_token ||= Devise.friendly_token
end
def name_to_display
if self.display_name
self.display_name
else
self.username
end
end
def password_required?
false
end
def password_match?
self.errors[:password] << "can't be blank" if password.blank?
self.errors[:password_confirmation] << "can't be blank" if password_confirmation.blank?
self.errors[:password_confirmation] << "does not match password" if password != password_confirmation
password == password_confirmation && !password.blank?
end
end
I am not sure what I am missing to be able to access the current users information after a successful sign in.
Update
Here is the new routes file:
Rails.application.routes.draw do
devise_scope :user do
get '/users/sign_out' => 'devise/sessions#destroy'
get "/users/sign_in" => "devise/sessions#new"
# delete "/logout" => "devise/sessions#destroy"
end
devise_for :users
authenticate(:user) do
resources :users
resources :reports
resources :change_logs, only: [:index, :show]
resources :systems do
member do
get :targets, :sources
end
resources :change_logs, module: :systems
resources :components do
resources :change_logs, module: :components
end
resources :c_relations
end
resources :integrations
get '/static_pages/home' # => 'static_pages#home', as: 'home'
root 'systems#index'
end
In routes.rb you should enclose the rows following
devise_for :users
in a block
authenticate(:user) do
resources :users
[...]
end
The problem was that I had overridden the devise mapping and current_user. I removed them and was able to access current user in my views.
Do you have before_action :authenticate_user! in your controller chain at all?
Current_user could be nil after signin is if you aren't asking devise to authenticate the action.
I have updated the code spec code.
What I seek is to destroy a record only with the same user that has created it.
I've tried in the view section and it seems to be working, but the Rspec is throwing me some errors.
Can anyone please tell me how to do a correct spec?
Thanks
My Record model:
class Record < ActiveRecord::Base
#Associations
belongs_to :user
# Validations
validates :user, presence: true
end
My Record factory:
FactoryGirl.define do
factory :record do
user
end
end
My Record controller:
class RecordsController < ApplicationController
before_action :find_record, only: [:show, :edit, :update, :destroy]
before_action :require_permission, only: [:destroy]
def destroy
#record.destroy
flash[:notice] = "The record was deleted successfully"
redirect_to #record
end
private
def require_permission
if current_user != Record.find(params[:id]).user
flash[:notice] = "Permission required"
redirect_to root_path
end
end
end
My record spec:
require 'rails_helper'
describe RecordsController do
let(:record) { create(:record) }
let(:user) { create(:user) }
describe "#destroy" do
let!(:record) { create(:record) }
#UPDATED
login_user
it "deletes the record" do
expect {
delete :destroy, id: record.id, :record => {:user => record.user}
}.to change(Record, :count).by(-1)
expect(flash[:notice]).to eq("The record was deleted successfully")
end
end
end
UPDATE 2
rails_helper.rb
require 'spec_helper'
require 'devise'
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
config.extend ControllerMacros, type: :controller
end
at spec/support/controller_macros.rb
module ControllerMacros
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in FactoryGirl.create(:admin) # Using factory girl as an example
end
end
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
#user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the "confirmable" module
sign_in user
end
end
end
My errors:
#destroy
deletes the record (FAILED - 1)
Failures:
1) RecordsController#destroy deletes the record
Failure/Error: expect {
expected #count to have changed by -1, but was changed by 0
you use #record but you let record that's why in error #record is nil
it "deletes the record" do
expect {
delete :destroy, id: record.id, :record => {:user => record.user}
}.to change(Record, :count).by(-1)
expect(flash[:notice]).to eq("The record was deleted successfully")
end
I have set up users with devise and each user can select a role. What I am trying to do is allow admins to be able to edit any user on the site if they have role admin. I currently have a UsersController setup like this:
class UsersController < ApplicationController
before_filter :authenticate_user!, only: [:index, :new, :edit, :update, :destroy]
skip_before_filter
def index
#users = User.order('created_at DESC').all
end
def show
#user = User.friendly.find(params[:id])
#users_authors = User.all_authors
end
# get authors index in here
def authors
end
def create
#user = User.create(user_params)
end
def edit
#user = User.find(params[:id])
end
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
def destroy
#user = User.find(params[:id])
#user.destroy
if #user.destroy
redirect_to users_url, notice: "User deleted."
end
end
private
def user_params
params.require(:user).permit(:avatar, :email, :name, :biography, :role_id, :book_id, :username, :password, :password_confirmation)
end
end
This is trying to create a CRUD to edit users which works but I need to be able to populate the forms in the users/edit view wityh the correct selected users details. I my devise controller I have this setup:
class Admin::UsersController < Admin::BaseController
helper_method :sort_column, :sort_direction
before_filter :find_user, :only => [:edit, :update, :show, :destroy]
def index
#q = User.search(params[:q])
#users = find_users
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to admin_users_path, :notice => "Successfully created user."
else
render :new
end
end
def show
end
def edit
#user = User.find(params[:id])
end
def update
if #user.update_attributes(user_params)
redirect_to admin_users_path, :notice => "Successfully updated user."
else
render :edit
end
end
def destroy
#user.destroy
redirect_to admin_users_path, :notice => "User deleted."
end
protected
def find_user
#user = User.find(params[:id])
end
def find_users
search_relation = #q.result
#users = search_relation.order(sort_column + " " + sort_direction).references(:user).page params[:page]
end
def sort_column
User.column_names.include?(params[:sort]) ? params[:sort] : "created_at"
end
def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : "desc"
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:email,:username,:name,:biography,:role_id,:book_id,:role_name,:password,:password_confirmation,:encrypted_password,:reset_password_token,:reset_password_sent_at,:remember_created_at,:sign_in_count,:current_sign_in_at,:last_sign_in_at,:current_sign_in_ip,:last_sign_in_ip)
end
end
For clarity here is the user model:
class User < ActiveRecord::Base
belongs_to :role
has_many :books, dependent: :destroy
has_many :ideas, dependent: :destroy
accepts_nested_attributes_for :books
accepts_nested_attributes_for :ideas
def confirmation_required?
false
end
extend FriendlyId
friendly_id :username, use: [:slugged, :finders]
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_attached_file :avatar, styles: {
large: "600x450#",
medium: "250x250#",
small: "100x100#"
}, :default_url => "/images/:style/filler.png"
#validates_attachment_content_type :avatar, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
validates :avatar, :email, :username, :password, presence: true
def self.all_authors
User.select('users.id, users.username, users.role_id AS USER_ROLE')
.joins(:role).where(users: {role_id: '2'})
end
before_create :set_default_role
private
def set_default_role
self.role ||= Role.find_by_name('Admin')
end
end
In my routes I added a new route for users below the devise users resource as suggested on devise wiki like so:
devise_for :users, :path_prefix => 'my', :path_names => { :sign_up => "register" }
namespace :admin do
resources :users
end
Can anyone help with adding the ability of admins being able to edit all users here, I think its right but I cannot get the correct data into the forms in edit, it uses the current logged users details only.
A first draft for your ability.rb would be:
class Ability
include CanCan::Ability
def initialize(user)
# ...
if user.admin?
can :manage, User
end
# ...
end
end
And then in your user's controller remove the before_filter :find_user, :only => [:edit, :update, :show, :destroy] and related method, and use
load_and_authorize_resource :user
That would load the user from the URL and authorize! it using CanCan. You'll also need to handle the CanCan::AccessDenied exception for non-admin users visiting those pages, but that is another question that you can check in the CanCan docs.
When you visit admin_users_path routes you'll be able to CRUD them if you have the views ready and working.