Skip to content

andrewjh271/odin_facebook

Repository files navigation

Social Scrolls

A social media app inspired by Facebook. Created as part of the Odin Project curriculum. View live page.

OmniAuth is implemented for Facebook and Github. Active Storage is used for avatar and photo uploads, using cloud storage on Cloudinary. Action Mailer is used with the default Mail gem.

Test suite covers Models, Controllers, Mailers, and Integration Testing with Capybara.

Thoughts

Reciprocal Friendships

It was challenging to implement friendships since a user could be on either side of the friendship association (friend_a or friend_b). I found a number of possible solutions — possibly the simplest was to just duplicate the association in reverse, but I didn't like the idea of doubling all that data. What I came up with and liked best was to rely more on SQL rather than Active Record queries:

def friends
	join_statement = <<-SQL
  	INNER JOIN friendships
    	ON (friendships.friend_a_id = users.id OR friendships.friend_b_id = users.id)
    	AND (friendships.friend_a_id = #{id} OR friendships.friend_b_id = #{id})
		SQL
   User.joins(join_statement).where.not(id: id)
end

I only write the join statment in SQL so that I can incorporate it into an Active Record query and ultimately still return an ActiveRecord::Relation. I used similar strategies to create methods that for a given user returned other users who were not friends, or who had no relationship (no friendship or friend_request association).

Active Storage

I decided to use Cloudinary rather than AWS for cloud storage because I wanted to avoid dealing with the AWS S3 1 year free trial period ending. Configuring Active Storage with Cloudinary was seamless, and after testing in development and fairly generous seeding I am still only at 5% the monthly allotment.

Some helpful links:

  • This article gives a great overview of using Active Storage. Particularly useful was the mention of the with_attached scope for preloading.

  • This Stack Overflow post describes the basic setup for saving an image from a URL.

  • This article and this pull request explain an improvement with Rails 6.1 involving tracking variants in the database.

  • This page in the docs clarify what happens when attaching persisted vs unpersisted records.

Attaching a file by letting the user upload one in a form worked immediately, but I encountered some difficulties attaching an image from a file or URL. This block in User::from_omniauth, for instance, would throw an IOError: closed stream because the io file needs to still be open at the end of the first_or_create block (and the File.open block closes the file):

where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
  File.open(URI.open(auth.info.image)) do |io|
    user.avatar.attach(io: io, filename: 'image-download')
  end
end

I originally used a slightly verbose workaround that worked while still closing the file, but this was no longer possible when I needed to switch to first_or_initialize and not save a new record until back in the Users::OmniauthCallbacksController. Many examples on Stack Overflow simply open the file without closing it, and that seems to work ok so is my current solution. I am slightly uneasy about it, but at least the docs say "I/O streams are automatically closed when they are claimed by the garbage collector."

I encountered this error in other places as well (attaching default avatars, testing...). This comment from a Rails Issue briefly explains what's going on.

I wanted to be mindful when I created variants of images vs just using CSS to resize them, since the number of transactions on Cloudinary counts against the free allotment. I only create variants for the avatar thumbnails, since many of those can appear on a page at once and the server should not be retrieving full sized images for each one. I experimented with using Cloudinary's cl_image_tag to create the image variants, but ended up using Active Storage's variant, which uses the image-processing gem.

OmniAuth

Facebook requires some extra work to allow OmniAuth to work in Live Mode, including having a privacy policy, instructions for data deletion, and Valid OAuth Redirect URIs listed. It's a little deceptive that everything works perfectly for me in Development Mode since my account is connected to the app, but wouldn't work for anyone else.

User::new_with_session as recommended on the Devise/OmniAuth page allows the data retrieved from an unsuccessful OAuth attempt to be prefilled in the rerendered form.

Some helpful links:

  • Stack Overflow post with helpful advice for getting Facebook OmniAuth to work (particularly secure_image_url: true)
  • Pull request that also discusses secure_image_url: true to avoid a 500 Internal Service Error.
  • Stack Overflow post that discusses why Facebook appends #_=_ to the redirect_uri.
Mail

I briefly tried to use SendGrid again for this project — I got further than I did when I spent much more time on it for my Flight Booker project, but when I tried to setup Single Sender Verification (the first step they recommended after I logged in), it shut me out of the account and said I was unauthorized to access SendGrid without telling me why.

Instead I am just using the default Mail gem, configured for Yahoo Mail.

Javascript

I really felt the lack of Javascript in this project — it was onerous having to rerender pages for small things like liking a post or displaying a form to reply to a particular comment.

Configuring Devise Controllers

I wanted the :ensure_avatar, :create_friend_invitations, :send_welcome_email callbacks to be attached to the controller and not the model, so I followed the steps in the Devise instructions to customize them. It looked daunting but ended up being fairly straightforward.

-Andrew Hayhurst