Ruby Devise, Sessionscontroller.Create, JSON - Getting Nameerror: Undefined 'Build_Resource'

Ruby Devise, SessionsController.create, json - getting NameError: undefined 'build_resource'?

If you go here you see the devise registrations_controller.

It has the build_resource method, that you are calling in you sessions_controller

  # Build a devise resource passing in the session. Useful to move
# temporary session data to the newly created user.
def build_resource(hash=nil)
self.resource = resource_class.new_with_session(hash || {}, session)
end

The problem is that it is protected ( under the line that says protected ) That means that the build_resource method can only be called from the devise registrations_controller.

The reason it works with the browser it that the create action in you sessions_controller calls

super

This means that it calls the create action from the devise sessions_controller, which your sessions_controller inherits from -

#devise/sessions_controller
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

This gist shows how to login users trough a json api.

It uses this include

include Devise::Controllers::InternalHelpers

in the sessions_controller. I think this makes it possible to use the build_resource method.

Good luck!

Edit

def create
respond_to do |format|
# when you log into the application through a web browser you go to the format.html option
# Thus you're not calling the build_resource method
format.html {
super
}
# So, lets try to sign in without the build_resource
# I am not really sure what you can do, but try this
format.json {

resource = User.find_for_database_authentication(:login=>params[:user_login][:login])
return invalid_login_attempt unless resource

if resource.valid_password?(params[:user_login][:password])
sign_in("user", resource)
render :json=> {:success=>true, :auth_token=>resource.authentication_token, :login=>resource.login, :email=>resource.email}
return
end
invalid_login_attempt
end
# build_resource # <-This line is evidently producing an error!
# user = User.find_for_database_authentication(:email => params[:user][:email])
# return invalid_login_attempt unless resource
# return invalid_login_attempt unless user

NameError (undefined local variable or method `sign_up_params' for #RegistrationsController:0x0000000000f280)

The RegistrationsController should inherit from Devise::RegistrationsController since you're trying to override it. Once you do that, you can access the sign_up_params instance method.

class RegistrationsController < Devise::RegistrationsController
# ...
end

Rails 5 API and devise flash undefined method error

If you really want to keep Flash,

adding the middleware is indeed necessary. And you also need to include the Flash module in your SessionsController. The module Flash apparently has an implicit dependency on Helpers, so include that, too.

class SessionsController < Devise::SessionsController
include ActionController::Helpers
include ActionController::Flash
...

Note that Devise lets its controllers inherit from your ApplicationController and that one in turn inherits from ActionController::API, which is slimmer than the usual parent class ActionController::Base.



However, you don't have to

Devise is actually aware of so-called navigational formats. Typically, this is only :html (which also happens to be the default format for requests if no other format is specified).

If you explicitly use the :json format with your request, devise will be smart and refrain from adding flashes.

For this to work, you will have to undo a configuration you made: You declared :json to be a navigational format. Don't. Revert that line in your devise initializer:

config.navigational_formats = [:json]  # THIS IS BAD

Then, use an extra .json suffix with your request and all will work as expected:

curl -X POST -F "user[email]=istoselidas@gmail.com" -F "user[password]=example" localhost:3000/v1/sign_in.json

You can get rid of the Flash middleware, too.



Extended answer to extended question: password/edit.json

It seems that Devise's PasswordsController does not respect non-navigational formats when setting flash messages. Probably this was good enough so far because noone cared about new and edit actions with a JSON API. You in your specs are making sure these actions are not accessable, which is perfectly legitimate.

As far as I understand, this is a bug in Devise. Let's devise a pull request to fix it! :)

Your PasswordsController specializes Devise::PasswordsController by overwriting the new and edit actions. The reason why you still get the error is that the harmful code resides in a before_filter called assert_reset_token_passed.

Hence a quick and clean solution for you is to prepend your own filter. You can use your json_not_found method. Since that method renders something, it will stop Devise's filter from being executed.
With this approach you also don't need to override the action methods any more.

class PasswordsController < Devise::PasswordsController
prepend_before_filter :json_not_found, only: [:new, :edit]
end

Devise - Sign In with Ajax

1. Generate Devise controllers so we can modify it

rails g devise:controllers

Sample Image

Now we have all controllers in the app/controllers/[model] directory

2. Edit routes.rb

Let's set Devise to use our modified SessionsController

First add this code (of course change :users to your devise model) into config/routes.rb

devise_for :users, controllers: {
sessions: 'users/sessions'
}

3. Modify sessions_controller.rb

Sample Image

Find the create method and change it to

def create
resource = User.find_for_database_authentication(email: params[:user][:email])
return invalid_login_attempt unless resource

if resource.valid_password?(params[:user][:password])
sign_in :user, resource
return render nothing: true
end

invalid_login_attempt
end

Create new method after protected

def invalid_login_attempt
set_flash_message(:alert, :invalid)
render json: flash[:alert], status: 401
end

4. devise.rb

Insert this into config/initializers/devise.rb

config.http_authenticatable_on_xhr = false
config.navigational_formats = ["*/*", :html, :json]

5. Invalid email or password message

Insert a new message into config/locales/devise.en.yml under the sessions

invalid: "Invalid email or password."

sessions

6. View

= form_for resource, url: session_path(:user), remote: true do |f|
= f.text_field :email
= f.password_field :password
= f.label :remember_me do
Remember me
= f.check_box :remember_me
= f.submit value: 'Sign in'

:javascript
$(document).ready(function() {
//form id
$('#new_user')
.bind('ajax:success', function(evt, data, status, xhr) {
//function called on status: 200 (for ex.)
console.log('success');
})
.bind("ajax:error", function(evt, xhr, status, error) {
//function called on status: 401 or 500 (for ex.)
console.log(xhr.responseText);
});
});

Important thing remote: true

The reason why I am using status 200 or 401 unlike {status: 'true'} is less data size, so it is much faster and cleaner.

Explanation

On signing in, you get these data in params

action: "create"
commit: "Sign in"
controller: "users/sessions"
user: {
email: "test@test.cz"
password: "123"
remember_me: "0"
}
utf8: "✓"

Before signing, you need to authorize the user.

resource = User.find_for_database_authentication(email: params[:user][:email])

User.find_for_database_authentication

If user is found, resource will be filled with something like

created_at: "2015-05-29T12:48:04.000Z"
email: "test@test.cz"
id: 1
updated_at: "2015-06-13T19:56:54.000Z"

Otherwise will be

null

If the user is authenticated, we are about to validate his password

if resource.valid_password?(params[:user][:password])

And finally sign in

sign_in :user, resource

Sources

SessionsController

Helped me
Andreas Lyngstad

Override DeviseController base class - Rails 4, Devise 3

I believe this is the syntax to override Devise controllers:

class RegistrationsController <  Devise::RegistrationsController

If you're receiving method errors, you need to remember this won't totally overwrite the controller - your methods will be delegated to from the "main" devise controller, so you can use things like this:

def method
super
your_code_here
end

Update

class SessionsController < DeviseController
prepend_before_filter :require_no_authentication, :only => [ :new, :create ]
prepend_before_filter :allow_params_authentication!, :only => :create
prepend_before_filter { request.env["devise.skip_timeout"] = true }

prepend_view_path 'app/views/devise'

# GET /resource/sign_in
def new
self.resource = resource_class.new(sign_in_params)
clean_up_passwords(resource)
respond_with(resource, serialize_options(resource))
end

# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)

respond_to do |format|
format.json { render :json => {}, :status => :ok }
format.html { respond_with resource, :location => after_sign_in_path_for(resource) }
end
end

# DELETE /resource/sign_out
def destroy
redirect_path = after_sign_out_path_for(resource_name)
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
set_flash_message :notice, :signed_out if signed_out && is_navigational_format?

# We actually need to hardcode this as Rails default responder doesn't
# support returning empty response on GET request
respond_to do |format|
format.all { head :no_content }
format.any(*navigational_formats) { redirect_to redirect_path }
end
end

protected

def sign_in_params
devise_parameter_sanitizer.sanitize(:sign_in)
end

def serialize_options(resource)
methods = resource_class.authentication_keys.dup
methods = methods.keys if methods.is_a?(Hash)
methods << :password if resource.respond_to?(:password)
{ :methods => methods, :only => [:password] }
end

def auth_options
{ :scope => resource_name, :recall => "#{controller_path}#new" }
end
end


Related Topics



Leave a reply



Submit