I'm currently learning Ruby on Rails, as a 2014 resolution and I'm really pleased with my progress so far under the Bloc Curriculum. I will write more about the general learning experience, but this post is somewhat specific.

In my first full demo app, which I've lovingly branded Jello.io - a reddit clone complete with users, topics, posts, user roles & capabilities, voting, favouriting, etc. etc. The User Authentication part is handled by a popular gem, Devise.

Whilst Devise is excellent at a lot of things, it is also quite problematic in a number of ways, most of which I have not yet experienced. I look forward to rolling my own Auth functionality in future projects.

I have added the Carrierwave gem with the MiniMagic gem to support a User avatar upload feature. This all works very nicely using the internal system.

But, then I added Facebook connect for user signups, via the Omniauth gem and wanted grab the Facebook User Profile Image from the Facebook hash.

I tried a few options, overly complicated as usual, including:

Original method without profile image for Facebook in user.rb

...

mount_uploader :avatar, AvatarUploader
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
pass = Devise.friendly_token[0,20]
user = User.new(name: auth.extra.raw_info.name,
 provider: auth.provider,
 uid: auth.uid,
 email: auth.info.email,
 password: pass,
 password_confirmation: pass
)
user.skip_confirmation!
user.save
end
user
end

...

Added Avatar to the User.new arguments. Didn't work.

...

mount_uploader :avatar, AvatarUploader
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
pass = Devise.friendly_token[0,20]
user = User.new(name: auth.extra.raw_info.name,
 provider: auth.provider,
 uid: auth.uid,
 email: auth.info.email,
 avatar: auth.info.image,
 password: pass,
 password_confirmation: pass
)
user.skip_confirmation!
user.save
end
user
end

...

Then changed from using the method magic (which may or may not always work) to retrieve the image from the Facebook Hash, to explicitly selecting from the Hash which is much more reliable, so I'm told...

...

mount_uploader :avatar, AvatarUploader
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
pass = Devise.friendly_token[0,20]
user = User.new(name: auth.extra.raw_info.name,
 provider: auth.provider,
 uid: auth.uid,
 email: auth.info.email,
 avatar: auth[:info][:image],
 password: pass,
 password_confirmation: pass
)
user.skip_confirmation!
user.save
end
user
end

...

Then, realising that this is just passing us an image url, which still isn't quite sufficient, we need to open the url first before uploading. So trying:

...

mount_uploader :avatar, AvatarUploader
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
pass = Devise.friendly_token[0,20]
user = User.new(name: auth.extra.raw_info.name,
 provider: auth.provider,
 uid: auth.uid,
 email: auth.info.email,
 avatar: open(auth[:info][:image]),
 password: pass,
 password_confirmation: pass
)
user.skip_confirmation!
user.save
end
user
end

...

But, that's a bit of a dirty hack, and nonetheless, also didn't work. So, when all else fails, the lesson here is Go Back to the Documentation.

With a little more searching, the solution was actually very straightforward. By providing the remote_avatar_url key, which has some baked in magic for unpacking the url ready for your app to display the image.

...

mount_uploader :avatar, AvatarUploader
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
pass = Devise.friendly_token[0,20]
user = User.new(name: auth.extra.raw_info.name,
 provider: auth.provider,
 uid: auth.uid,
 email: auth.info.email,
 remote_avatar_url: auth[:info][:image],
 password: pass,
 password_confirmation: pass
)
user.skip_confirmation!
user.save
end
user
end

...

Phew.