Rails 4, devise, omniauth, facebook: problems when regular sign up involves nested forms - facebook

I am trying to set up facebook login integration with omniauth and devise.
Note that I have a MEMBER model which contains the devise info and a USER model that contains the member profile info (to keep them separate)
On the sign up page, the user has a nested form in the member sign up form and receives all the user data at that time. Once the member is saved, the email value for the saved member is also entered into the user table like so
class Member < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable, :omniauthable
after_save :update_user
def update_user
user.email = self.email
user.save
end
I have the standard model method for the facebook data processing.....
def self.process_omniauth(auth)
where(provider: auth.provider, uid: auth.uid, email: auth.info.email).first_or_create do |member|
member.provider = auth.provider
member.uid = auth.uid
member.email = auth.info.email
end
end
But of course, in this case, there is no nested form so the 'user' is not instantiated and it throws an error when the update_user method is called on saving the member.
How can I modify this method above to instantiate a new user, when signing up via the facebook pathway?
EDIT: This works.....
def update_user
if User.find_by_member_id(self.id).present?
user.email = self.email
user.save
else
User.create(:email => self.email, :member_id => self.id)
end
end
BUT THIS RESULTS IN A CONSTRAINT ERROR - on email - it already exists in the database. From the logs, the else statement here appears to be attempting to create the user twice.
def update_user
if User.find_by_member_id(self.id).present?
user.email = self.email
user.save
else
User.create(:email => self.email)
end
end
Can anyone explain this? I am suprised I had to pass the foreign key to the user in the else block.

I'm not sure what fields are being stored in the User Model but what you should be doing here that in case of Facebook Callback you should create the User if the Member don't have an user associated with it. i.e your update_user should be something like this:
def update_user
if user.present?
user.email = self.email
user.save
else
User.create(:email => self.email, :member_id => self.id)
/*note that a lot of other information is returned in
facebook callback like First Name, Last Name, Image and
lot more, which i guess you should also start saving to
User Model */
end
end
**** EDIT ******** . You should also check if there is user with the same email and associate that with the member.
def update_user
if user.present?
user.email = self.email
user.save
else
user = User.find_by_email(self.email)
if user
user.update_attribute(:member_id, self.id)
else
User.create(:email => self.email, :member_id => self.id)
end
end

Related

Ruby on Rails Tutorial NoMethodError in Users#index

I'm reading Michael Hartl's great "The Ruby on Rails Tutorial". And I faced a problem that wasn't described in the book.
I push a submit button on a Sign up page with blank fields, then, as it expected, the mistakes messages like these appears:
Name can't be blank
Email can't be blank
Email is invalid
Password can't be blank
But the URL changes from http://[::1]:3000/signup to http://[::1]:3000/users
Then I push the Chrome's refresh button and get redirecting to exactly http://[::1]:3000/users URL.
I understand that the pushing the submit button directs to .../users URL. How to change this route to the right one (still http://[::1]:3000/signup)?
Here's my users_controller.rb file
class UsersController < ApplicationController
before_action :logged_in_user, only: [:show, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def index
#users = User.where(activated: true).paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
redirect_to root_url and return unless #user.activated?
#microposts = #user.microposts.paginate(page: params[:page])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'users/new', status: :unprocessable_entity
end
end
def edit
#user = User.find(params[:id])
end
end
Here's my routes.rb file
Rails.application.routes.draw do
get 'password_resets/new'
get 'password_resets/edit'
root 'static_pages#home'
get '/help', to: 'static_pages#help'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
get '/signup', to: 'users#new'
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
default_url_options :host => "example.com"
resources :users
end
Thanks in advance!

PG::SyntaxError at /bookmarks - I'm unable to work out why the SQL query is wrong

When running my application using sinatra, I get the error message PG::SyntaxError at /bookmarks
ERROR: syntax error at or near "{" LINE 1: SELECT * FROM users WHERE id = {:id=>"5"} ^
It happens when I click the submit button on /users/new route which should then take me to index route /.
The backtrace provides the following information
/Users/BartJudge/Desktop/Makers_2018/bookmark-manager-2019/lib/database_connection.rb in async_exec
#connection.exec(sql)
/Users/BartJudge/Desktop/Makers_2018/bookmark-manager-2019/lib/database_connection.rb in query
#connection.exec(sql)
/Users/BartJudge/Desktop/Makers_2018/bookmark-manager-2019/lib/user.rb in find
result = DatabaseConnection.query("SELECT * FROM users WHERE id = #{id}")
app.rb in block in <class:BookmarkManager>
#user = User.find(id: session[:user_id])
This is the database_connection file
require 'pg'
class DatabaseConnection
def self.setup(dbname)
#connection = PG.connect(dbname: dbname)
end
def self.connection
#connection
end
def self.query(sql)
#connection.exec(sql)
end
end
This is the user model
require_relative './database_connection'
require 'bcrypt'
class User
def self.create(email:, password:)
encypted_password = BCrypt::Password.create(password
)
result = DatabaseConnection.query("INSERT INTO users (email, password) VALUES('#{email}', '#{encypted_password}') RETURNING id, email;")
User.new(id: result[0]['id'], email: result[0]['email'])
end
attr_reader :id, :email
def initialize(id:, email:)
#id = id
#email = email
end
def self.find(id)
return nil unless id
result = DatabaseConnection.query("SELECT * FROM users WHERE id = #{id}")
User.new(
id: result[0]['id'],
email: result[0]['email'])
end
end
This is the controller
require 'sinatra/base'
require './lib/bookmark'
require './lib/user'
require './database_connection_setup.rb'
require 'uri'
require 'sinatra/flash'
require_relative './lib/tag'
require_relative './lib/bookmark_tag'
class BookmarkManager < Sinatra::Base
enable :sessions, :method_override
register Sinatra::Flash
get '/' do
"Bookmark Manager"
end
get '/bookmarks' do
#user = User.find(id: session[:user_id])
#bookmarks = Bookmark.all
erb :'bookmarks/index'
end
post '/bookmarks' do
flash[:notice] = "You must submit a valid URL" unless Bookmark.create(url: params[:url], title: params[:title])
redirect '/bookmarks'
end
get '/bookmarks/new' do
erb :'bookmarks/new'
end
delete '/bookmarks/:id' do
Bookmark.delete(id: params[:id])
redirect '/bookmarks'
end
patch '/bookmarks/:id' do
Bookmark.update(id: params[:id], title: params[:title], url: params[:url])
redirect('/bookmarks')
end
get '/bookmarks/:id/edit' do
#bookmark = Bookmark.find(id: params[:id])
erb :'bookmarks/edit'
end
get '/bookmarks/:id/comments/new' do
#bookmark_id = params[:id]
erb :'comments/new'
end
post '/bookmarks/:id/comments' do
Comment.create(text: params[:comment], bookmark_id: params[:id])
redirect '/bookmarks'
end
get '/bookmarks/:id/tags/new' do
#bookmark_id = params[:id]
erb :'/tags/new'
end
post '/bookmarks:id/tags' do
tag = Tag.create(content: params[:tag])
BookmarkTag.create(bookmark_id: params[:id], tag_id: tag.id)
redirect '/bookmarks'
end
get '/users/new' do
erb :'users/new'
end
post '/users' do
user = User.create(email: params[:email], password: params[:password])
session[:user_id] = user.id
redirect '/bookmarks'
end
run! if app_file == $0
end
self.find(id), in the user model, is where the potentially offending SQL query resides.
I've tried;
"SELECT * FROM users WHERE id = #{id}"
and "SELECT * FROM users WHERE id = '#{id}'"
Beyond that, I'm stumped. The query looks fine, but sinatra is having none of it.
Hopefully someone can help me resolve this.
Thanks, in advance.
You're call find with a hash argument:
User.find(id: session[:user_id])
but it is expecting just the id:
class User
...
def self.find(id)
...
end
...
end
Then you end up interpolating a hash into your SQL string which results in invalid HTML.
You should be saying:
#user = User.find(session[:user_id])
to pass in just the id that User.find expects.
You're also leaving yourself open to SQL injection issues because you're using unprotected string interpolation for your queries rather than placeholders.
Your query method should use exec_params instead of exec and it should take some extra parameters for the placeholder values:
class DatabaseConnection
def self.query(sql, *values)
#connection.exec_params(sql, values)
end
end
Then things that call query should use placeholders in the SQL and pass the values separately:
result = DatabaseConnection.query(%q(
INSERT INTO users (email, password)
VALUES($1, $2) RETURNING id, email
), email, encypted_password)
result = DatabaseConnection.query('SELECT * FROM users WHERE id = $1', id)
...

Facebook redirects to sign up page after authenticating user Rails

I am intergrating Facebook login using device, and after allowing Facebook to get your info it redirects you to the sign up page. Here's my code:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :authenticate_user!
before_action :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :description, :photo ])
devise_parameter_sanitizer.permit(:account_update, keys: [:username, :first_name, :last_name, :description, :photo ])
end
end
Callback controller
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
#user = User.from_omniauth(request.env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
def failure
redirect_to root_path
end
end
And my user model is
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"]
user.email = data["email"] if user.email.blank?
end
end
end
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.first_name = auth.info.name # assuming the user model has a name
user.image = auth.info.image # assuming the user model has an image
end
end
I'd really appreciate an extra eye on what I'm doing wrong, or where I am missing something. Thanks!
You need to add this column for provider and uid to your users table, i think rails can't match your user :
rails g migration add_provider_and_uid_to_users provider:string uid: string
And try to write me the rails server response it Can help to figure out the bug source

Devise facebook login. #user not persisting, getting redirected to sign up page

I followed all the instructions in Omniauth's walkthrough.
Note that my User model does not have name or image properties.
I followed the instructions in the Rails tutorial. However, I did have to change this:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, :omniauth_providers => [:facebook]
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.name = auth.info.name # assuming the user model has a name
user.image = auth.info.image # assuming the user model has an image
end
end
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"]
user.email = data["email"] if user.email.blank?
end
end
end
end
to this:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, :omniauth_providers => [:facebook]
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
# user.name = auth.info.name # assuming the user model has a name
# user.image = auth.info.image # assuming the user model has an image
end
end
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"]
user.email = data["email"] if user.email.blank?
end
end
end
end
an error was being thrown because User doesn't have name or image properties.
That led to a new error:
The #user created in the Omniauth_callbacks_controller is not persisting, so instead of getting signed-in, I'm just getting redirected back to the user sign-up page.
omniauth_callbacks_controller.rb:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user = User.from_omniauth(request.env["omniauth.auth"])
puts "BEFORE IF STATEMENT"
puts #user.inspect
if #user.persisted?
puts "INSIDE IF STATEMENT"
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
puts "AFTER ELSE STATEMENT"
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
Server read out, which includes the puts statement and the #user object. You'll see that most of its fields are nil:
Started GET "/users/auth/facebook" for ::1 at 2015-09-01 20:41:01 +0100
I, [2015-09-01T20:41:01.891561 #7662] INFO -- omniauth: (facebook) Request phase initiated.
Started GET "/users/auth/facebook" for ::1 at 2015-09-01 20:41:01 +0100
I, [2015-09-01T20:41:01.978637 #7662] INFO -- omniauth: (facebook) Request phase initiated.
Started GET "/users/auth/facebook/callback?code=AQBcPU1SSS5908ZaCIgZjKnffSjG0PZTNcoyeSjiTbZDbVZvOKIz_YDoZzijNvfuc5QdIHczQPVAFqv3TMxDPZ_lGdUC3sg6d9iScaBgwVqU6uuoGppV7fAO-Q2ALN48is9-Exkr1o0JF2Yry9nebSxcSEDpBz39jDU0EMxOWCShGwG0CCaKLavOo0GzXzmZr1mpYaUZoBgxHSUdr3rRfhoqYMZOrYAYQeR8DMcAw7WR-C4PNKN9NyMwhzWDFv7mtoneP6dWAd22SNOLmQC64ahgJTFsN76brLl1Xl6HYR0wqjd4LBJeeV4uAumdVWkiIgo&state=0c40e8bcabffe59ec093223f78250db73c9a3d9b7717be97" for ::1 at 2015-09-01 20:41:02 +0100
I, [2015-09-01T20:41:02.098574 #7662] INFO -- omniauth: (facebook) Callback phase initiated.
Processing by Users::OmniauthCallbacksController#facebook as HTML
Parameters: {"code"=>"AQBcPU1SSS5908ZaCIgZjKnffSjG0PZTNcoyeSjiTbZDbVZvOKIz_YDoZzijNvfuc5QdIHczQPVAFqv3TMxDPZ_lGdUC3sg6d9iScaBgwVqU6uuoGppV7fAO-Q2ALN48is9-Exkr1o0JF2Yry9nebSxcSEDpBz39jDU0EMxOWCShGwG0CCaKLavOo0GzXzmZr1mpYaUZoBgxHSUdr3rRfhoqYMZOrYAYQeR8DMcAw7WR-C4PNKN9NyMwhzWDFv7mtoneP6dWAd22SNOLmQC64ahgJTFsN76brLl1Xl6HYR0wqjd4LBJeeV4uAumdVWkiIgo", "state"=>"0c40e8bcabffe59ec093223f78250db73c9a3d9b7717be97"}
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."provider" = $1 AND "users"."uid" = $2 ORDER BY "users"."id" ASC LIMIT 1 [["provider", "facebook"], ["uid", "10153518057141280"]]
(0.2ms) BEGIN
(0.2ms) ROLLBACK
BEFORE IF STATEMENT
#<User id: nil, email: nil, encrypted_password: "$2a$10$6Ls8Eng6pNFz11sV/AmOuuUdq8JRas33H8UnWAX0YyA...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, created_at: nil, updated_at: nil, provider: "facebook", uid: "10153518057141280">
AFTER ELSE STATEMENT
Redirected to http://localhost:3000/users/sign_up
Completed 302 Found in 93ms (ActiveRecord: 1.1ms)
Started GET "/users/sign_up" for ::1 at 2015-09-01 20:41:02 +0100
Processing by Devise::RegistrationsController#new as HTML
Rendered /Users/makerslaptop91/.rvm/gems/ruby-2.2.0/gems/devise-3.5.1/app/views/devise/shared/_links.html.erb (1.0ms)
Rendered /Users/makerslaptop91/.rvm/gems/ruby-2.2.0/gems/devise-3.5.1/app/views/devise/registrations/new.html.erb within layouts/application (6.9ms)
Completed 200 OK in 55ms (Views: 54.3ms | ActiveRecord: 0.0ms)
Notice the puts inside the 'if' statement was never printed.
routes.rb:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
resources :restaurants do
resources :reviews
end
root to: "restaurants#index"
I'm stumped.
If you remove the :validatable option then it will work.

Redirected to http://localhost:3000/session/new

So I have a rails app 2.x app that works fine via the web, but when trying to perform a POST I keep getting "Redirected to http://localhost:3000/session/new Filter chain halted as [:require_user] rendered_or_redirected.". In my iPhone app, I can create a new session and sign-in via my iPhone app, but cannot POST to say the POSTS_Controller.
I have this in my code
Posts_Controller
before_filter :require_user, :only => [:create, :update, :destroy]
Application_Controller
# Filters added to this controller apply to all controllers in the application.
# Likewise, all the methods added will be available for all controllers.
class ApplicationController < ActionController::Base
include AuthenticatedSystem
include Geokit::Geocoders
helper :all # include all helpers, all the time
#session :session_key => '_cwa_session_id'
#filter_parameter_logging :password
# See ActionController::RequestForgeryProtection for details
# Uncomment the :secret if you're not using the cookie session store
protect_from_forgery # :secret => 'eejj7eded74769099999944a729b4f'
#filter_parameter_logging(:password)
before_filter :login_from_cookie
before_filter :find_user_interests
before_filter :find_user_posts
protected
def find_user_interests
#user_interests = cur_user ? cur_user.interesting_posts : []
logger.debug "User interests hash: #{current_user.inspect}"
end
def find_user_posts
#user_posts = cur_user ? cur_user.posts : []
end
def cur_user
User.find(session[:user_id]) if session[:user_id]
end
def require_user
unless cur_user
flash[:error] = "You must be logged in to do that."
redirect_to '/session/new'
return false
end
end
geocode_ip_address
def geokit
#location = session[:geo_location]
end
end
I have been working on this for 2 months and cannot figure out the issue. In my iPhone app I am using ObjectiveResource. I am sending over json and have "Mime::Type.register_alias "application/json", :json" set up on the rails side.
I am not a rails developer, but the before filter for require_user is unable to pass the cur_user test in that is cannot find :user_id in the session hash. Are you sure that you have a session that persists when using the iPhone? Are you using devise for authentication? Just for kicks, does it work if you manually pass the user_id as params?