0
votes

I'm trying to integrate Facebook login into my website.

I've built the model like this so far (I can add a better email validator):

class FBUser < ActiveRecord::Base

    attr_accessor :access_token, :expires_in, :reauthorize_in
    attr_reader :email, :fb_id

    validates :fb_id, :email, :access_token, allow_blank: false, uniqueness: true
    validates :access_token, :expires_in, :reauthorize_in, presence: true

end

When a user logs in with Facebook, it passes the returned parameters to this in-development controller function:

def fb_login
   params[:email] ||= params[:userID][-7...-1] + "@facebook.com"
   fb_user = FBUser.create({
   fb_id: params[:userID],
   email: params[:email],
   access_token: params[:accessToken],
   expires_in: params[:expiresIn],
   reauthorize_in: params[:reauthorize_required_in]
  })
  sign_in!(fb_user)
  render json: fb_user
end

All of those params are present but when it does the validations during the create it passes NULL for the email and fb_id. From the Rails output:

FBUser Exists (0.4ms) SELECT 1 AS one FROM "fb_users" WHERE "fb_users"."fb_id" IS NULL LIMIT 1

FBUser Exists (0.2ms) SELECT 1 AS one FROM "fb_users" WHERE "fb_users"."email" IS NULL LIMIT 1

FBUser Exists (0.4ms) SELECT 1 AS one FROM "fb_users" WHERE "fb_users"."access_token" = 'EAAEDVcUvjT4BACT8es...

SQL (0.2ms) INSERT INTO "fb_users" ("fb_id", "email", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["fb_id", "101564XXXXXXX688"], ["email", "[email protected]"], ["created_at", "2018-08-17 02:42:20.440539"], ["updated_at", "2018-08-17 02:42:20.440539"]]

I know it is the uniqueness validation because if I remove it those queries go away. As it is, the uniqueness validation is not doing anything. I don't know why the access token is not being passed to the INSERT statement at all, but this is a secondary issue.

I've passed the "param[]" parameters into a variable and that did nothing. I'm stumped as to why it is passing in NULL for the uniqueness check instead of the values for email and fb_id, but not the access token. It passes the values in the INSERT being called from the same create method right afterwards!

I was creating multiple FBUsers with the same fb_id and email, but the table was created as:

create_table :fb_users do |t|
      t.string :fb_id, null: false, unique: true
      t.string :email, unique: true
      t.string :access_token ...

More strangeness...

1
Can you debug and paste the params you're getting back before the FBUser.create - Kedarnag Mukanahallipatna
They're included in the INSERT INTO statement, they are the same beforehand. - MBudnick
Can you please edit the above question with your additional information. - Kedarnag Mukanahallipatna
But your INSERT INTO does not have the column access_token or any of the other columns after that, it just shows email and fb_id - Kedarnag Mukanahallipatna
The necessary params that are being used in the create method are present, we do not need to investigate that. - MBudnick

1 Answers

1
votes

ActiveRecord attributes (i.e. columns that are stored in the database) are not the same as instance variables. You have this:

attr_reader :email, :fb_id

in your model. That's just shorthand for:

def email
  @email
end

def fb_id
  @fb_id
end

so whenever someone says fbuser.email or fbuser.fb_id, you'll be looking at the instance variables. But, when you say:

FBUser.create({
  fb_id: params[:userID],
  email: params[:email],
  ...
})

ActiveRecord will call the fb_id= and email= methods store those two values in the model instance. ActiveRecord creates those mutator methods for you and they will update attributes, not instance variables. Normally AR would also create fb_id and email methods to read those attributes but you've already created those methods with your attr_reader call.

The result is that fbuser.email will read from @email but fbuser.email = something will write to something else and so fbuser.email will always give you nil. Similarly for fbuser.fb_id.

You probably want to get rid of the attr_reader and attr_accessor calls completely and make your model look like this:

class FBUser < ActiveRecord::Base
  validates :fb_id, :email, :access_token, allow_blank: false, uniqueness: true
  validates :access_token, :expires_in, :reauthorize_in, presence: true
end