{"id":22654442,"url":"https://github.com/andrewjh271/odin_facebook","last_synced_at":"2025-04-09T04:37:38.641Z","repository":{"id":139233721,"uuid":"318964035","full_name":"andrewjh271/odin_facebook","owner":"andrewjh271","description":null,"archived":false,"fork":false,"pushed_at":"2024-06-12T19:33:12.000Z","size":34054,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-14T22:49:01.844Z","etag":null,"topics":["rails","ruby","ruby-on-rails","social-media","social-network"],"latest_commit_sha":null,"homepage":"https://socialscrolls.herokuapp.com/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andrewjh271.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-12-06T06:05:05.000Z","updated_at":"2024-06-12T19:33:16.000Z","dependencies_parsed_at":null,"dependency_job_id":"12c943f8-5095-431e-94fa-b26ca1e6e842","html_url":"https://github.com/andrewjh271/odin_facebook","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewjh271%2Fodin_facebook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewjh271%2Fodin_facebook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewjh271%2Fodin_facebook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrewjh271%2Fodin_facebook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andrewjh271","download_url":"https://codeload.github.com/andrewjh271/odin_facebook/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247980830,"owners_count":21027803,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rails","ruby","ruby-on-rails","social-media","social-network"],"created_at":"2024-12-09T09:50:31.371Z","updated_at":"2025-04-09T04:37:38.530Z","avatar_url":"https://github.com/andrewjh271.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Social Scrolls\n\nA social media app inspired by Facebook. Created as part of the Odin Project [curriculum](https://www.theodinproject.com/courses/ruby-on-rails/lessons/final-project). View [live page](https://socialscrolls.herokuapp.com/).\n\nOmniAuth is implemented for Facebook and Github. Active Storage is used for avatar and photo uploads, using cloud storage on [Cloudinary](https://cloudinary.com/). Action Mailer is used with the default [Mail](https://github.com/mikel/mail) gem.\n\nTest suite covers Models, Controllers, Mailers, and Integration Testing with Capybara.\n\n### Thoughts\n\n###### Reciprocal Friendships\n\nIt 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:\n\n```sql\ndef friends\n\tjoin_statement = \u003c\u003c-SQL\n  \tINNER JOIN friendships\n    \tON (friendships.friend_a_id = users.id OR friendships.friend_b_id = users.id)\n    \tAND (friendships.friend_a_id = #{id} OR friendships.friend_b_id = #{id})\n\t\tSQL\n   User.joins(join_statement).where.not(id: id)\nend\n```\n\nI 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).\n\n###### Active Storage\n\nI 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.\n\nSome helpful links:\n\n- This [article](https://blog.capsens.eu/how-to-use-activestorage-in-your-rails-5-2-application-cdf3a3ad8d7) gives a great overview of using Active Storage. Particularly useful was the mention of the `with_attached` scope for preloading.\n\n- This [Stack Overflow](https://stackoverflow.com/questions/51027995/how-to-save-an-image-from-a-url-with-rails-active-storage) post describes the basic setup for saving an image from a URL.\n- This [article](https://bigbinary.com/blog/rails-6-1-tracks-active-storage-variant-in-the-database) and this [pull request](https://github.com/rails/rails/pull/37901) explain an improvement with Rails 6.1 involving tracking variants in the database.\n- This page in the [docs](https://edgeapi.rubyonrails.org/classes/ActiveStorage/Attached/One.html#method-i-attach) clarify what happens when attaching persisted vs unpersisted records.\n\n\nAttaching 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):\n\n```ruby\nwhere(provider: auth.provider, uid: auth.uid).first_or_create do |user|\n  File.open(URI.open(auth.info.image)) do |io|\n    user.avatar.attach(io: io, filename: 'image-download')\n  end\nend\n```\n\nI 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](https://ruby-doc.org/core-2.7.1/IO.html#method-i-close) say \"I/O streams are automatically closed when they are claimed by the garbage collector.\"\n\nI encountered this error in other places as well (attaching default avatars, testing...). This comment from a [Rails Issue](https://github.com/rails/rails/issues/38185#issuecomment-572848893) briefly explains what's going on.\n\nI 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.\n\n###### OmniAuth\n\nFacebook 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.\n\n`User::new_with_session` as recommended on the Devise/OmniAuth [page](https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview) allows the data retrieved from an unsuccessful OAuth attempt to be prefilled in the rerendered form.\n\nSome helpful links:\n\n- [Stack Overflow](https://stackoverflow.com/questions/30748779/want-to-save-facebook-image-into-my-rails-app) post with helpful advice for getting Facebook OmniAuth to work (particularly `secure_image_url: true`)\n- [Pull request](https://github.com/simi/omniauth-facebook/issues/345) that also discusses `secure_image_url: true` to avoid a 500 Internal Service Error.\n- [Stack Overflow](https://stackoverflow.com/questions/7131909/facebook-callback-appends-to-return-url#:~:text=When%20the%20callback%20returns%20with,need%20not%20worry%20about%20it) post that discusses why Facebook appends `#_=_` to the redirect_uri.\n\n###### Mail\n\nI 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.\n\nInstead I am just using the default [Mail](https://github.com/mikel/mail) gem, configured for Yahoo Mail.\n\n###### Javascript\n\nI 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.\n\n###### Configuring Devise Controllers\n\nI 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](https://github.com/heartcombo/devise#configuring-controllers) to customize them. It looked daunting but ended up being fairly straightforward.\n\n-Andrew Hayhurst","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrewjh271%2Fodin_facebook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandrewjh271%2Fodin_facebook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrewjh271%2Fodin_facebook/lists"}