I had created a rails 5 api project
and use
Devise gem 4.1.0
Ruby 2.3.0
Rails 5.0.0.rc1
I ran command to override SessionsController
rails generate devise:controllers users
My Custom SessionsController is
class Users::SessionsController < Devise::SessionsController
# before_action :configure_sign_in_params, only: [:create]
# GET /resource/sign_in
# def new
# super
# end
# POST /resource/sign_in
def create
super
end
# DELETE /resource/sign_out
def destroy
super
end
# protected
# If you have extra params to permit, append them to the sanitizer.
# def configure_sign_in_params
# devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute])
# end
end
I got error when call path to sign_out
127.0.0.1:3000/users/sign_out
The error is
"status": 500,
"error": "Internal Server Error",
"exception": "#<NameError: undefined local variable or method `flash' for #<Users::SessionsController:0x00000003d2e8f0>>",
"traces": {
"Application Trace": [],
"Framework Trace": [
{
"id": 0,
"trace": "devise (4.1.1) app/controllers/devise_controller.rb:157:in `set_flash_message'"
},
{
"id": 1,
"trace": "devise (4.1.1) app/controllers/devise_controller.rb:164:in `set_flash_message!'"
},
{
"id": 2,
"trace": "devise (4.1.1) app/controller
s/devise/sessions_controller.rb:61:in `verify_signed_out_user'"
},
My current routes in routes.rb
devise_for :users,
controllers: {
sessions: 'users/sessions',
registrations: 'users/registrations'
}
I debugged but the path didn't route to app/controller/users/SessionsController
How can i route that path
The destroy needs to be altered to perform on a get request
proceed to config/enitializers/devise.rb and on line 239 change
config.sign_out_via = :delete
to
config.sign_out_via = :get
Related
I have 3 models: team, developer and message.
I've been trying to receive a response to a request I send to an endpoint (/trigger_notification) like this:
Response
{
team_id: "team_id",
sms: {
id: "ARANDOMID",
mobiles: ["numer1","number2"]
content: "the content of this message",
sent_at: "18:54:34 IST 2021"
},
email: {
id: "ARANDOMID",
emails: ["email1#email.com", "email2#email.com"]
title: "Used in emails for title",
content: "the content of this message",
sent_at: "18:54:34 IST 2021"
}
}
While most of the response works just fine, the mobiles and emails specifically aren't retrieving all the assigned developers. This should be done when creating a new team with the following request params and some additional optional params in case you also want to create a new developer using nested attributes:
Adding “developers” from the Team API creates developers as well
{
"name": "Core Backend",
"dept_name": "Engineering",
"dev_ids": [id1, id2, id3, id5],
"developers":[
{
"full_name": "Some Name",
"email": "same.name#example.com",
"mobile": "1234567890"
},
{
"full_name": "Other Name",
"email": "other.name#example.com",
"mobile": "9876543210"
}
]
}
Specifically dev_ids is used to note the id of the devs included in a team (since they have an HMT association with each other). I created a separate join table to enable this functionality that is separate from the one used for the has_many through: association called memberships:
class Membership < ApplicationRecord
belongs_to :team
belongs_to :developer
end
So, I create a team and set the dev_ids: [3, 4].
However, I still find that the response ends up looking more like this:
{
"Response": {
"team_id": 3,
"sms": {
"id": "6cefeed1-f0c2-47c0-83cd-1db764e0acae",
"mobile": [],
"content": "This is a test message.",
"sent_at": "2021-10-12T12:51:30.620+05:30"
},
"email": {
"id": "cfddd984-ded0-40dd-badc-42d6694a5d92",
"email": [],
"title": "Test Message #5",
"content": "This is a test message.",
"sent_at": "2021-10-12T12:51:30.620+05:30"
}
}
}
For a few where I created the developer and the team together, it gives a response like this.
{
"Response": {
"team_id": 2,
"sms": {
"id": "435887a7-3729-4b35-b75f-10784a9d056c",
"mobile": [
"1234567890"
],
"content": "This is a test message.",
"sent_at": "2021-10-12T12:52:22.629+05:30"
},
"email": {
"id": "8e4a642c-2008-42b7-b2f8-77319fb7a71e",
"email": [
"pierceb#gmail.com"
],
"title": "Test Message #5",
"content": "This is a test message.",
"sent_at": "2021-10-12T12:52:22.629+05:30"
}
}
}
Controller for this action:
trigger_controller.rb
class TriggerController < ApplicationController
require 'securerandom'
def notification
#message = Message.new(message_params)
#id = params[:team_id]
#content = params[:content]
#title = params[:title]
# #mob = Team.joins(:developers).where(team_id: #id).pluck(:mobile)
# #mob = Team.joins(:developers).pluck(:mobile)
# #mob = Team.includes(:developers).find(#id).pluck(:'developers.mobile')
#mob = Team.find(params[:team_id]).developers.pluck(:mobile)
#mail = Team.find(params[:team_id]).developers.pluck(:email)
tim = Time.now
if #message.save
respond_to do |format|
format.json { render json: { 'Response' => {:team_id => #id,
'sms' => { "id" => SecureRandom.uuid, "mobile" => #mob, :content => #content, "sent_at" => tim },
'email' => { "id" => SecureRandom.uuid, "email" => #mail, :title => #title, :content => #content, "sent_at" => tim } } } }
end
else
render json: #message.errors, status: :unprocessable_entity
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_message
#message = Message.find(params[:id])
end
# Only allow a list of trusted parameters through.
def message_params
params.permit(:team_id, :title, :content)
end
end
Models:
team.rb
class Team < ApplicationRecord
validates :name, presence: true
has_many :messages
has_many :developer_teams
has_many :developers, through: :developer_teams
accepts_nested_attributes_for :developers
end
developer.rb
class Developer < ApplicationRecord
validates :full_name, presence: true
has_many :developer_teams
has_many :teams, through: :developer_teams
end
message.rb
class Message < ApplicationRecord
validates :title, presence: true
belongs_to :team
end
Controller for team:
team_controller.rb
class TeamsController < ApplicationController
before_action :set_team, only: [:show, :update, :destroy]
# GET /teams
def index
#teams = Team.all
render json: #teams
end
# GET /teams/1
def show
render json: #team
end
# POST /teams
def create
#team = Team.new(plus_params)
ActiveRecord::Base.transaction do
# if plus_params.has_key?(:dev_ids)
# #team.developer_ids << Developer.find(plus_params.dev_ids)
# end
# if plus_params.has_key?(:developer_attributes)
# #team.developers << Developer.insert_all(plus_params.developer_attributes)
# end
#team.save!
end
render json: #team, status: :created, location: #team
rescue
render json: #team.errors, status: :unprocessable_entity
end
# PATCH/PUT /teams/1
def update
if #team.update(team_params)
render json: #team
else
render json: #team.errors, status: :unprocessable_entity
end
end
# DELETE /teams/1
def destroy
#team.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_team
#team = Team.find(params[:id])
end
# Only allow a list of trusted parameters through.
def team_params
params.require(:team).permit(:name, :dept_name, dev_ids: [])
end
def plus_params
params.require(:team).permit(:name, :dept_name, dev_ids: [], :developers_attributes => [:full_name, :email, :mobile])
end
end
Here's what the log usually shows when I send a request at the /trigger_notification endpoint:
Started POST "/trigger_notification" for ::1 at 2021-10-12 13:01:16 +0530
Processing by TriggerController#notification as */*
Parameters: {"team_id"=>6, "title"=>"Test Message #5", "content"=>"This is a test message.", "trigger"=>{"team_id"=>6, "title"=>"Test Message #5", "content"=>"This is a test message."}}
Unpermitted parameter: :trigger
Team Load (0.7ms) SELECT "teams".* FROM "teams" WHERE "teams"."id" = $1 LIMIT $2 [["id", 6], ["LIMIT", 1]]
↳ app/controllers/trigger_controller.rb:11:in `notification'
(1.0ms) SELECT "developers"."mobile" FROM "developers" INNER JOIN "developer_teams" ON "developers"."id" = "developer_teams"."developer_id" WHERE "developer_teams"."team_id" = $1 [["team_id", 6]]
↳ app/controllers/trigger_controller.rb:11:in `notification'
CACHE Team Load (0.0ms) SELECT "teams".* FROM "teams" WHERE "teams"."id" = $1 LIMIT $2 [["id", 6], ["LIMIT", 1]]
↳ app/controllers/trigger_controller.rb:12:in `notification'
(0.4ms) SELECT "developers"."email" FROM "developers" INNER JOIN "developer_teams" ON "developers"."id" = "developer_teams"."developer_id" WHERE "developer_teams"."team_id" = $1 [["team_id", 6]]
↳ app/controllers/trigger_controller.rb:12:in `notification'
CACHE Team Load (0.1ms) SELECT "teams".* FROM "teams" WHERE "teams"."id" = $1 LIMIT $2 [["id", 6], ["LIMIT", 1]]
↳ app/controllers/trigger_controller.rb:15:in `notification'
TRANSACTION (0.4ms) BEGIN
↳ app/controllers/trigger_controller.rb:15:in `notification'
Message Create (1.2ms) INSERT INTO "messages" ("team_id", "title", "content", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["team_id", 6], ["title", "Test Message #5"], ["content", "This is a test message."], ["created_at", "2021-10-12 07:31:16.420140"], ["updated_at", "2021-10-12 07:31:16.420140"]]
↳ app/controllers/trigger_controller.rb:15:in `notification'
TRANSACTION (7.7ms) COMMIT
↳ app/controllers/trigger_controller.rb:15:in `notification'
Completed 200 OK in 37ms (Views: 0.5ms | ActiveRecord: 12.2ms | Allocations: 5075)
Thanks in advance!
Rails 5.0.0.1
Ruby 2.3.0p0
I am trying to build a CRUD Rails API and I am having trouble with doing a POST. I am using devise gem for user management. Am I missing something here ?
When I try to create a POST thorugh Postman, I get the following response
{
"status": 406,
"error": "Not Acceptable",
"exception": "#<ActionController::UnknownFormat: ActionController::UnknownFormat>",
"traces": {
"Application Trace": [],
"Framework Trace": [
{
"id": 0,
"trace": "responders (2.3.0) lib/action_controller/respond_with.rb:207:in `respond_with'"
},
{
"id": 1,
"trace": "devise (4.2.0) app/controllers/devise/registrations_controller.rb:32:in `create'"
},
{
"id": 2,
"trace": "actionpack (5.0.0.1) lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'"
},
{
"id": 3,
"trace": "actionpack (5.0.0.1) lib/abstract_controller/base.rb:188:in `process_action'"
},
.....
}
Log looks like this
Started POST "/users/" for 127.0.0.1 at 2016-10-19 02:37:15 -0400
Processing by Devise::RegistrationsController#create as JSON
Parameters: {"email"=>"gabanimillin#gmail.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}
(0.1ms) begin transaction
(0.0ms) rollback transaction
Completed 406 Not Acceptable in 2ms (ActiveRecord: 0.1ms)
ActionController::UnknownFormat (ActionController::UnknownFormat):
responders (2.3.0) lib/action_controller/respond_with.rb:207:in `respond_with'
devise (4.2.0) app/controllers/devise/registrations_controller.rb:32:in `create'
actionpack (5.0.0.1) lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'
actionpack (5.0.0.1) lib/abstract_controller/base.rb:188:in `process_action'
actionpack (5.0.0.1) lib/action_controller/metal/rendering.rb:30:in `process_action'
actionpack (5.0.0.1) lib/abstract_controller/callbacks.rb:20:in `block in process_action'
.....
As far as I can see, my code in user_controller should work
app/controllers/api/v1/user_controller.rb
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
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
config/routes.rb
require 'api_constraints'
Rails.application.routes.draw do
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]
end
end
end
My spec file looks like
spec/controllers/api/v1
require 'rails_helper'
RSpec.describe Api::V1::UsersController, type: :controller do
before(:each) { request.headers['Accept'] = "application/vnd.traveltime_test.v1"}
describe "GET #show" do
before(:each) do
#user = FactoryGirl.create :user
get :show, params: {id: #user.id}, format: :json
end
it "returns the information about a reporter on a hash" do
user_response = JSON.parse(response.body, symbolize_names: true)
expect(user_response[:email]).to eql #user.email
end
it "respond is successful" do
expect(response.status).to eql 200
end
end
describe "POST #create" do
before(:each) do
#user_attributes = FactoryGirl.create :user
post :create, {user: #user_attributes}, format: :json
end
it "returns json body for the user just created" do
end
end
end
This is what I get once I run the test
F
Failures:
1) Api::V1::UsersController POST #create returns json body for the user just created
Failure/Error: params.require(:user).permit(:email, :password, :password_confirmation)
NoMethodError:
undefined method `permit' for "1":String
Did you mean? print
# ./app/controllers/api/v1/users_controller.rb:19:in `user_params'
# ./app/controllers/api/v1/users_controller.rb:8:in `create'
# /Users/GabBook/.rvm/gems/ruby-2.3.0/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:33:in `block in process'
# /Users/GabBook/.rvm/gems/ruby-2.3.0/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:100:in `catch'
# /Users/GabBook/.rvm/gems/ruby-2.3.0/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:100:in `_catch_warden'
# /Users/GabBook/.rvm/gems/ruby-2.3.0/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:33:in `process'
# ./spec/controllers/api/v1/users_controller_spec.rb:25:in `block (3 levels) in <top (required)>'
Finished in 0.05922 seconds (files took 1.18 seconds to load)
3 examples, 1 failure
Failed examples:
rspec ./spec/controllers/api/v1/users_controller_spec.rb:28 # Api::V1::UsersController POST #create returns json body for the user just created
The problem is your POSTMAN parameters.
You posted:
{ "email" => "email", "password" => "password }
Whereas the your controller expects the following params:
{ "user" => { "email" => "email", "password" => "password" } }
As described by your params sanitizer:
params.require(:user).permit(:email, :password, :password_confirmation)
change it
before(:each) do
#user_attributes = FactoryGirl.create :user
post :create, {user: #user_attributes}, format: :json
end
for this
before(:each) do
#user_attributes = FactoryGirl.create :user
post :create, params: {user: #user_attributes}, format: :json
end
Environment
Rails 4.2.0
ruby-2.2.1 [ x86_64 ]
devise 3.4.1
rspec-core 3.2.2
rspec-rails 3.2.1
In my /spec/rails_helper.rb I have included Devise helpers for spec files tagged with type: :controller and type: :request
spec/rails_helper.rb
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:suite) do
begin
DatabaseCleaner.start
FactoryGirl.lint
ensure
DatabaseCleaner.clean
end
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run # ==================> L-60
end
end
config.include FactoryGirl::Syntax::Methods
# RSpec Rails can automatically mix in different behaviours to your tests
# based on their file location, for example enabling you to call `get` and
# `post` in specs under `spec/controllers`.
#
# You can disable this behaviour by removing the line below, and instead
# explicitly tag your specs with their type, e.g.:
#
# RSpec.describe UsersController, :type => :controller do
# # ...
# end
#
# The different available types are documented in the features, such as in
# https://relishapp.com/rspec/rspec-rails/docs
config.infer_spec_type_from_file_location!
config.include Devise::TestHelpers, type: :controller
config.include Devise::TestHelpers, type: :request
end
With that config in place the type: controller specs runs fine. However when running type: request specs I am getting following error:
Failure/Error: Unable to find matching line from backtrace
NoMethodError:
undefined method `env' for nil:NilClass
# /home/.rvm/gems/ruby-2.2.1#myapp/gems/devise-3.4.1/lib/devise/test_helpers.rb:24:in `setup_controller_for_warden'
# ./spec/rails_helper.rb:60:in `block (3 levels) in <top (required)>'
# /home/.rvm/gems/ruby-2.2.1#simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/generic/base.rb:15:in `cleaning'
# /home/.rvm/gems/ruby-2.2.1#simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/base.rb:92:in `cleaning'
# /home/.rvm/gems/ruby-2.2.1#simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/configuration.rb:86:in `block (2 levels) in cleaning'
# /home/.rvm/gems/ruby-2.2.1#simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/configuration.rb:87:in `call'
# /home/.rvm/gems/ruby-2.2.1#simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/configuration.rb:87:in `cleaning'
# ./spec/rails_helper.rb:59:in `block (2 levels) in <top (required)>'
https://github.com/plataformatec/devise/blob/master/lib/devise/test_helpers.rb#L24 is following
def setup_controller_for_warden #:nodoc:
#request.env['action_controller.instance'] = #controller # ==================> L-24
end
I am aware that #request instance is not available for :request type specs and hence the error.
Are there any helpers available we can use to sign-in a user in :request type specs when using Devise?
I found a similar issue https://github.com/plataformatec/devise/issues/1114, the reply to which suggests following:
If you're doing integration tests, make sure to sign in your user in the tradicional way, by filling the sign in form and submitting.
But I would like to by pass the actual login for specs which requires a signed-in user.
Thanks.
With the help of a few SO posts(please refer to the References section below) I have managed to achieve the desired solution. I am posting my working code below, in case it can help others looking out for the same:
spec/rails_helper.rb
RSpec.configure do |config|
....
....
config.include Devise::TestHelpers, type: :controller
config.include Warden::Test::Helpers, type: :request
end
spec/shared_contexts.rb
RSpec.shared_context "api request global before and after hooks" do
before(:each) do
Warden.test_mode!
end
after(:each) do
Warden.test_reset!
end
end
RSpec.shared_context "api request authentication helper methods" do
def sign_in(user)
login_as(user, scope: :user)
end
def sign_out
logout(:user)
end
end
/spec/requests/api/logout_spec.rb
require 'rails_helper'
require 'shared_contexts'
RSpec.describe "Api Logout", :type => :request do
include_context "api request authentication helper methods"
include_context "api request global before and after hooks"
let(:email) { 'test_user1#example.com' }
let(:password) { 'password' }
# Assumes you have FactoryGirl included in your application's test group.
let!(:user) { create(:user, email: email, password: password) }
context "DELETE /logout" do
it "responds with 204 and signs out the signed-in user" do
sign_in(user)
# Till not figured out how to assert Warden has successfully logged in the user like we can do in a Devise controller spec by asserting subject.current_user. If anybody knows a way to do it please share.
# expect(subject.current_user).to_not be_nil
delete "/logout"
expect(response).to have_http_status(204)
end
end
end
I have still not figured out how to assert Warden has successfully logged in the user like we can do in a Devise controller spec by asserting expect(subject.current_user).to_not be_nil. If anybody knows a way to do it please share.
References
Integration test with rspec and devise sign_in env
https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
https://github.com/hassox/warden/blob/master/lib/warden/test/helpers.rb
undefined method 'env' for nil:NilClass
https://github.com/plataformatec/devise/wiki/How-To:-Stub-authentication-in-controller-specs
The code in the above link still relies on a request object which is only available in Controller specs. Thus not useful for type: :request specs.
https://github.com/plataformatec/devise/issues/3555
Thanks,
Jiggnesh
While the popular answer here, also replicated on the Devise wiki, is ok, it is simplest to just:
spec/rails_helper.rb
RSpec.configure do |config|
# ...
config.include Devise::Test::IntegrationHelpers, type: :request
end
And just use sign_in in your request spec. This is the equivalent of declaring include Devise::Test::IntegrationHelpers in an system/feature spec or Rails system/controller test.
Reference
Devise wiki: How to sign in and out a user in request type specs - Simple approach
I'm using 'omniauth-google-oauth2' for sign in with google and follow all instruction here carefully
https://github.com/plataformatec/devise/wiki/OmniAuth%3A-Overview
but i have error above.
my routes
devise_for :users, :controllers => {
:omniauth_callbacks => "users/omniauth_callbacks"
}
devise.rb code
config.omniauth :google_oauth2, "863625299460- 420n6c7lvad91dfvko60uamtvtr6huhf.apps.googleusercontent.com", "dcvA2aZRZi27KCQjWTYP30pw", { access_type: "offline", approval_prompt: "" }
omniauth callback controller code
def google_oauth2
##user = User.find_for_google_oauth2(request.env["omniauth.auth"], current_user)
binding.pry #control not coming here
end
i have error below after callback. see screenshot
https://github.com/zquestz/omniauth-google-oauth2/issues/52
This looks like a route issue. If you do "rake routes | grep auth" what do you see?
I had exactly the same problem you described. Make sure you require the omniauth-google-oauth2 gem in config/initializers/deviser.rb
# ==> OmniAuth
# Add a new OmniAuth provider. Check the wiki for more information on setting
# up on your models and hooks.
require "omniauth-google-oauth2"
config.omniauth :google_oauth2, ENV["GOOGLE_KEY"], ENV["GOOGLE_SECRET"],
{ access_type: "offline", approval_prompt: "force" }
I've added the entire portion of my devise.rb file to provide context.
It's very late but this answer might be useful for others
If you are using devise for authentication then devise by default generates routes in the route file devise_for :users and your omniouth_callback route should be above the default devise route so that it overwrites default devise route.
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks"}
devise_for :users
I have Project controller and rspec file for it. I'm usig current_user helper in this way:
# projects_controller_spec.rb
describe "user signed in" do
before(:each) { sign_in #user }
after(:each) { sign_out #user}
describe "GET index" do
it "assigns all projects as #projects" do
project = FactoryGirl.create(:project, :user => current_user)
get :index, {}, valid_session
assigns(:projects).should eq([project])
end
end
...
end
The helpers sign_in and sign_out seem to work fine but I'v got an error on current_user:
undefined local variable or method `current_user'
What might be a problem, how to fix it?
It's normal. You should create a User instance. You could use let like this :
# projects_controller_spec.rb
describe "user signed in" do
let(:user) {FactoryGirl.create(:user)}
before(:each) { sign_in user }
after(:each) { sign_out user}
describe "GET index" do
it "assigns all projects as #projects" do
project = FactoryGirl.create(:project, :user => user)
get :index, {}, valid_session
assigns(:projects).should eq([project])
end
end
...
end
You know than the current_user is user ...
You don't need to sign out your use after each spec.