Hi there I am currently following railscast 360 on adding Facebook authentication to my webapp. It was all going well until I started getting this error:
"NoMethodError in SessionsController#create
undefined method `name=' for #"
app/models/user.rb:6:in block in from_omniauth'
app/models/user.rb:3:intap'
app/models/user.rb:3:in from_omniauth'
app/controllers/sessions_controller.rb:3:increate'
I have looked at a few different answers on this website but a lot of them seem to be for the Twitter authentication I am just trying to get the Facebook working.
I have created an app on the Facebook Developers and followed the tutorial for the rail cast fully. I would appreciate any help with this greatly.
My code so far is
user.rb:
class User < ActiveRecord::Base
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
end
end
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
rescue ActiveRecord::RecordNotFound
end
helper_method :current_user
end
session_controller.rb
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url
end
def destroy
session[:user_id] = nil
redirect_to root_url
end
end
This is my code inside my application.html.erb:
<div id="user_nav">
<% if current_user %>
Signed in as <strong><%= current_user.name %></strong>!
<%= link_to "Sign out", signout_path, id: "sign_out" %>
<% else %>
<%= link_to "Sign in with Facebook", "/auth/facebook", id: "sign_in" %>
<% end %>
Would really appreciate any help.
Thanks
Going off of your call stack trace (it is in reverse order of calls), it seems like your session_controller calls user model, and in particular the user block.
Looking at your code, no error stands out, but the stack trace is flagging line 6 and mentions that it does not understand the method: undefined method 'name='. If I had to take a guess, it is likely that there is no name column in the table - this may not solve your issue, but try the following:
$ rake db:migrate
and
$ rails c
Then in the rails console, try checking the fields of the User object.
> User.new
Hopefully you'll know whether name is listed in the object or not.
Related
The default devise authorization path (i.e. user_omniauth_authorize_path mentioned here) appears to be designed to work for both OmniAuth registration and login. By the time auth is received in the OmniauthCallbacksController, your rails app typically uses the auth information to create a new user or update an existing user regardless of whether the user intended to login or register with a service (i.e. Facebook).
My stakeholders have requested different behavior for auth and login. If a user clicks "Register with Facebook" and there's no account with their Facebook email, they want to go ahead and create an account with that email. However, if a user clicks "Login with Facebook" and there's no account with their Facebook email, they want to present the user with an error message explaining "The Facebook account you are using does not match an account in our records. Please sign in using your barre3 email and password"
The place for this logic seems to be in the OmniauthCallbacksController's Facebook method. What's the cleanest way to pass the user's intent ('login' or 'register') into this callback method?
You can get back parameters from the authorization to indicate the user's intent by adding some parameters on the authorization URL.
For example:
- if devise_mapping.omniauthable?
- resource_class.omniauth_providers.each do |provider|
= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider, {intent: :sign_in})
(This creates a URL like: http://whatevs.dev/users/auth/facebook?intent=sign_in)
Then, in the callbacks controller (whatever you name it):
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
if env["omniauth.params"]["intent"] == "sign_in"
# Don't create a new identity
else
# Normal flow, such as:
#user = User.from_omniauth(request.env["omniauth.auth"])
sign_in_and_redirect #user
end
end
end
If a user clicks "Register with Facebook" and there's no account with their Facebook email, they want to go ahead and create an account with that email
This assumption is not valid since Facebook can be created with just a phone number. Even user has an email, extra permission is required to get the user email from Facebook.
Your application should validate facebook_uid returned by Facebook API instead of email.
What's the cleanest way to pass the user's intent ('login' or 'register') into this callback method?
For OmniAuth there is no difference between the 'login' or 'register'. All it does is try to authenticate the user with the provided Facebook token.
One clean way to differentiate is to separate on the controller level. If user tries to login in, call SessionsController#create, if user tries to sign up, call UsersController#create.
I have also researched too much for the answer but didn't get any satisfactory answer so I took a step forward and create functionality to work as required. #Jason answer helps me to reach here.
views/users/sessions/new.html.erb
<% if devise_mapping.omniauthable? %>
<% resource_class.omniauth_providers.each do |provider| %>
<%= link_to omniauth_authorize_path(resource_name, provider, {intent: :sign_in}) do %>
<i class="fa fa-<%= provider.to_s.split('_')[0] %>"></i>
<% end %>
<% end %>
<% end %>
views/users/registrations/new.html.erb
<% if devise_mapping.omniauthable? %>
<% resource_class.omniauth_providers.each do |provider| %>
<%= link_to omniauth_authorize_path(resource_name, provider, {intent: :sign_up}) do %>
<i class="fa fa-<%= provider.to_s.split('_')[0] %>"></i>
<% end %>
<% end %>
<% end %>
controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def self.provides_callback_for(provider)
class_eval %Q{
def #{provider}
if request.env["omniauth.params"]["intent"] == "sign_up"
#user = User.from_omniauth_sign_up(request.env['omniauth.auth'])
elsif request.env["omniauth.params"]["intent"] == "sign_in"
#user = User.from_omniauth_sign_in(request.env['omniauth.auth'])
end
if #user.present?
sign_in_and_redirect #user, event: :authentication
set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
else
flash[:errors] = {:email => "is not registered with us."}
session["devise.#{provider}_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
}
end
[:google_oauth2, :facebook, :linkedin].each do |provider|
provides_callback_for provider
end
end
models/user.rb
def self.from_omniauth_sign_up(auth)
# check if email address obtained from auth server is in User table
user = User.where(email: auth[:info][:email]).first
if user.present?
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.image = auth.info.image
user.provider = auth.provider
user.uid = auth.uid
user.save
else
user = User.new(provider: auth.provider, uid: auth.uid)
user.email = auth.info.email
user.username = "#{auth.uid}-#{auth.provider}"
user.password = Devise.friendly_token[0,20]
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.image = auth.info.image
user.status_id = 2
user.skip_mobile_validation = true
user.skip_confirmation!
user.save
end
user
end
def self.from_omniauth_sign_in(auth)
if user = User.where(email: auth[:info][:email]).first
user
end
end
I'm working through the tutorial at Railstutorial.org and have completed chapter 8 with passing tests. My issue is that if I follow the guide's code exactly, I am able to log in, but unable to log out.
If I click "Log out" I am redirected to the root_path, but as a still logged in member.
I think I traced the behavior to my sessions helper. Specifically the following lines:
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
Through the rails console and pry I was able to determine that cookies[:remember_token] is nil, but since my user's remember_token is also nil, the find_by_remember_token is saying, "Hey nil == nil! Great! We found our user!" Except that this is obviously not desired behavior.
I've fixed it by changing the current_user method to the following:
def current_user
#current_user ||= cookies[:remember_token] && User.find_by_remember_token(cookies[:remember_token])
end
I completely accept and understand that this is likely an error in my code. I've found Michael Hartl's commit from this point in the project and compared our files and can't find a discrepancy. Any ideas on what might be going on here?
Thank you for your time.
put this in your...
Controller:
def logout
reset_session
redirect_to :controller => 'classified', :action => 'list'
end
and in your view: you need to add a logout link.
<% else %>
<p><%= "Welcome #{session[:user].login}!" -%></p>
<p><%= link_to 'Logout', :controller => 'user',:action => 'logout' -%></p>
<% end %>
I am trying to confirm user accounts in test mode while using devise and rails. Here's the scenario. I changed the devise confirmation email that is sent into a partial that is rendered after a user has successfully signed up. Problem is it shows the following error "undefined method email". Here's my confirmation partial.Welcome <%= #resource.email %>!
You can confirm your account through the link below:
<%= link_to 'Confirm my account', confirmation_url(#resource, :confirmation_token => #resource.confirmation_token) %>. And below is my application_helper in order to access the resource details. def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end</pre>
Is #resource actually the user in application_helper? I know if it is nil it will be assigned a new User, but what is it before it assigns it?
I suspect #resource isn't a User, but something else (a something that doesn't have an email field).
RailsTutorial.org Exercise 9.6.2: I'm trying to sign in with cookies if a 'Stay signed in' checkbox is checked, otherwise sign in with a session. Starting from Michael Hartl's code, I've made the following changes:
I've added this code to the sign-in form in views > sessions > new.html.erb:
<div class="field">
<%= f.check_box :stay_signed_in %> Stay signed in?
</div>
I've added this one line in the sessions controller:
def create
session[:staysignedin] = (params[:session][:stay_signed_in] == "1") ? true : false
...
end
And I've made the following alterations in the Sessions Helper:
def sign_in(user)
if session[:staysignedin]
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
else
session[:userid] = user.id
end
self.current_user = user
end
def sign_out
if session[:staysignedin]
cookies.delete(:remember_token)
else
session[:userid] = nil
session[:staysignedin] = nil
end
self.current_user = nil
end
def user_from_remember_token
session[:staysignedin] ? User.authenticate_with_salt(*remember_token) : User.find_by_id(session[:userid])
end
The problem is that it always behaves as if the checkbox is unchecked, always signing in using session rather than cookies, even when the checkbox is checked, and I just can't figure out why.
Try this: http://www.nimweb.it/web-development/ruby-on-rails-web-development/ruby-on-rails-tutorial-exercise-9-6-2-rails-session/
I'm currently going through a RoR tutorial (http://railstutorial.org/chapters/sign-in-sign-out#sec:signin_success is the relevant section) which seems to be fairly good, although I've run into the following problem when trying to view the sample site.
Extracted source (around line #10):
7: <li><%= link_to "Home", root_path %></li>
8: <li><%= link_to "About", about_path %></li>
9:
10: <% if signed_in? %>
11: <li><%= link_to "Profile", current_user %></li>
12: <li><%= link_to "Sign out", signout_path, :method => delete %></li>
13: <% else %>
As you can see, the issue is stemming from my method "signed_in?" which is supposed to check if the user has logged in or not by checking whether the current_user variable is set (I've included the rest of the code from the helper to give a context, apologies):
module SessionsHelper
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
current_user = user
end
def sign_out
cookies.delete[:remember_token]
current_user = nil
end
def current_user= (user)
#current_user ||= user_from_remember_token
end
def signed_in?
!current_user.nil?
end
private
def user_from_remember_token
User.authenticate_with_salt(*remember_token)
end
def remember_token
cookies.signed[:remember_token] || [nil, nil]
end
end
From my understanding, .nil? is a method that checks whether or not an object has been defined and therefore the object being undefined shouldn't generate an error but rather return false? I searched the tutorial for all cases of current_user (before checking to see if anyone else had this problem with little success) and my code seems correct so I'm a little confused and if anyone is able to help me understand the way Ruby variables are supposed to be accessed and why my code isn't working I'd be most grateful.
Edit:
I'm not sure if it's important with scope as I'm just beginning both Rails and Ruby, however the helper SessionsHelper is being used by my Users controller and views (it's included in my Applications controller)
I ran in to this same issue & it was for the same reason. You overlooked part of the instructions on 'Listing 9.16'.
def current_user= (user)
#current_user ||= user_from_remember_token
end
You were supposed to change this to the following.
def current_user
#current_user ||= user_from_remember_token
end
You'll also want to change all of the instances of *self.*current_user to *#*current_user.
Once you do this the error(s) are resolved.
Make sure you have the following code in the SessionHelper
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
The nil? method is not going to check whether a variable or method is defined. It is solely checking whether it is defined as a nil object or not. Ruby walks up the ancestors chain for SessionsHelper and finally determines that current_user is not defined anywhere (it will eventually end at Kernel#method_missing) and then throws an error. The quickest way to solve the problem would be:
#app/helpers/sessions_helper.rb
def current_user
#current_user ||= false
end
I asked a friend, and he corrected my errors. I think a large part of my mistake came from not being completely familiar with variable scope in Ruby and forgetting that everything is an object and therefore the method current_user=(user) was overriding the assignment function.
My friend's solution was to change the scope of current_user to an instanced variable (so it can be properly used), and change the function curent_user=(user) to a simple get_current_user function in order to determine if the current user exists in the cookie.
The final changed code is as follows:
#app/helpers/sessions_helper.rb
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
#current_user = user
end
def sign_out
cookies.delete(:remember_token)
#current_user = nil
end
def get_current_user
#current_user ||= user_from_remember_token
end
def signed_in?
!get_current_user.nil?
end
#app/views/layouts/_header.erb
<% if signed_in? %>
<li><%= link_to "Profile", get_current_user %></li>
<li><%= link_to "Sign out", signout_path, :method => :delete %></li>
<% else %>
<li><%= link_to "Sign in", signin_path %></li>
<% end %>
As you can see the variable in my header partial has also been changed to reflect the method used in the helper to obtain the user.
Going to start reading some basic Ruby guides so next time I get in over my head I have an idea of where to start fixing it :)