2

I am trying desperately to find a way to get devise to work in my Rails 4 app. I have tried every tutorial I can find to get this set up.

The current tutorial is:http://sourcey.com/rails-4-omniauth-using-devise-with-twitter-facebook-and-linkedin/

My previous bounty questions on related problems (commonly voted down & I don't understand why), show my other attempts at getting this done.

My problem currently is with this bit of text in the user model:

def self.find_for_oauth(auth, signed_in_resource = nil)

    # Get the identity and user if they exist
    identity = Identity.find_for_oauth(auth)

    # If a signed_in_resource is provided it always overrides the existing user
    # to prevent the identity being locked with accidentally created accounts.
    # Note that this may leave zombie accounts (with no associated identity) which
    # can be cleaned up at a later date.
    user = signed_in_resource ? signed_in_resource : identity.user

    # Create the user if needed
    if user.nil?

      # Get the existing user by email if the provider gives us a verified email.
      # If no verified email was provided we assign a temporary email and ask the
      # user to verify it on the next step via UsersController.finish_signup
      email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
      email = auth.info.email if email_is_verified
      user = User.where(:email => email).first if email

      # Create the user if it's a new registration
      if user.nil?
        user = User.new(
          name: auth.extra.raw_info.name,
          #username: auth.info.nickname || auth.uid,
          email: email ? email : "#{auth.uid}-#{auth.provider}.com",
          password: Devise.friendly_token[0,20]
        )
        user.skip_confirmation!
        user.save!
      end
    end

The problem is (pointing at this line: user = User.new) in the above method:

unknown attribute 'name' for User.

In the previous tutorial I tried, I discovered that linkedin worked when i changed the attributes to:

# self.email = omniauth['extra']['raw_info']['emailAddress'] 
 #      self.first_name = omniauth['extra']['raw_info']['firstName']
    #     self.last_name = omniauth['extra']['raw_info']['lastName']

The field names for the omniauth strategy are different to what's shown in the tutorial (at least for linkedin). But then if Facebook or twitter use different field names again, how does the tutorial solve for that?

Also, in my user table, I have attributes called first_name and last_name, but I tried changing the line to:

first_name: auth.extra.raw_info.'firstName',

That didn't work either.

When I try:

first_name: auth.extra.raw_info.name

I get this error:

undefined method `password_required?' for #<User:0x007fb7bc2ce6f8>

But anyway, I only want first name in that field and i think this is putting the whole name into first name (although Im not sure about that). Also, if this is going to be amended to work for linkedin, will that mean it will not work for Facebook and twitter?

It's all a big mess. I'm growing increasingly frustrated with this. Does anyone know how to solve this particular problem. I have been trying for 2.5 years to get devise/omniauth working.

My recent previous tutorial linked questions are:

https://stackoverflow.com/questions/33888972/rails-devise-omniauth-strategies-omniauthcallbackscontroller

Devise Omniauth - setup & defining strategies

Rails, Devise & Omniauth - problems with setup

There are several others, but I'm not figuring this out by my own efforts. I've had a few sessions on codementor.io but not been able to find help. A source of help would be greatly appreciated.

So trying the suggestion below, I tried changing the method in the user model to:

def self.find_for_oauth(auth, signed_in_resource = nil)

    # Get the identity and user if they exist
    identity = Identity.find_for_oauth(auth)

    # If a signed_in_resource is provided it always overrides the existing user
    # to prevent the identity being locked with accidentally created accounts.
    # Note that this may leave zombie accounts (with no associated identity) which
    # can be cleaned up at a later date.
    user = signed_in_resource ? signed_in_resource : identity.user

    # Create the user if needed
    if user.nil?

      # Get the existing user by email if the provider gives us a verified email.
      # If no verified email was provided we assign a temporary email and ask the
      # user to verify it on the next step via UsersController.finish_signup
      email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
      email = auth.info.email if email_is_verified
      user = User.where(:email => email).first if email

      # Create the user if it's a new registration
      if user.nil?
        user = User.new(
          case auth.provider
            when 'linkedin'
              first_name: auth.extra.raw_info.firstName,
              last_name: auth.extra.raw_info.lastName,
              email: emailAddress ? email : "#{auth.uid}-#{auth.provider}.com",
              #username: auth.info.nickname || auth.uid,
              # email: email ? email : "#{auth.uid}-#{auth.provider}.com",
              password: Devise.friendly_token[0,20]
            when 'facebook'
              first_name: auth.extra.raw_info.first_name,
              last_name: auth.extra.raw_info.last_name,
              email: auth.extra.raw_info.email ? email : "#{auth.uid}-#{auth.provider}.com",
              password: Devise.friendly_token[0,20]

            when 'twitter' 
            first_name: auth.extra.raw_info.nickname, 
          end
        )
        user.skip_confirmation!
        user.save!
      end
    end

There are several problems with this, being:

unexpected ':', expecting keyword_end first_name: auth.extra.raw_info.firstName,
unexpected tLABEL, expecting '=' last_name: auth.extra.raw_info.lastName
unexpected tLABEL, expecting '=' email: emailAddress ? email : "#{au... ^
unexpected ',', expecting keyword_end
unexpected keyword_when, expecting keyword_end when 'facebook' ^
unexpected ':', expecting keyword_end first_name: auth.extra.raw_info.first_name, ^ 
unexpected tLABEL, expecting '=' last_name: auth.extra.raw_info.last_name,
unexpected tLABEL, expecting '=' email: auth.extra.raw_info.email ? ... ^
unexpected ',', expecting keyword_end
unexpected keyword_when, expecting keyword_end when 'twitter' ^ 
unexpected ':', expecting keyword_end first_name: auth.extra.raw_info.nickname, ^
unexpected keyword_end, expecting '='

I don't know how to solve any of this - I can't find any example (other than the one in the post below) of how to approach this - and that's clearly not working).

On top of all of the above, what do I do with Twitter? It has a nickname field. How can I separate the words out so the first word is saved as first_name and the 2nd word is saved as last_name?

Extrapolating from the particularly snarky comments below, I tried that suggestion again, with if statements. It still doesnt work.

def self.find_for_oauth(auth, signed_in_resource = nil)

    # Get the identity and user if they exist
    identity = Identity.find_for_oauth(auth)

    # If a signed_in_resource is provided it always overrides the existing user
    # to prevent the identity being locked with accidentally created accounts.
    # Note that this may leave zombie accounts (with no associated identity) which
    # can be cleaned up at a later date.
    user = signed_in_resource ? signed_in_resource : identity.user

    # Create the user if needed
    if user.nil?

      # Get the existing user by email if the provider gives us a verified email.
      # If no verified email was provided we assign a temporary email and ask the
      # user to verify it on the next step via UsersController.finish_signup
      email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
      email = auth.info.email if email_is_verified
      user = User.where(:email => email).first if email

      # Create the user if it's a new registration
      if user.nil?
        user = User.new(
          if auth.provider = linkedin
              first_name: auth.extra.raw_info.firstName,
              last_name: auth.extra.raw_info.lastName,
              email: emailAddress ? email : "#{auth.uid}-#{auth.provider}.com",
              #username: auth.info.nickname || auth.uid,
              # email: email ? email : "#{auth.uid}-#{auth.provider}.com",
              password: Devise.friendly_token[0,20]
          end

          if auth.provider = facebook    
              first_name: auth.extra.raw_info.first_name,
              last_name: auth.extra.raw_info.last_name,
              email: auth.extra.raw_info.email ? email : "#{auth.uid}-#{auth.provider}.com",
              password: Devise.friendly_token[0,20]

          end

          if auth.provider = twitter
            first_name: auth.extra.raw_info.firstName, 
          end
        )
        user.skip_confirmation!
        user.save!
      end
    end

Absent finding a solution to this on this board, I would appreciate advice on how much you think it would be reasonable to pay a professional to help resolve these problems.

I HAVE GONE RIGHT BACK AND COMMENTED OUT ALL OF THE CODE RELATED TO DEVISE AND OMNIAUTH AND NOW TRYING AGAIN, WITH THE DOC ON THE OMNIAUTH WIKI CALLED: MANAGING MULTIPLE PROVIDERS

It seems this doc may have typos in it that experienced coders can read past.

Currently, there is an error message being generated as follows:

/Users/config/routes.rb:35: syntax error, unexpected [, expecting keyword_do or '{' or '(' ...', to: 'sessions#create', via [:get, :post] ... ^ /Users/config/routes.rb:36: syntax error, unexpected [, expecting keyword_do or '{' or '(' match '/logout', to: 'sessions#destroy', via [:get, :post] ^

I have copied these routes directly from the user guide. I'm by far from an expert but I'm also confused about why '==' is used in some places and '=' is used in others in this doc. For example:

if signed_in?
            if @identity.user == current_user

Whilst:

@identity.user = current_user

In the same method there is a variance.

I'm also confused about why the sessions controller doesnt inherit from the devise controller. In each of the other tutorials I have done that have had a sessions controller, it has inherited from devise.

There are quite a few other confusing aspects of this tutorial (like why doesnt it have a registrations controller, why are there no other routes for devise, why does the application controller have create and destroy methods)?

Desperately seeking help.

Community
  • 1
  • 1
Mel
  • 2,481
  • 26
  • 113
  • 273
  • `unknown attribute 'name' for User.` I guess you dont have the attribute name on your User model? So how about not changing the whole line `name: auth.extra.raw_info.name` to `first_name: auth.extra.raw_info.'firstName'`. Try first of all `first_name: auth.extra.raw_info.name` – Denny Mueller Dec 07 '15 at 10:17

2 Answers2

0

This is not a complete answer, but I have solved part of my problem.

The point of the oauth gem is to take the attributes from each social media strategy and unify them into a common form of expression.

By incorporating raw_info from the strategy, the code is not working with oauth. For example, linkedin auth hash returns data labelled 'firstName' where oauth recognises that as first_name. If you use

first_name: auth.extra.raw_info.first_name,

the attribute will be nil when linked is being called. This is already strange to me since LinkedIn gives basic profile details which suggest a label is first-name.

Anyway, the part I fixed is to remove reference to 'extra.raw' in the above line and use auth.info.first_name. That should resolve differences between strategy labels.

I am still working on the rest of the problems arising in this set up. Some of the issues in the sourcey tutorial are syntax, others are more substantial. I'll post again if I can sort them out.

Mel
  • 2,481
  • 26
  • 113
  • 273
-1
unknown attribute 'name' for User.

This means that there's no name column in users table. You need to create this column or use some other column that you have (like first_name or username). Like so:

username: auth.extra.raw_info.name

But then if Facebook or twitter use different field names again, how does the tutorial solve for that?

Yeah, this tutorial gives only one variant, but others are not so difficult to find out. You just need to know, what fields returns this or that provider (twitter, facebook or linkedin) and do something like this:

def self.find_for_oauth(auth, signed_in_resource = nil)
  ...
  # Create the user if needed
  if user.nil?
    case auth.provider
      when 'facebook'
        # Take fields that Facebook provides and use them when creating a new user
      when 'twitter'
        # Take fields that Twitter provides and use them when creating a new user
      when 'linkedin'
        # Take fields that Linkedin provides and use them when creating a new user
    end
  end
  ...
end

It will be better for code organization if you implement separate methods for each provider and call them in certain when.

Also check this out https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema to understand what's inside env['omniauth.auth'].

Jeiwan
  • 954
  • 6
  • 13
  • hi Jeiwan, I have first_name and last_name, but I can't figure how to make that work with this tutorial – Mel Dec 07 '15 at 10:32
  • Then just use `first_name` when creating a user, like so: `first_name: auth.extra.raw_info.name` But I think it's better to create column `name`, because you don't know what's in `auth.extra.raw_info.name`, it can contain full names as well, and if you store full name in `first_name` column it's not good. The main problem here is to correctly create all columns in `users` table, because different providers give different info. – Jeiwan Dec 07 '15 at 10:58
  • But won't name put the whole name into the field i call 'first name" also, the linkedin field I found is called 'firstName', but that doesnt work – Mel Dec 08 '15 at 00:13
  • If you use this code `first_name: auth.extra.raw_info.name`, then, yes, you'll have full name in first_name column. For linkedin you should try something like `first_name: auth.extra.raw_info.firstName` (no need for quotes around firstName) – Jeiwan Dec 08 '15 at 00:28
  • It's so strange to me that the tutorial I am trying to follow is specifically for omniauth with multiple strategies, but it doesn't have a way to solve this. I will try your suggestion for 'when' options. Thanks – Mel Dec 08 '15 at 01:04
  • I tried this - I've pasted my attempt and the errors above. Do you have any other ideas? – Mel Dec 08 '15 at 02:09
  • You have syntax errors in your code. You can't insert `case...when` in `User.new` just like that. I'm sorry, but in this case I can only recommend you to spend more time learning and practicing Ruby. It will be much easier for you then. Good luck! – Jeiwan Dec 08 '15 at 03:10
  • Any tips on where to learn ruby relevant to the case suggestion above? – Mel Dec 08 '15 at 03:17
  • Actually, there's no specific topic, just general programming. You need to understand, that `new` method instantiates a new User object, and that it accepts a hash as arguments. So you can't put `case...when` statement in it. Maybe this task is too difficult for you and you should begin with something easier and go step by step all the way up. – Jeiwan Dec 08 '15 at 03:41
  • I put case when in it because your suggestion set that out.I have been trying to learn for 3 years. I don't know what you mean by your suggestion if not to use it as you had set out. Anyway, I will get rid of it because it plainly doesnt work. – Mel Dec 08 '15 at 03:43
  • I'm not going to solve the problem for you, instead I just give you hints how to solve it. It's up to you whether you use them or not. Good luck. – Jeiwan Dec 08 '15 at 03:50
  • Thanks - 'learn ruby' is a bit of a general hint. Anyway - if others are similarly stuck, the above solution doesnt' work. – Mel Dec 08 '15 at 03:54