Making Devise respond to both html and json? - ruby-on-rails

I am working on an app which needs has both website and an api and I am implementing a common Devise authentication for that. To implement this requirement I am overriding Devise Registrations Controller to respond to both html and json. For json I want the controller to return the user data and I am able to implement this, but for html I want to use the original devise behavior of signing in and redirecting to root path.
The code for Registrations Controller is:
class RegistrationsController < Devise::RegistrationsController
def create
#user = User.create(user_params)
respond_to do |format|
format.html {
#user.save ? (super) : (render :new)
}
format.json {
#user.save ? (render :json => {:state => {:code => 0}, :data => #user }) :
(render :json => {:state => {:code => 1, :messages => #user.errors.full_messages} })
}
end
end
private
def user_params
params.require(:user).permit(:email, :password)
end
end
Using this controller I get a validation error Email has already been taken but when I check my logs it shows the user has been created.
Can anyone tell me what error I am doing? I just want my controller to fallback to original devise functionality in case of html request.

The problem was when you used HTML, you created the user twice by running both #user = User.create(user_params) and super. I moved that first line of code to JSON format, and HTML to only run super.
class RegistrationsController < Devise::RegistrationsController
def create
respond_to do |format|
format.html {
super
}
format.json {
#user = User.create(user_params)
#user.save ? (render :json => {:state => {:code => 0}, :data => #user }) :
(render :json => {:state => {:code => 1, :messages => #user.errors.full_messages} })
}
end
end
private
def user_params
params.require(:user).permit(:email, :password)
end
end

Here is the working version of the code (tested locally myself).
You controller should look like:
def create
#user = User.create(user_params)
respond_to do |format|
format.html {
#user.save ? (render #user) : (render 'layouts/application')
}
format.json {
#user.save ? (render :json => {:state => {:code => 0}, :data => #user }) :
(render :json => {:state => {:code => 1, :messages => #user.errors.full_messages} })
}
end
end
Add a _user partial app/views/users/_user.html.erb:
<p>
<%= #user.email %>
<%= #user.created_at %>
<%= #user.updated_at %>
</p>
So, the changes are minimal. Once you make these changes, your RegistrationsController#create action will work successfully for HTML format.

Related

Rails method that handles ajax complaining about no template?

I have a subscriber#create method that is only used for ajax submits to it (the html form uses data-remote="true" to do the Ajax. The form does indeed submit and the data ends up in the db but the method throws an error saying that the template was not found.
How can I specify a function as being an Ajax handler in Rails? -- one that doesn't have to render a template, etc.
Here is what the method looks like:
class SubscribersController < ApplicationController
def create
Subscriber.create(:email => params[:email],
:ip_address => request.remote_ip,
:referring_page => request.referer ) unless Subscriber.find_by_email(params[:email])
end
end
You should handle the call in your respond_to properly.
...
respond_to do |format|
format.html
format.js { :nothing => true }
end
The thing it, you should probably return something. Even if it is an AJAX call, you should send something back to let the caller know that the creation was a success.
def create
#subscriber = Subscriber.new(#your params)
respond_to do |format|
if #subscriber.save
format.js { render :json => #subscriber, :status => :created, :location => #susbscriber }
else
format.js { render :json => #susbcriber.errors, :status => :unprocessable_entity }
end
end
end
Also, you shouldn't have to do the unless Subscriber.find_by_email(params[:email]) in your controller. You should just add validates_uniqueness_of :email to the Subscriber model.
you want something like render :layout => !request.xhr? in your controller, this will prevent the layout if the request is ajax

OAuth with authlogic and twitter. Doesn't redirect properly

I have an issues with connecting loose end in oauth and authlogic.
I'm running rails 3.0.9 with authlogic working fine and I wanted to add on twitter login.
The issue that I'm having is that after logging in on twitter instead being redirected to call back url defined in twitter dev settings. The app redirects to top domain while appending this to the url
user_sessions?oauth_token=[t_o_k_e_n]
I don't have index action in user_sessions_controller.rb, so I get the action index couldn't be found error, but I can't decipher why I'm being redirected to this url?
My user_sessions.rb
class UserSession < Authlogic::Session::Base
# def to_key
# new_record? ? nil : [ self.send(self.class.primary_key) ]
# end
#
# def persisted?
# false
# end
#
def self.oauth_consumer
OAuth::Consumer.new("asdasdsad", "asdasdasdas",
{ :site=>"http://twitter.com",
:authorize_url => "http://twitter.com/oauth/authenticate"})
end
end
My user_sessions_controller.rb
class UserSessionsController < ApplicationController
# GET /user_sessions/new
# GET /user_sessions/new.xml
def new
#user_session = UserSession.new
end
# POST /user_sessions
# POST /user_sessions.xml
def create
#user_session = UserSession.new(params[:user_session])
#user_session.save do |result|
if result
flash[:notice] = "Login successful!"
redirect_back_or_default root_path
else
render :action => :new
end
end
# respond_to do |format|
# if #user_session.save
# format.html { redirect_to(root_path, :notice => 'User session was successfully created.') }
# format.xml { render :xml => #user_session, :status => :created, :location => #user_session }
# else
# format.html { render :action => "new" }
# format.xml { render :xml => #user_session.errors, :status => :unprocessable_entity }
# end
# end
end
# DELETE /user_sessions/1
# DELETE /user_sessions/1.xml
def destroy
#user_session = UserSession.find
#user_session.destroy
respond_to do |format|
format.html { redirect_to(root_path, :notice => 'Goodbye!') }
format.xml { head :ok }
end
end
end
I even tried adding
:oauth_callback => "http://127.0.0.1:3000/"
to the Consumer.new clause, but that didn't help.
Lastly, my routes.rb looks like this:
resources :users, :user_sessions
match 'login' => 'user_sessions#new', :as => :login
match 'logout' => 'user_sessions#destroy', :as => :logout
Anyone has any ideas on how to troubleshoot this or had a similar problem?
https://dev.twitter.com/sites/default/files/images_documentation/oauth_diagram.png defines quite clearly what you should send and get from Twitters Oauth Provider.
Are you sure you get a oauth_callback_confirmed in step B, if so you might wanna contact Twitter why they validate your oauth_callback then modify it

Rails 3 app doesn't redirect after AJAX form submission

I have an Authlogic login form with :remote => true that does a little inline validation with an RJS template if the user/password isn't valid. This works fine, but when the credentials are valid, it doesn't properly redirect elsewhere.
Here's the controller that responds to the form input:
class UserSessionsController < ApplicationController
respond_to :html, :js
before_filter :require_no_user, :only => [:new, :create]
before_filter :require_user, :only => :destroy
def new
#user_session = UserSession.new
end
def create
#user_session = UserSession.new(params[:user_session])
respond_to do |format|
if #user_session.save
flash[:notice] = "Login successful!"
format.html { redirect_to account_url }
else
format.js
end
end
end
def destroy
current_user_session.destroy
flash[:notice] = "Logout successful!"
redirect_to root_path
end
end
The format.js part works but if the user/password are good (format.html), nothing happens. However, if I look a development.log, it is requesting the account_url page. It's just not redirecting you in the browser. I think it's returning the account page via AJAX and I really just want a normal redirect.
The HTML for the form is this:
<%= simple_form_for(
:user_session, #user_session,
:url => { :controller => 'user_sessions', :action => "create" },
:html => { :id => 'login-dropdown' }, :remote => true) do |f| %>
I found a way to fix it. Per http://www.ruby-forum.com/topic/168406#945053, I added the following to my application controller:
def redirect_to(options = {}, response_status = {})
if request.xhr?
render(:update) {|page| page.redirect_to(options)}
else
super(options, response_status)
end
end
This prevents the redirect response from being delivered via xhr. If there's a more "correct" way of doing this in the controller, I'd like to hear it, though.
You can do like below :
in allpication.js
$(document).ready(function(){
$(document).ajaxError( function(e, xhr, options){
if("401" == xhr.responseText)
{
$(location).attr('href','/users/sign_in');
}
});
})
and controller's method has below code:
respond_to do |format|
format.html {redirect_to #new_user_session_url}
format.js { render :text=>'401' ,:status=>401}
end
But I think there is better way to do so....
app
views
user_sessions
create.js.erb(create file this)
in your create action add this code
def create
#user_session = UserSession.new(params[:user_session])
respond_to do |format|
if #user_session.save
flash[:notice] = "Login successful!"
#url = account_url
end
format.js
end
end
in your "create.js.erb"
window.location.replace(<%= #url %>);

ActiveResource error handling

I have been searching for a while and yet I am not able to find a satisfactory answer as yet. I have two apps. FrontApp and BackApp. FrontApp has an active-resource which mimics a model in BackApp. All the model level validations live in BackApp and I need to handle those BackApp validations in FrontApp.
I have following active-resource code:
class RemoteUser < ActiveResource::Base
self.site = SITE
self.format = :json
self.element_name = "user"
end
This mimics a model which is as follows
class User < ActiveRecord::Base
attr_accessor :username, :password
validates_presence_of :username
validates_presence_of :password
end
Whenever I create a new RemoteUser in front app; I call .save on it. for example:
user = RemoteSession.new(:username => "user", :password => "")
user.save
However, since the password is blank, I need to pass back the errors to FrontApp from BackApp. This is not happening. I just don't understand how to do that successfully. This must be a common integration scenario; but there doesn't seem to be a good documentation for it?
My restful controller that acts as a proxy is as follows:
class UsersController < ActionController::Base
def create
respond_to do |format|
format.json do
user = User.new(:username => params[:username], :password => params[:password])
if user.save
render :json => user
else
render :json => user.errors, :status => :unprocessable_entity
end
end
end
end
end
What is it that I am missing? Any help will be much appreciated.
Cheers
From rails source code I figured out that the reason ActiveResource didn't get errors was because I wasn't assigning errors to "errors" tag in json. It's undocumented but required. :)
So my code should have been:
render :json => {:errors => user.errors}, :status => :unprocessable_entity
In the code:
class UsersController < ActionController::Base
def create
respond_to do |format|
format.json do
user = User.new(:username => params[:username], :password => params[:password])
if user.save
render :json => user
else
render :json => user.errors, :status => :unprocessable_entity
end
end
end
end
end
try to replace
user = User.new(:username => params[:username], :password => params[:password])
with
user = User.new(params[:user])
Your active-resource model pass the params like the hash above:
:user => { :username => "xpto", :password => "yst" }
This solution worked for me: https://stackoverflow.com/a/10051362/311744
update action:
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
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
Calling controller:
#remote_user = RemoteUser.find(params[:id])
if (#remote_user.update_attributes(params[:remote_user]))
redirect_to([:admin, #remote_user], notice: 'Remote user was successfully updated.')
else
flash[:error] = #remote_user.errors.full_messages
render action: 'edit'
end

Simple Authlogic Question: JSON login?

Working from the railscast #160 base code I've set up a very simple site that allows me to log in, out and register an account. (Its almost identical except that I've removed the 'username' from the users migrate table and relevant views so only an email address is required)
I'm now trying to create a new log in action so that I can log in via JSON.
I'd like to be able to send a get request to http://app:3000/apilogin?email=my#email.com&password=p4ssw0rd and have the rails app store the IP address the request came from (if the log in was correct) and send a relevant response (in JSON).
So far I have added a section to controllers/user_sessions_controller.rb so that it goes:
class UserSessionsController < ApplicationController
#...
def new_api
respond_to do |format|
format.json
end
end
end
To routes.rb:
map.apilogin "apilogin", :controller => "user_sessions", :action => "new_api"
But I'm at a loss as to what to put in views/user_sessions/new_api.json.rb! Can you help?
You don't need to define a view at all - just return appropriate json from the controller.
def new_api
#user_session = UserSession.new({:email => params[:email], :password => params[:password]})
respond_to do |format|
if #user_session.save
format.json { render :json => {:success => true} }
else
format.json { render :json => {:success => false, :message => 'incorrect username or password'}, :status => :unauthorized }
end
end
end
You can do something like this:
def new_api
respond_to do |format|
format.json { render :json => user.slice(:name).to_json }
end
end
Or you can also generate the JSON in views/user_sessions/new_api.json.erb as you would write normal erb code. Not a good idea though:
{"name":"<%= #user.name %>"}

Resources