I'm using the gem called omniauth-facebook, with which I succeeded at implementing facebook login auth.
It looks fine but it won't pass data of the whole object to the view. It just says nil.
It should show an array of things when using 'debug' or 'inspect'. It shows the content of session[:name] fine somehow.
Controller
class SessionsController < ApplicationController
def create
#auth = request.env["omniauth.auth"]
session[:oauth_token] = #auth.credentials.token
session[:username] = #auth.extra.raw_info.username
session[:name] = #auth.extra.raw_info.name
redirect_to bookd_url, :notice => "Signed in!"
end
end
View
<% if signed_in? %>
<%= #auth.inspect %>
<%= debug #auth %><br />
<%= session[:name] %>
<% end %>
Output HTML
nil
---
...
John
Your create controller action does a redirect. After the redirect, the process will start from scratch, and #auth will no longer be defined. If you render the view at this point, #auth will be nil. This is your problem.
You need to think about what you are trying to do here. You set an #auth variable from the authentication details in the initial request. You then use this to set some data in the session, which records who is logged in for example. Then, on the next page, where the user is logged in, you want to look at #auth. This doesn't really make sense: once you've authenticated a user, all you need to care about is remembering which user is currently logged in. You don't need to store details about HOW they logged in, and in fact you probably shouldn't.
You should instead be doing something like this:
#in ApplicationController, protected section
protected
def current_user
if #current_user
return #current_user
elsif session[:username]
#current_user = User.find_by_username(session[:username])
return #current_user
end
end
This will allow you to write current_user in your controller and view code, to access the user who authenticated, originally, which is the whole reason for logging someone in and keeping them logged in.
Related
Just installed Geocoder and trying to get it to show signs of life. I want a user to log in and have the profile landing page to show data on their ip. Since I'm in the dev environment, here is my attempt at a temporary solution:
class SessionsController
def create
user = User.authenticate(params[:username_or_email], params[:password])
if user
session[:user_id] = user.id
redirect_to profile_path, :notice => "Logged in!"
#request = Rack::Request.new({'REMOTE_ADDR' => '107.128.188.218'})
#location = #request.location
#blah blah blah
end
in the view:
<%= #location.data %>
the error I get is undefined method 'data' for nil:NilClass
in the intializers/geocoder.rb
Geocoder.configure(
:timeout => 30
)
You are assigning ivars #request and #location in the create action of your SessionsController, but the lifetime of these variables is just that request. After the controller action finishes, you are redirecting the client's browser to your ProfilesController, so you need to move those two assignments into ProfilesController#show, then it should work.
EDIT: Since you say you are using the SessionsController show action for the user's profile, moving those two statements to that action method should do the trick. :-)
I am working on a reservation project and after login I want to pass the current user information from the Sessions Controller to a Reservations Controller via a home page. How can I do that? I have been following Michael Hartl's Rails Tutorial for the login. Here's what I have tried in the create section of the Sessions Controller
render home_path :local_variables[:id => user_id]
and in the home_path(users#home)
<%= link_to new_reservation_path(:user_id => :id) %>
but it shows me an undefined method error. My Reservation model has a user_id column.I am confused regarding this matter. What should I do?
render home_path :local_variables[:id => user_id]
Seems weird to me to pass locals that way (don't even know if it's possible, never seen locals used outside of rendering views/partials).
I think the best way is to redirect instead and set the user in the sessions once they have been logged in successfully, so in your login action:
user = User.find_by_email(params[:user][:email]) # or however you are finding the person trying to login
session[:user] = user
redirect_to home_path
then in users#home
#user = session[:user]
and finally, in the view:
<%= link_to new_reservation_path(:user_id => #user.id) %>
EDIT
Actually, probably not a good idea to store an object in the session, instead of
session[:user] = user
You could try:
session[:user_id] = user.id
and then to find the user:
#user = User.find(session[:user_id])
If you still get an undefined error then it's probably because the user is nil (unless the User model actually has no id attribute, which would be strange) so there might be an issue with the login part, it's hard to say without knowing what that looks like.
If you need the logged in user on every page, you could abstract the functionality out into the application controller:
before_filter :check_user
def check_user
#user = User.find(session[:user_id]) if session[:user_id]
end
Then, you can use the #user instance variable anywhere in your app.
I have a small problem:
I am trying to send a variable from controller 1 to controller 2 and then send it back to controller 1. Here is the logic:
In order to start making a new model, the user has to sign in.
Guest users must also have access to the path of the new model.
If not logged in, guest users have to be redirected to sign in.
Once logged in, users have to go back to the path they previously tried to access.
I decided to pass the type of the model to the log in screen and then pass it back to the new model action.
The variable type usually comes from the route, so upon redirecting to the login screen, I just pass it over.
businesses_controller.rb:
before_filter :require_login
def require_login
unless current_user
redirect_to signin_path( :type => params[:type])
end
end
When I get redirected, the variable gets passed into my url - so far, so good:
/signin?type=ccompany
The problem comes when I try to grab the variable from the URL and use its value to redirect the successfully logged in user back to where he tried to go to:
sessions_controller.rb:
class SessionsController < ApplicationController
before_filter :initialize_type , :only => [:new , :create]
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
sign_in user
if defined?(#type)
redirect_to send("new_#{#type}_path")
else
redirect_back_or user
end
else
flash.now[:error] = t('sign.invalid')
render 'new'
end
end
def destroy
sign_out
redirect_to root_url
end
private
def initialize_type
#type = params[:type]
end
end
When I put the #type variable into my view, its value renders without problems. But when I try to log in, this is the error I get:
undefined method `new__path' for #<SessionsController:0x007f76189d42f8>
I also tried
redirect_to :controller => "businesses" , :action => "new" , :type => params[:type]
But the type symbol passes with no key. I also tried putting a variable there, still doesn't work.
So far, it has only worked when I just write down a string that matches the path, but that is not what I need. I need to pass the value of the params[:type] hash. I've put the value of the variable in the view and it's all good, but when I try to pass it into a function, its value somehow 'disappears'.
How can I make this work?
Problem is:
if defined?(:type)
it always returns true, as a symbol is always defined. What you want is:
if defined?(#type)
however this might add 'unintentional feature' to your code, as nil is well defined in ruby. You should do:
if #type
NOTE:
I need to pass the value of the :type symbol. - symbol has no value. Variables do have values, symbol is a symbol and it is its own value.
After understanding the question:
The above still holds.
You cannot pass instance variable from one action to another. For each request rails instantiate new controller instance and all instance variables are lost. There are number of ways to pass it though.
Create hidden field in your form to hold the value. You need to keep in mind that its value can be overwritten by any internet user with a minimal knowledge of how internet works.
In your new action simple write the value into a session and read it in you create action. Since session is either stored server side or stored in encrypted cookie, there is very small chance anyone can temper with it.
Ok, I found a workaround.It's a bit clumsy and may cause security issues, but it works. I forced the variable to pass as a session parameter.
I put the received variable as a hidden field in the new session form:
<%= form_for(:session , url: sessions_path ) do |f| %>
<%= f.label :email , t('session.email') %>
<%= f.text_field :email %>
<%= f.label :password , t('session.password') %>
<%= f.password_field :password%>
<%= f.hidden_field :val , value: #type %>
##type is set in the before filter as a variable passed from another controller
Then, upon submit, I just catched the parameter and put it into its place:
if user && user.authenticate(params[:session][:password])
sign_in user
if defined?(#type)
params.require(:session).permit(:val)
#type = params[:session][:val]
redirect_to send("new_#{#type}_path")
else
redirect_back_or user
end
else
flash.now[:error] = t('sign.invalid')
render 'new'
end
end
Now the function works properly. Unfortunately, I have to use the view as a "conduit" that passes a variable. I would be really happy if somebody writes a helper method for this or a better workaround.
Cheers.
I am trying to figure out the best way to do the following (there are a few ways I can think of, but I want to know what the best way to handle it is):
A user is putting together a shipment, and then clicks the "Send" link, which sends him to the /shipments/:id/confirm page. The confirm action checks to see if the user has a completed ShippingAddress; if not, it sends him to the ShippingAddress#new. (If he does, it render the confirm page.
I want the user to be able to complete the ShippingAddress#new page, submit it, and then be redirect back to the /shipments/:id/confirm. How can I do that? How can I pass the :id to the ShippingAddress#new page without doing something like redirect_to new_shipping_address_path(shipment_id: #shipment.id) in the Shipment#confirm action? Or is that the best way to do that?
class ShipmentsController < ApplicationController
def confirm
#shipment = Shipment.where(id: params[:id]).first
unless current_user.has_a_shipping_address?
# Trying to avoid having a query string, but right now would do the below:
# in reality, there's a bit more logic in my controller, handling the cases
# where i should redirect to the CardProfiles instead, or where I don't pass the
# shipment_id, and instead use the default shipment.
redirect_to new_shipping_address_path(shipment_id: #shipment.id)
end
end
end
class ShippingAddressesController < ApplicationController
def new
#shipment = Shipment.where(id: params[:shipment_id]).first
end
def create
#shipment = Shipment.where(id: params[:shipment_id]).first
redirect_to confirm_shipment_path(#shipment)
end
end
[In reality, there is also a CardProfiles#new page that needs to be filled out after the shipping address is].
Try calling render instead of redirect_to, and set the id into an instance variable. Adjust the view logic to pull that instance variable if it exists.
#shipment_id = #shipment.id
render new_shipping_address_path
In the view
<%= form_for #shipment_address do |f| %>
<% if #shipment_id %>
<%= hidden_field_tag :shipment_id, #shipment_id %>
<% end %>
I don't know your view logic entirely, but giving an example.
Say I have a site like this (generic Q&A site) in Rails and I wanted this "ask" page w/ a text box to be the first page a user sees, even if he's not logged in. He enters a question, and on the 'new' method I check that he's not logged in, and bounced him to /session/new, where he can either log in or create a new account. Question is, how do I (and what is the best way to) preserve that question that he initially asked all through this process?
I'm understanding the flow of action described in the question to be
user is presented with a form
user is redirected to log in page on submit
user is redirected back to form on successful log in
repopulate form on load (Question asks how to do this step)
user finally submits their form.
With steps 2-4 omitted if the user is logged in.
I'm sorry, but I see your question more as a symptom of an underlying UI issue than a rails question.
If only logged in users can post questions, then why display the text box?
If a user is going to have log in any way, why not get that out of the way first. An even better solution is to integrate the log in and form.
Something like this in the view:
<% form_for :question do |form| %>
<% unless logged_in? %>
<% fields_for :session do |session_form|%>
<%= session_form.label :login %>
<%= session_form.text_field :login %>
<%= session_form.label :password %>
<%= session_form.password_field :password %>
<%end%>
<%end%>
<%= form.text_area :question %>
<%end%>
And in the controller
def new
...
unless params[:session].nil?
self.current_user = User.authenticate(params[:session][:login], params[:session][:password])
end
if logged_in?
flash[:notice] = "Logged in successfully"
else
flash[:error] = "Incorrect username and or password."
end
if logged_in? && #question.save
.... process successful entry
else
... process unsuccessful entry
end
end
Edit: Mohamad's raises the question of reusing this pattern across multiple controllers and forms. So the answer was updated to address reuse of this pattern.
To simplify this for reuse, you could put this block in a helper function that is referenced in the before_filter for actions that require it.
def login
unless params[:session].nil?
self.current_user = User.authenticate(params[:session][:login], params[:session][:password])
if logged_in?
flash[:notice] = "Logged in successfully"
else
flash[:error] = "Incorrect username and or password."
end
end
end
as in:
before_filter :login => :only [:new , :edit, :update, :delete]
On the view side, it shouldn't be too hard to construct a new variant of form_for that embeds the session parameters. Maybe form_for_with_session?
As for handling an unsuccessful response, I would suggest helper function that takes a block of code. Sorry I don't have time to write out or test one for you.
You keep it in the session. So after logging in, when the user goes back to asking his question, you see there's already something in session.
And you can directly display it.
def create
if current_user # Implement this method in your auth framework
#question = Question.new(params[:question] || session.delete[:question])
# (the usual stuff you'd do to save)
else
session[:question] = params[:question]
redirect_to :controller => :sessions, :action => "new"
end
end
Then, after your user creation and authentication stuff is all done in your login action, just make sure you POST back to this create action if session[:question] is defined.