Ruby on Rails Facebook Implementation: SpyBook

In line with the Odin Mission: the objective of this workout is to construct one of the most baseline options present in one of the extra fashionable social media web page packages, Facebook.
Whilst following this newsletter we can be placing in combination one of the most core options of the platform — customers, profiles, “friending”, posts, feedback, information feed, and “liking”. We can additionally enforce a sign-in function by way of the use of the actual Facebook via the usage of gemstones equivalent to ‘OmniAuth and ‘Devise.

Entity Courting Diagram (ERD) And Affiliation Making plans

In our Facebook reproduction Ruby on Rails Mission we can want particular tables inside our database to carry positive desk relative data. So, with this in thoughts we should plan forward and take into consideration all of the data we can wish to retailer and get admission to.

Smartly, for nearly each and every piece of device in the market that has been created with the objective of interplay between a couple of customers in thoughts. There should be some form of database desk containing data referring to those customers. I.e. their names, emails, passwords, ids and with regards to another data wanted from the consumer to perform the device’s targets.

Since we’re making an attempt to construct an application containing the core options of Facebook, we can want a Customers Desk, Friendship Requests Desk, Posts Desk, Feedback Desk, Likes Desk and a Notifications Desk.

Customers Desk

Data we can need saved inside this desk might be column identification (integer kind), e-mail, first identify, remaining identify, password digest and a picture (in all probability to carry the identify of a profile image).

All of the columns for the consumer desk might be strings apart from for the principle key which is the column’s identification.

Friendship Requests Desk

Data we can need saved on this desk might be column identification (number one key, integer kind), sent_to_id (overseas key, integer), sent_by_id (overseas key, integer) and standing (Boolean).

The two overseas keys are just about self-explanatory, the sent_by_id comprises the ID of the Consumer sending the pal request and the sent_to_id comprises the ID of the Consumer the pal request used to be despatched to. Standing determines whether or not or now not the pal request used to be accredited.

Posts Desk

Data we can need saved on this desk might be column identification (number one key, integer), content material (textual content) and user_id (overseas key, integer).

The content material would be the column that holds the tips of the publish and the user_id will grasp the ID of the Consumer that created the publish.

Feedback Desk

Data we can need saved on this desk would be the column identification (number one key, integer), content material (textual content), post_id (overseas key, integer) and user_id (overseas key, integer).

The Feedback desk is very similar to the Posts desk with the addition of a post_id overseas key which issues to the ID of the Submit this remark is related to.

Likes Desk

Data we can need saved on this desk would be the column identification (number one key, integer), post_id (overseas key, integer) and comment_id (overseas identification, integer).

Very similar to each the Posts and Feedback desk the Likes desk will grasp references to both the ID of the Submit that used to be cherished or the Remark that used to be cherished.

Notifications Desk

Data we can need saved on this desk would be the column identification (number one key, integer), notice_id (integer) and kind (string).

The notice_id will grasp the ID of both a Submit, a Remark or a Good friend Request. The kind might be a string which holds the string illustration of which Desk the notice_id belongs to. As an example, a Remark’s ID may also be saved in notice_id and the string “remark” might be saved within the kind column.

This notifications style will actually have a connection with the consumer who’s going to be notified when any person likes one in their posts, feedback on one in their posts or sends them a pal request. This might be a somewhat elementary notification style which won’t give connection with the consumer who brought about the notification to be despatched.

After making plans out the tables and their associations we got here to conclude that:

Mission Setup

Growing a brand new Rails app the use of PostgreSQL

To mechanically have rails arrange the postgresql (pg) gem for us we can use the next line within the terminal to construct our new app:

$ rails new fakebook --database=postgresql 

Gem stones to Set up

All of the under gemstones will have to be added or already integrated on your Gemfile

# Use postgresql because the database for Lively File
gem 'pg', '>= 0.18', '< 2.0'
# Rubocop gem to proper linter similar problems retaining your code just about same old coding practices
gem 'rubocop'
# Devise safety gem
gem 'devise'
# To deal with pictures
gem 'carrierwave', '~> 2.0'
gem 'mini_magick'
# Use omniauth-facebook gem lets in Facebook login integration
gem 'omniauth-facebook'

Non-compulsory Gem stones

# Permits get admission to to twitter's Bootstrap framework
gem 'bootstrap'
# Hirb gem organizes the show for lively document data into tables when the use of the rails console… eg. After opening rails console kind Hirb.allow to turn on it
gem 'hirb'
# All gemstones under are associated with the RSpec Gem apart from the dotenv-rails gem
organization :building, :check do # RSpec Checking out gem 'database_cleaner' gem 'rspec-rails' # A Ruby gem to load surroundings variables from `.env` recordsdata. gem 'dotenv-rails'
finish
organization :check do gem 'capybara' gem 'selenium-webdriver'
finish

After including all of the important gemstones into the Gemfile (Usually discovered on the root listing). Run the package deal set up command into the terminal:

$ package deal set up 

or the shorthand ‘package deal’:

Putting in place PostgreSQL Database

First make sure that PostgreSQL is put in by way of working this apt command throughout the terminal:

$ apt-get -y set up postgresql postgresql-contrib libpq-dev 

In some circumstances, it’s possible you’ll wish to run this command with administrative authority the use of sudo:

$ sudo apt-get -y set up postgresql postgresql-contrib libpq-dev

When the set up is finished, login to the postgres consumer and get admission to the postgresql shell.

$ su — postgres $ psql

In some circumstances, it’s possible you’ll wish to run this command with administrative authority the use of sudo:

$ sudo su — postgres 

Subsequent, give the postgres consumer a brand new password with command under:

$ password postgres Input new password:

Subsequent, create a brand new function named ‘rails-dev’ with the password ‘aqwe123’ the use of the command under:

$ create function rails_dev with createdb login password ‘aqwe123’

You don’t need to create this function the use of ‘rails-dev’ or password ‘aqwe123’ however simply keep in mind what you used.

Now test to peer if the brand new function used to be effectively created:

Listing of all roles and their permissions in Postgres.

Getting Rails App to connect with PostgreSQL Database

Now it is very important edit your database.yml (

/config /database.yml/

) report to incorporate the function’s username and password you simply created.

Within the building phase, uncomment line 32 and sort the identify of the function we’ve created in previous ‘rails_dev’.

username: rails_dev

Set the password box on line 35 to the password you used for the ‘rails_dev’ function.

password: aqwe123

Uncomment line 40 and 44 for the database host configuration.

host: localhost
port: 5432

Now cross to the check phase and upload the brand new configuration under. The database would be the identify of your rails app adopted by way of an ‘_test’:

database: fakebook_test
 host: localhost
 port: 5432
 username: rails_dev
 password: aqwe123

After enhancing your database.yml report it will have to glance very similar to this, however the database is also other relying on what you named your rails app.

# PostgreSQL. Variations 9.3 and up are supported.
#
# Set up the pg motive force:
# gem set up pg
# On macOS with Homebrew:
# gem set up pg - - with-pg-config=/usr/native/bin/pg_config
# On macOS with MacPorts:
# gem set up pg - - with-pg-config=/decide/native/lib/postgresql84/bin/pg_config
# On Home windows:
# gem set up pg
# Select the win32 construct.
# Set up PostgreSQL and put its /bin listing on your trail.
#
# Configure The use of Gemfile
# gem 'pg'
#
default: &default
adapter: postgresql
encoding: unicode
# For main points on connection pooling, see Rails configuration information
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS")  5  %>
building:
<<: *default
database: fakebook_development # The desired database function getting used to connect with postgres.
# To create further roles in postgres see `$ createuser - assist`.
# When left clean, postgres will use the default function. 
# That is
# the similar identify because the working device consumer that initialized the database.
username: rails_dev # The password related to the postgres function (username).
password: aqwe123 # Attach on a TCP socket. Unnoticed by way of default because the consumer makes use of 
# a site socket that does not want configuration. Home windows does now not 
# have area sockets, so uncomment those traces.
host: localhost
# The TCP port the server listens on. Defaults to 5432.
# In case your server runs on a unique port quantity, alternate
# accordingly.
port: 5432 # Schema seek trail. The server defaults to $consumer,public
#schema_search_path: myapp,sharedapp,public
# Minimal log ranges, in expanding order:
# debug5, debug4, debug3, debug2, debug1,
# log, realize, caution, error, deadly, and panic
# Defaults to caution.
# min_messages: realize
# Caution: The database outlined as "check" might be erased and
# re-generated out of your building database while you run "rake".
# Don't set this db to the similar as building or manufacturing.
check: <<: *default
 database: fakebook_test
 host: localhost
 port: 5432
 username: rails_dev
 password: aqwe123
# As with config/credentials.yml, you by no means wish to retailer delicate # data,like your database password, on your supply code.
# In case your supply code is
# ever noticed by way of somebody, they now have get admission to in your database.
#
# As a substitute, give you the password as a unix surroundings variable when # you boot the app.
# Learn https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a complete rundown on how you can supply those surroundings variables
# in a manufacturing deployment.
#
# On Heroku and different platform suppliers, you could have a complete 
# connection URL to be had as an atmosphere variable. As an example:
#
# DATABASE_URL="postgres://myuser:[email protected]/somedatabase"
#
# You'll use this database configuration with:
#
# manufacturing:
# url: <%= ENV['DATABASE_URL'] %>
#
manufacturing: <<: *default
 database: fakebook_production
 username: rails_dev
 password: <%= ENV['FAKEBOOK_DATABASE_PASSWORD'] %>

Subsequent, generate the database with the rails command:

$ rails db:setup
Should you added the not obligatory gem ‘gem ‘dotenv-rails’’ (Documentation) you’ll be able to use a ‘.env’ report which you’d create on the root listing to retailer a ‘USERNAME’ and ‘PASSWORD’ worth. Inside the report named ‘.env’ you might put:
USERNAME = 'rails_dev'
PASSWORD = 'aqwe123'
Then within the database.yml(

/config /database.yml/

) report you might change all cases of the price of username and password:

username: rails_dev
password: aqwe123

can be modified to:

username: <%=ENV['USERNAME']%>
password: <%=ENV['PASSWORD']%>

Bootstrap (Non-compulsory)

Be sure that you’ve integrated the gem ‘bootstrap’ on your Gemfile.

Application.css to Application.scss

Search for the report ‘application.css’ (app/property/stylesheets/application.css) and alter the identify of the report from ‘application.css to ‘application.scss’ switching it to a Sass report.

Open the report and Erase traces 13 and 14

*= require_tree .
*= require_self

and upload on this new line to import Bootstrap CSS

// Customized bootstrap variables should be set or imported *earlier than* bootstrap.
@import "bootstrap";
@import "customized";

Import Bootstrap module and dependencies

Now it is very important upload a couple of recordsdata in your yarn report. Inside the terminal run this command:

$ yarn upload bootstrap jquery popper.js
Subsequent, make your manner into the ‘application.js’ report discovered within the listing (

app/javascript/packs/application.js

) and upload those traces in

import 'bootstrap';
import 'jquery';
import 'popper.js';

This will have to help you use all the newest bootstrap categories inside your rails perspectives.

The use of Devise Gem

After including all important gemstones and working package deal set up, it is very important set up devise. Within the terminal use the command:

$ rails generate devise:set up

At this level, various directions will seem within the console. Amongst those directions, you’ll wish to arrange the default URL choices for the Devise mailer in each and every surroundings.

Inside the ‘building.rb’ report (

config/environments/building.rb

) upload the next line:

config.action_mailer.default_url_options =  host: 'localhost', port: 3000 

Customers Style

After Devise has been correctly put in you’ll now use it to generate the Consumer style. The use of the equipped parameter on the rails generate command.

$ rails generate devise Consumer

Alternatively, since we need to additionally upload a First identify, Final identify and Symbol column to our Consumer style in keeping with the ERD equipped up above. We can upload a couple of extra parameters onto the terminal command.

$ rails generate devise Consumer fname:string lname:string symbol:string

We gained’t wish to upload e-mail as a parameter or password digest as a result of Devise handles this for us.

Afterwards we can migrate the database to Upload the newly created Customers Desk to the Database:

$ rails db:migrate
– Don’t disregard to run

$ rails db:migrate

after you create each and every style. Particularly those who might be referenced

Allowing further parameters with Devise

Since we’re the use of Devise to deal with the enroll and check in processes, we can have so as to add a couple of traces throughout the application controller to permit the retrieval of the fname, lname and symbol columns parameters from our long run paperwork for Consumer introduction. The alternate we can make might be very similar to this:

app/controllers/application.rb
1 magnificence ApplicationController < ActionController::Base
2 before_action :authenticate_user!
3 before_action :configure_permitted_parameters, if: devise_controller?
4 5 secure
6 7 def configure_permitted_parameters
8 devise_parameter_sanitizer.allow(:sign_up, keys: %i[fname lname image])
9 devise_parameter_sanitizer.allow(:account_update, keys: %i[fname lname image])
10 finish
11 finish

Understand that on traces 8 and 9, fname, lname and symbol, are added to the listing of symbols throughout the keys: parameter. Additionally consider that on line 2 we added the before_action way and ordered it to run the :authenticate_user! way. Since that is positioned throughout the application controller, this system is ran earlier than each and every controller motion, forcing customers to check in earlier than they try to do anything else.

Customers Controller

Following the overall Rails Style View Controller (MVC) style we can additionally generate a controller for our Consumer style.

$ rails generate controller Customers index display
The added parameters index and display reasons Rails to create two empty strategies throughout the generated Customers controller(app/controllers/users_controller) in addition to two similar perspectives (

app/perspectives/customers

) and routes (

config/routes.rb

) to those strategies.

Devise Perspectives

Now, since we added further columns onto the bottom Devise Consumer style (fnamelname and symbol), we can wish to upload the ones fields to the sign-up variety. Since Devise handles log ins and sign-u.s.we can want get admission to to the perspectives that Devise creates. To realize get admission to to those perspectives we can wish to run the next command within the terminal:

$ rails generate devise:perspectives
This command creates and provides all the perspectives that Devise makes use of to the venture folder. There’s a manner so as to add parameters to the serve as above to restrict the volume of perspectives created to steer clear of muddle, see devise documentation for that (Documentation).

Upload first identify and remaining identify fields to registration web page

<div magnificence="col-md-6 mx-auto mb-5"> <h1 magnificence="middle formidable">Enroll</h1> <%= form_for(useful resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= render "devise/shared/error_messages", useful resource: useful resource%> <div magnificence="form-group row"> <div magnificence="col"> <%= f.label :first_name %> <%= f.text_field :fname, magnificence:"variety-keep an eye on col", autofocus: true, autocomplete: "fname" %> </div> <div magnificence="col"> <%= f.label :last_name %><br /> <%= f.text_field :lname, magnificence:"variety-keep an eye on col", autofocus: true, autocomplete: "lname" %> </div> </div> <div magnificence="form-group"> <%= f.label :e-mail %><br /> <%= f.email_field :e-mail, magnificence:"variety-keep an eye on", autofocus: true, autocomplete: "e-mail" %> </div> <div magnificence="form-group"> <%= f.label :password %> <% if @minimum_password_length %> <em>(<%= @minimum_password_length %> characters minimal)</em> <% finish %><br /> <%= f.password_field :password, magnificence:"variety-keep an eye on", autocomplete: "new-password" %> </div> <div magnificence="form-group"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, magnificence:"variety-keep an eye on", autocomplete: "new-password" %> </div> <div magnificence="row"> <div magnificence="movements col-md-3"> <%= f.put up "Enroll", magnificence:"btn btn-secondary" %> </div> <div magnificence="col"> <%= render "devise/shared/hyperlinks" %> </div> </div> <% finish %>
</div>

Bootstrap categories are used within the code above however you’ll be able to use your individual CSS categories or the defaults discovered within the report. Now with this code we will have to have the addition of two new variety fields, first_name and last_name.

Including Symbol Uploads

Be sure that the ‘carrierwave’ and the ‘mini_magick’ gemstones are integrated on your Gemfile and are ‘package deal put in’.

Afterwards, run the command:

$ rails generate uploader Symbol
This will have to create a report ‘image_uploader.rb’ within the listing

app/uploaders/image_uploader.rb

Subsequent, you’re going to open this report and uncomment traces 4 and 38–40

Then you’ll upload this code on line 5:

procedure resize_to_limit: [400, 400]

This line of code merely resizes any symbol uploaded which has a better width or top than 400 pixels again to 400 pixels width and 400 pixels top. You might alternate this worth as you spot have compatibility.

Upload mount_uploader to Consumer

1 magnificence Consumer < ApplicationRecord
2 mount_uploader :symbol, ImageUploader
3 # Come with default devise modules. Others to be had are:
4 # :confirmable, :lockable, :timeoutable, :trackable and 
5 # :omniauthable
6 devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable,
7 :omniauthable, omniauth_providers: %i[facebook]
8 validate :picture_size
9 10 non-public
11 # Validates the scale of an uploaded image.
12 def picture_size
13 mistakes.upload(:symbol, 'will have to be lower than 1MB') if symbol.length > 1.megabytes
14 finish
15 finish

On line 2, the mount_uploader way is added from the carrierwave gem and the ‘ImageUploader’ parameter provided references the identify of the uploader you generated. We use ‘ImageUploader’ since we generated an uploader referred to as symbol (app/uploaders/image_uploader.rb).

On traces 12–14 we create a brand new non-public way referred to as ‘picture_size’ which returns an error if the picture uploaded is larger than 1 megabyte.

And on line 8 we upload the ‘picture_size’ way into the validation necessities for the Consumer style.

Upload symbol box to registration web page

app/perspectives/devise/registrations/new.html.erb
<!-- ...Does not display different code up above -->
<div magnificence="image form-group"> <%= f.label :upload_your_profile_picture %><br /> <%= f.file_field :symbol, magnificence:"variety-keep an eye on", settle for: 'symbol/jpeg,symbol/gif,symbol/png' %>
</div>
<div magnificence="row"> <div magnificence="movements col-md-3"> <%= f.put up "Enroll", magnificence:"btn btn-secondary" %> </div> <div magnificence="col"> <%= render "devise/shared/hyperlinks" %> </div>
</div>
<% finish %>
</div>

Following the similar layout we used previous we upload some other box to the registration web page variety permitting the importing of pictures.


<script kind="textual content/javascript">
file.getElementById('user_image').addEventListener('alternate', serve as()  var size_in_megabytes = this.recordsdata[0].length/1024/1024; if (size_in_megabytes > 1) 
);
</script>

The use of the script tag we upload some JavaScript to the top of the web page which presentations an alert error when the report uploaded is larger than 1 MB in length.

Posts Style

The use of:

$ rails generate style Submit content material:textual content consumer:references

We can generate a Submit style with two columns content material and user_id. The consumer:references parameter will mechanically upload the road ‘ belongs_to :consumer’ in:

app/fashions/publish.rb
magnificence Submit < ApplicationRecord belongs_to :consumer
finish

This line creates an affiliation between Posts and Customers. Which principally says a Submit can belong to a Consumer resulting in the inverse hyperlink of the affiliation the place a Consumer is authorized to have many posts. To complete growing this hyperlink we can now wish to cross into the Consumer style and upload this line in:

app/fashions/consumer.rb
magnificence Consumer < ApplicationRecord has_many :posts
finish

Posts Controller

The use of:

$ rails generate controller Posts index display new create
Simply as earlier than once we generated the Customers controller with further parameters empty strategies might be added into the controller report, perspectives might be created and routes to those strategies might be added into

config/routes.rb

Feedback Style

The use of:

$ rails generate style Remark content material:textual content publish:references consumer:references

This generates a Remark style with three columns content material, post_id and user_id. The consumer:references parameter will mechanically upload the traces ‘belongs_to :consumer’ and ‘ belongs_to :publish ’ in:

app/fashions/remark.rb

magnificence Remark < ApplicationRecord belongs_to :consumer belongs_to :publish
finish

This line creates an affiliation between Feedback and Customers in addition to Feedback and Posts. Which principally says a Remark can belong to one Consumer and one Submit resulting in the inverse hyperlink of the affiliation the place a Consumer is authorized to have many posts and a Submit is authorized to have many feedback. To complete growing this hyperlink we can now wish to cross into the Consumer style and upload this line in:

app/fashions/consumer.rb
magnificence Consumer < ApplicationRecord has_many :feedback
finish

in addition to this line within the Submit style:

app/fashions/publish.rb
magnificence Submit < ApplicationRecord belongs_to :consumer has_many :feedback
finish

We can generate a Feedback Controller the use of:

$ rails generate controller Feedback new create

Since we handiest want in an effort to create feedback and all feedback will handiest be view-able on the similar web page because the publish it used to be commented below.

Likes Style

The use of:

$ rails generate style Likes consumer:references publish:references remark:references

This generates a Like style with three columns user_id, post_id and comment_id. The parameters given will mechanically upload the traces ‘belongs_to :consumer’ and ‘belongs_to :publish’ and ‘belongs_to :remark’ in:

app/fashions/like.rb
magnificence Like < ApplicationRecord belongs_to :consumer belongs_to :publish belongs_to :remark
finish

We can additionally wish to upload some further values to the belongs_to way on publish and remark to permit null values for the two overseas keys.

To try this we can upload the ‘not obligatory: true’ parameter.

app/fashions/like.rb
magnificence Like < ApplicationRecord belongs_to :consumer belongs_to :publish, not obligatory: true belongs_to :remark, not obligatory: true
finish

This line creates an affiliation between Likes and Customers , Likes and Posts in addition to Likes and Feedback. Which principally says a Like can belong to one Consumer and one Submit or one Remark. 

Now, as we did prior to now we wish to upload the inverse of this affiliation to the fitting fashions:

Consumer style

app/fashions/consumer.rb
magnificence Consumer < ApplicationRecord has_many :likes, dependent: :damage
finish

The added parameter ‘dependent :damage‘ lets in us to delete information from the Likes desk with out SQL affiliation mistakes. 

If we intend on permitting feedback or posts to be deleted, we will additionally upload the ones parameters onto the has_many strategies referring to posts and feedback.

in addition to within the Submit style:

app/fashions/publish.rb
magnificence Submit < ApplicationRecord belongs_to :consumer has_many :feedback has_many :likes, dependent: :damage
finish

finally, the Remark style:

app/fashions/remark.rb
magnificence Remark < ApplicationRecord belongs_to :consumer belongs_to :publish has_many :likes, dependent: :damage
finish

Now, we wish to permit the publish and remark overseas key columns to be allowed to be null. To try this we edit the generated migration present in:

db/migrate/***_create_likes.rb
1 magnificence CreateLikes < ActiveRecord::Migration[6.0]
2 def alternate
3 create_table :likes do |t|
4 t.references :consumer, null: false, foreign_key: true
5 t.references :publish, null: true, foreign_key: true
6 t.references :remark, null: true, foreign_key: true
7
8 t.timestamps
9 finish
10 finish
11 finish

On Strains 5 and 6 we set the parameter null the place ‘null: false‘ to ‘null: true‘.

We can generate a Likes Controller the use of:

$ rails generate controller Likes create

Friendship Request Style

The use of:

$ rails generate style Friendship sent_to:references sent_by:references

Now earlier than we commence enhancing the Friendship style that used to be created, we can wish to upload some code to the newly generated Migration:

db/migrate/***_create_friendships.rb
magnificence CreateFriendships < ActiveRecord::Migration[6.0] def alternate create_table :friendships do |t| t.references :sent_by, null: false, foreign_key:  to_table: :customers  t.references :sent_to, null: false, foreign_key:  to_table: :customers  t.boolean :standing, default: false t.timestamps finish finish
finish

At first ‘foreign_key:’ would were set to ‘true’ however as a substitute we set it to ‘foreign_key: to_table: :customers ’ linking it to the customers desk.

Afterwards, we will run $ rails db:migrate , then we’re going to upload some further parameters into the Friendship.rb style created in addition to two scopes:

app/fashions/Friendship.rb
magnificence Friendship < ApplicationRecord belongs_to :sent_to, class_name: ‘Consumer’, foreign_key: ‘sent_to_id’ belongs_to :sent_by, class_name: ‘Consumer’, foreign_key: ‘sent_by_id’ scope :pals, ->  scope :not_friends, -> 
finish

The scopes created merely supply all information within the Friendship Desk the place the standing column is the same as true (for the chums scope) or false (not_friends scope).

The ‘belongs_to’ associative hyperlinks create two associations between Friendships and Customers. Sent_to and sent_by linking it to the columns ‘sent_to_id’ and ‘sent_by_id’ as overseas keys. To complete growing this hyperlink we can now wish to cross into the Consumer style and upload those traces in:

1 magnificence Consumer < ApplicationRecord
2 has_many :friend_sent, class_name: 'Friendship',
3 foreign_key: 'sent_by_id',
4 inverse_of: 'sent_by',
5 dependent: :damage
6 has_many :friend_request, class_name: 'Friendship',
7 foreign_key: 'sent_to_id',
8 inverse_of: 'sent_to',
9 dependent: :damage
10 has_many :pals, ->  merge(Friendship.pals) ,
11 via: :friend_sent, supply: :sent_to
12 has_many :pending_requests, ->  merge(Friendship.not_friends) ,
13 via: :friend_sent, supply: :sent_to
14 has_many :received_requests, ->  merge(Friendship.not_friends) ,
15 via: :friend_request, supply: :sent_by
16 finish
Strains 2–5 and 6–9 are the inverse associations created to hyperlink to the sent_to and sent_by associations made within the Friendship style (

app/fashions/friendship.rb

).

On traces 2–5 an affiliation named friend_sent is created, the class_name (The identify of the desk or style this affiliation is connected to) assigned is the Friendship Desk and the foreign_key that this affiliation is connected to throughout the Friendship Desk is ‘sent_by_id’.

Additionally it is set because the inverse_of:sent_by’, sent_by being the identify of the affiliation we created within the Friendship style on Line 3

app/fashions/Friendship.rb
3 belongs_to :sent_by, class_name: 'Consumer', foreign_key: 'sent_by_id'

which explicitly broadcasts bi-directional associations. The friend_sent affiliation additionally has the dependent: :damage parameter to permit information when it comes to the affiliation to be destroyed impartial of the associative hyperlink.

On traces 6–9 an affiliation named friend_request is created, the class_name assigned is the Friendship Desk and the foreign_key that this affiliation is connected to throughout the Friendship Desk is ‘sent_to_id’.

Additionally it is set because the inverse_of:sent_to’, sent_to being the identify of the affiliation we created within the Friendship style on Line 2:

app/fashions/Friendship.rb
2 belongs_to :sent_to, class_name: ‘Consumer’, foreign_key: ‘sent_to_id’

On line 10–11 an affiliation named pals is made, via the prior to now made affiliation friend_sent.

10 has_many :pals, ->  merge(Friendship.pals) ,
11 via: :friend_sent, supply: :sent_to

The next is the SQL generated from the code:

SELECT "customers".* FROM "customers" INNER JOIN "friendships"
ON "customers"."identification" = "friendships"."sent_to_id" WHERE "friendships"."sent_by_id" = $1 AND (standing =TRUE) LIMIT $2

This SQL observation SELECTS ALL columns from the Customers Desk WHERE the Consumer.identification is similar to the “sent_to_id” discovered within the Friendships Desk AND the standing column on that document must be TRUE (That means that the two customers are in a mutual friendship). 

The “sent_by_id” on that document might be similar to the Consumer.identification of the consumer of whom we need to to find out who all his pals are.

So, necessarily this observation returns to Rails all of the information from the Customers Desk the place the provided necessities are met. Information of all customers thought to be to be the present consumer’s pals.

On line 12–13 an affiliation named “pending_requests” is made, via the prior to now made affiliation “friend_sent”.

12 has_many :pending_requests, ->  merge(Friendship.not_friends) ,
13 via: :friend_sent, supply: :sent_to

The next is the SQL generated from the code:

SELECT "customers".* FROM "customers" INNER JOIN "friendships"
ON "customers"."identification" = "friendships"."sent_to_id"
WHERE "friendships"."sent_by_id" = $1 AND (standing =FALSE) LIMIT $2

This SQL observation SELECTS ALL columns from the Customers Desk WHERE the Consumer.identification is similar to the “sent_to_id” discovered within the Friendships Desk AND the standing column on that document must be FALSE (That means that the two customers don’t seem to be in a mutual friendship). 

The “sent_by_id” on that document might be similar to the Consumer.identification of the consumer of whom we need to know who all he despatched pal requests to.

Subsequently, this affiliation returns the customers that experience gained pal requests from the selected consumer.

On line 14–15 an affiliation named “received_requests” is made, via the prior to now made affiliation “friend_request”.

14 has_many :received_requests, ->  merge(Friendship.not_friends) ,
15 via: :friend_request, supply: :sent_by

The next is the SQL generated from the code:

SELECT "customers".* FROM "customers" INNER JOIN "friendships"
ON "customers"."identification" = "friendships"."sent_by_id"
WHERE "friendships"."sent_to_id" = $1 AND (standing =FALSE) LIMIT $2

This SQL observation SELECTS ALL columns from the Customers Desk WHERE the Consumer.identification is similar to the “sent_by_id” discovered within the Friendships Desk AND the standing column on that document must be FALSE (That means that the two customers don’t seem to be in a mutual friendship).

The “sent_to_id” on that document might be similar to the Consumer.identification of the consumer of whom we need to see who all despatched him pal requests.

So, this affiliation returns the customers that has despatched pal requests to a delegated consumer.

We can generate a Friendship Request Controller the use of:

The use of:

$ rails generate controller Friendships create

Notifications Style

The use of:

$ rails generate style Notification notice_id:integer notice_type:string consumer:references

This generates a Friendship style with 3 columns notice_id, notice_type and user_id

The notice_id would be the identification of both the publish that used to be written, remark that used to be added to one of the consumer’s posts or a friendship request that used to be despatched to the consumer. 

The notice_type assists in keeping a string worth document of whether or not the awareness made is a connection with both the Posts Desk, Feedback Desk or Friendship Desk

The consumer:references parameter will mechanically upload the road ‘belongs_to :consumer’ into (

app/fashions/notification.rb

).

Inside the newly generated Notification style we’re going to upload three scopes:

app/fashions/notification.rb
magnificence Notification < ApplicationRecord belongs_to :consumer scope :friend_requests, ->  scope :likes, ->  scope :feedback, ->  the place('notice_type = remark') 
finish

The scopes created will let us unmarried out notifications by way of kind, e.g. Likes, feedback or pal requests.

This line creates two associations between Notification and Customers. To complete growing this hyperlink we can now wish to cross into the Consumer style and upload:

app/fashions/consumer.rb
magnificence Consumer < ApplicationRecord has_many :notifications, dependent: :damage
finish

There is not any want for a Notifications controller, on the other hand, the principle strategies we can use to create notifications we can create within the application helper:

module ApplicationHelper  # Returns the brand new document created in notifications desk def new_notification(consumer, notice_id, notice_type) realize = consumer.notifications.construct(notice_id: notice_id, notice_type: notice_type) consumer.notice_seen = false consumer.save realize finish # Receives the notification object as parameter along side a sort # and returns a Consumer document, Submit document or a Remark document # relying on the kind provided  def notification_find(realize, kind) go back Consumer.to find(realize.notice_id) if kind == 'friendRequest' go back Submit.to find(realize.notice_id) if kind == 'remark' go back Submit.to find(realize.notice_id) if kind == 'like-post' go back except kind == 'like-comment' remark = Remark.to find(realize.notice_id) Submit.to find(remark.post_id) finish
finish

The new_notification(consumer, notice_id, notice_type) way will merely be used to create a brand new notification document and reserve it into the Notifications Desk

Since a notification will handiest be created as the results of some other way serve as being referred to as and it has no actual view. As a substitute of constructing a controller we can merely create it as a helper way.

The notification_find(realize, kind) way might be used to discover a explicit publish, remark of pal request primarily based on the notice_id and notice_type of the notification document stored.

The notice_type will decide which Desk to appear into and seek for the notice_id inside that Desk.

Putting in place Routes

config /routes.rb
Rails.application.routes.draw do root 'customers#index' devise_for :customers assets :customers, handiest: %i[index show] do assets :friendships, handiest: %i[create] finish assets :posts, handiest: %i[index new create show destroy] do assets :likes, handiest: %i[create] finish assets :feedback, handiest: %i[new create destroy] do assets :likes, handiest: %i[create] finish
finish

The order wherein you outline your routes is an important for the devise and customers routes since they may be able to in some cases finally end up overlapping.

Consumer’s and Posts

Including strategies into Consumer style

app/fashions/consumer.rb
magnificence Consumer < ApplicationRecord # Come with default devise modules. Others to be had are: # :confirmable, :lockable, :timeoutable, :trackable and  # :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_many :posts has_many :feedback, dependent: :damage has_many :likes, dependent: :damage has_many :friend_sent, class_name: 'Friendship', foreign_key: 'sent_by_id', inverse_of: 'sent_by', dependent: :damage has_many :friend_request, class_name: 'Friendship', foreign_key: 'sent_to_id', inverse_of: 'sent_to', dependent: :damage has_many :pals, ->  merge(Friendship.pals) , via: :friend_sent, supply: :sent_to has_many :pending_requests, ->  merge(Friendship.not_friends) , via: :friend_sent, supply: :sent_to has_many :received_requests, ->  merge(Friendship.not_friends) , via: :friend_request, supply: :sent_by has_many :notifications, dependent: :damage mount_uploader :symbol, PictureUploader validate :picture_size # Returns a string containing this consumer's first identify and remaining identify def full_name "# #" finish # Returns all posts from this consumer's pals and self def friends_and_own_posts myfriends = pals our_posts = [] myfriends.each and every do |f| f.posts.each and every do |p| our_posts << p finish finish posts.each and every do |p| our_posts << p finish our_posts finish non-public # Validates the scale of an uploaded image. def picture_size mistakes.upload(:symbol, 'will have to be lower than 1MB') if symbol.length > 1.megabytes finish
finish

We upload two new strategies into the Consumer style referred to as full_name and friends_and_own_posts.

Approach full_name merely returns a string that concatenates each the consumer’s fname and lname.

def full_name "# #"
finish

friends_and_own_posts way returns all posts of all of the consumer’s pals along side the consumer’s publish. 

def friends_and_own_posts myfriends = pals our_posts = [] myfriends.each and every do |f| f.posts.each and every do |p| our_posts << p finish finish posts.each and every do |p| our_posts << p finish our_posts finish

The variable ‘myfriends’ is an array this is populated the use of the ‘has_many :pals’ affiliation which returns all information of the consumer’s pals.

Afterwards, the posts of each and every of the chums along side the consumer’s personal posts are driven into ‘our_posts’.

Growing Posts

app/controllers/posts_controller.rb
magnificence PostsController < ApplicationController def index @our_posts = current_user.friends_and_own_posts finish def display @publish = Submit.to find(params[:identification]) finish def new @publish = Submit.new finish def create @publish = current_user.posts.construct(posts_params) if @publish.save redirect_to @publish else render 'new' finish finish def damage; finish non-public def posts_params params.require(:publish).allow(:content material, :imageURL) finish
finish
def index @our_posts = current_user.friends_and_own_posts
finish

The index way creates an example variable which retail outlets the results of the prior to now created way within the Consumer style, ‘friends_and_own_posts’ to assemble all of the posts of this consumer and his pals.

def display @publish = Submit.to find(params[:identification])
finish

The display way grabs a selected publish relying on the identification provided by way of the direction and retail outlets it into an example variable referred to as @publish.

def new @publish = Submit.new
finish

The new way creates a brand new Submit document and assigns it to the @publish
 variable however doesn’t reserve it.

def create @publish = current_user.posts.construct(posts_params) if @publish.save redirect_to @publish else render 'new' finish
finish

The create way creates a brand new Submit document, assigning the user_id of that publish to that of the current_user this is signed in. (current_user is a devise integrated helper way)

It additionally creates the Submit the use of the parameters accepted by way of the posts_params way. If the publish used to be created and effectively stored the browser will redirect_to the display web page view of the publish created.

Alternatively, if the publish used to be now not ready to be stored, the new web page view of a publish (which would be the variety used to create a publish) might be proven.

non-public
def posts_params params.require(:publish).allow(:content material, :imageURL)
finish

posts_params way is asserted as a non-public way (way is handiest available inside present report) which allows the parameters :content material and :imageURL equipped by way of the create publish direction returning them in a Hash layout.

# => <ActionController::Parameters "content material"=>"Instance publish", "imageURL"=>"http://instance.com" accepted: true>

Customers Controller

app/controllers/users_controller.rb
magnificence UsersController < ApplicationController def index @customers = Consumer.all @pals = current_user.pals @pending_requests = current_user.pending_requests @friend_requests = current_user.received_requests finish def display @consumer = Consumer.to find(params[:identification]) finish def update_img @consumer = Consumer.to find(params[:identification]) except current_user.identification == @consumer.identification redirect_back(fallback_location: users_path(current_user)) go back finish symbol = params[:consumer][:symbol] except params[:consumer].nil? if symbol @consumer.symbol = symbol if @consumer.save flash[:good fortune] = 'Symbol uploaded' else flash[:risk] = 'Symbol uploaded failed' finish finish redirect_back(fallback_location: root_path) finish
finish

Inside the index way:

def index @customers = Consumer.all @pals = current_user.pals @pending_requests = current_user.pending_requests @friend_requests = current_user.recieved_requests finish

We setup four(4) example variables, 3 of which makes use of the associations we created throughout the Consumer style.

  • @pals = current_user.pals’ gathers all of the information of all this consumer’s pals.
  • @pending_requests = current_user.pending_requests’ gathers all of the information of the customers this consumer has despatched pal requests to.
  • @friend_requests = current_user.recieved_requests’ gathers all information of the customers that has despatched this consumer a pal request.
def update_img @consumer = Consumer.to find(params[:identification]) except current_user.identification == @consumer.identification redirect_back(fallback_location: users_path(current_user)) go back finish symbol = params[:consumer][:symbol] except params[:consumer].nil? if symbol @consumer.symbol = symbol if @consumer.save flash[:good fortune] = 'Symbol uploaded' else flash[:risk] = 'Symbol uploaded failed' finish finish redirect_back(fallback_location: root_path) finish

The update_img way is used to change the picture report related to the Consumer document. 

A easy and immediately ahead way which retrieves a User document primarily based on the provided :identification parameter.

Then units the symbol column of the Consumer document to the picture equipped by way of the direction :symbol parameter and saves the document’s new symbol worth.

Feedback & Likes

Filling out the brand new, create and damage strategies within the Feedback controller

app/controllers/comments_controller.rb
magnificence CommentsController < ApplicationController come with ApplicationHelper def new @remark = Remark.new finish def create @remark = current_user.feedback.construct(comment_params) @publish = Submit.to find(params[:remark][:post_id]) if @remark.save @notification = new_notification(@publish.consumer, @publish.identification, 'remark') @notification.save finish redirect_to @publish finish def damage @remark = Remark.to find(params[:identification]) go back except current_user.identification == @remark.user_id @remark.damage flash[:good fortune] = 'Remark deleted' redirect_back(fallback_location: root_path) finish non-public def comment_params params.require(:remark).allow(:content material, :post_id) finish
finish

Beginning off with the create way:

def create @publish = Submit.to find(params[:remark][:post_id]) @remark = current_user.feedback.construct(comment_params) if @remark.save @notification = new_notification(@publish.consumer, @publish.identification, 'remark') @notification.save finish redirect_to @publish
finish

The example variable @publish is provided the price of the Submit document wherein this remark is written in connection with.

@remark retail outlets the price of a brand new remark document referencing the current_user as the landlord and the go back worth of comment_params. One way which allows the retrieval of two fields from the shape used to create a remark, :content material and :post_id.

As soon as the remark document is effectively stored and dedicated to the database, a brand new notification document is created the use of the prior to now created notification helper (

app/helpers/application_helper.rb

).

 if @remark.save @notification = new_notification(@publish.consumer, @publish.identification, 'remark') @notification.save finish

The damage remark way:

def damage @remark = Remark.to find(params[:identification]) go back except current_user.identification == @remark.user_id @remark.damage flash[:good fortune] = 'Remark deleted' redirect_back(fallback_location: root_path) finish

The go back except observation prevents customers from deleting commenting that aren’t their very own. 

The redirect_back() way merely refreshes the present web page.

Permitting Likes

app/controllers/likes_controller.rb

magnificence LikesController < ApplicationController come with ApplicationHelper def create kind = type_subject?(params)[0] @matter = type_subject?(params)[1] notice_type = "like-#kind" go back except @matter if already_liked?(kind) dislike(kind) else @like = @matter.likes.construct(user_id: current_user.identification) if @like.save flash[:good fortune] = "#kind cherished!" @notification = new_notification(@matter.consumer, @matter.identification, notice_type) @notification.save else flash[:risk] = "#kind like failed!" finish redirect_back(fallback_location: root_path) finish finish non-public def type_subject?(params) kind = 'publish' if params.key?('post_id') kind = 'remark' if params.key?('comment_id') matter = Submit.to find(params[:post_id]) if kind == 'publish' matter = Remark.to find(params[:comment_id]) if kind == 'remark' [type, subject] finish def already_liked?(kind) end result = false if kind == 'publish' end result = Like.the place(user_id: current_user.identification, post_id: params[:post_id]).exists? finish if kind == 'remark' end result = Like.the place(user_id: current_user.identification, comment_id: params[:comment_id]).exists? finish end result finish def dislike(kind) @like = Like.find_by(post_id: params[:post_id]) if kind == 'publish' @like = Like.find_by(comment_id: params[:comment_id]) if kind == 'remark' go back except @like @like.damage redirect_back(fallback_location: root_path) finish
finish

Because the create way relies on a couple of helper supply strategies I will be able to provide an explanation for the ones first.

def type_subject?(params) kind = 'publish' if params.key?('post_id') kind = 'remark' if params.key?('comment_id') matter = Submit.to find(params[:post_id]) if kind == 'publish' matter = Remark.to find(params[:comment_id]) if kind == 'remark' [type, subject]
finish

The type_subject?() way receives the parameters from the direction and determines if the identification acquired is that of a remark or a publish.

Because the create like way is offered by way of two nested routes. One inside a publish direction and some other inside a remark direction, relying on the direction used to name the process a unique parameter is given.

assets :posts, handiest: %i[index new create show destroy] do assets :likes, handiest: %i[create]
finish
assets :feedback, handiest: %i[new create destroy] do assets :likes, handiest: %i[create]
finish

After is it made up our minds which direction used to be used, the process returns an array containing the kind, a string worth of both ‘remark’ or ‘publish’ and matter, the document of both the remark or publish.

def already_liked?(kind) end result = false if kind == 'publish' end result = Like.the place(user_id: current_user.identification, post_id: params[:post_id]).exists? finish if kind == 'remark' end result = Like.the place(user_id: current_user.identification, comment_id: params[:comment_id]).exists? finish end result
finish

Subsequent up, the process already_liked? returns both a real or false worth figuring out whether or not a Like document exits for the publish or remark in query.

def dislike(kind) @like = Like.find_by(post_id: params[:post_id]) if kind == 'publish' @like = Like.find_by(comment_id: params[:comment_id]) if kind == 'remark' go back except @like @like.damage redirect_back(fallback_location: root_path)
finish

Then we’ve the dislike way which is largely the damage way for the Likes controller. It to find the like document for the given publish or remark primarily based on the direction used and destroys it.

Application helper Likes strategies

app/helpers/application_helper.rb

module ApplicationHelper # Assessments whether or not a publish or remark has already been cherished by way of the  # present consumer returning both true or false def cherished?(matter, kind) end result = false end result = Like.the place(user_id: current_user.identification, post_id: matter.identification).exists? if kind == 'publish' end result = Like.the place(user_id: current_user.identification, comment_id: matter.identification).exists? if kind == 'remark' end result finish
finish

Very similar to the already_liked way within the Likes controller, the cherished?(matter, kind) way takes two arguments. A document object, matter and the string literal of its kind.

Friendship Helpers

Because the Friendship controller relies on some helper strategies we can create them within the application helper.

app/helpers/application_helper.rb
module ApplicationHelper def friend_request_sent?(consumer) current_user.friend_sent.exists?(sent_to_id: consumer.identification, standing: false) finish def friend_request_received?(consumer) current_user.friend_request.exists?(sent_by_id: consumer.identification, standing: false) finish # Assessments whether or not a consumer has had a pal request despatched to them by way of the present consumer or  # if the present consumer has been despatched a pal request by way of the consumer returning both true or false def possible_friend?(consumer) request_sent = current_user.friend_sent.exists?(sent_to_id: consumer.identification) request_received = current_user.friend_request.exists?(sent_by_id: consumer.identification) go back true if request_sent != request_recieved go back true if request_sent == request_recieved && request_sent == true go back false if request_sent == request_recieved && request_sent == false finish
finish

We could glance somewhat nearer at this newly added strategies.

def friend_request_sent?(consumer) current_user.friend_sent.exists?(sent_to_id: consumer.identification, standing: false)
finish

The friend_request_sent? way exams whether or not a consumer has had a pal request despatched to them by way of the present consumer returning both true or false.

def friend_request_received?(consumer) current_user.friend_request.exists?(sent_by_id: consumer.identification, standing: false)
finish

This system exams whether or not a consumer has despatched a pal request to the present consumer returning both true or false.

def possible_friend?(consumer) request_sent = current_user.friend_sent.exists?(sent_to_id: consumer.identification) request_received = current_user.friend_request.exists? (sent_by_id: consumer.identification) go back true if request_sent != request_recieved go back true if request_sent == request_recieved && request_sent == true go back false if request_sent == request_recieved && request_sent == false
finish

This system exams whether or not a consumer has had a pal request despatched to them by way of the present consumer or if the present consumer has been despatched a pal request by way of the consumer. This system returns both true or false.

Friendship Controller

app/controllers/friendships_controller.rb
magnificence FriendshipsController < ApplicationController come with ApplicationHelper def create go back if current_user.identification == params[:user_id] # Disallow the facility to ship your self a pal request # Disallow the facility to ship pal request greater than as soon as to similar individual go back if friend_request_sent?(Consumer.to find(params[:user_id])) # Disallow the facility to ship pal request to any person who already despatched you one go back if friend_request_recieved?(Consumer.to find(params[:user_id])) @consumer = Consumer.to find(params[:user_id]) @friendship = current_user.friend_sent.construct(sent_to_id: params[:user_id]) if @friendship.save flash[:good fortune] = 'Good friend Request Despatched!' @notification = new_notification(@consumer, @current_user.identification, 'friendRequest') @notification.save else flash[:risk] = 'Good friend Request Failed!' finish redirect_back(fallback_location: root_path) finish def accept_friend @friendship = Friendship.find_by(sent_by_id: params[:user_id], sent_to_id: current_user.identification, standing: false) go back except @friendship # go back if no document is located @friendship.standing = true if @friendship.save flash[:good fortune] = 'Good friend Request Authorized!' @friendship2 = current_user.friend_sent.construct(sent_to_id: params[:user_id], standing: true) @friendship2.save else flash[:risk] = 'Good friend Request may just now not be accredited!' finish redirect_back(fallback_location: root_path) finish def decline_friend @friendship = Friendship.find_by(sent_by_id: params[:user_id], sent_to_id: current_user.identification, standing: false) go back except @friendship # go back if no document is located @friendship.damage flash[:good fortune] = 'Good friend Request Declined!' redirect_back(fallback_location: root_path) finish
finish

The create way:

def create go back if current_user.identification == params[:user_id] go back if friend_request_sent?(Consumer.to find(params[:user_id])) go back if friend_request_received?(Consumer.to find(params[:user_id])) @consumer = Consumer.to find(params[:user_id]) @friendship = current_user.friend_sent.construct(sent_to_id: params[:user_id]) if @friendship.save flash[:good fortune] = 'Good friend Request Despatched!' @notification = new_notification(@consumer, @current_user.identification, 'friendRequest') @notification.save else flash[:risk] = 'Good friend Request Failed!' finish redirect_back(fallback_location: root_path)
finish

The first go back observation prevents the facility of sending your self a pal request.

The 2nd go back observation makes use of the process friend_request_sent? to forestall the sending of pal requests greater than as soon as to similar individual.

The 3rd go back observation prevents the sending of pal requests to any person who has already despatched you one, the use of way friend_request_received?.

Since this friendships controller create way is a nested direction below the customers useful resource the direction created supplies the parameter user_id to be used inside this serve as.

@friendship = current_user.friend_sent.construct(sent_to_id: params[:user_id])

current_user.friend_sent.construct() creates a brand new document within the Friendship desk supplying the price of sent_by_id as that of the present consumer the use of the friend_sent affiliation between the Consumer style and Friendship style.

As soon as the Friendship document is effectively stored a new_notification() is created and connected.

def accept_friend @friendship = Friendship.find_by(sent_by_id: params[:user_id], sent_to_id: current_user.identification, standing: false) go back except @friendship # go back if no document is located @friendship.standing = true if @friendship.save flash[:good fortune] = 'Good friend Request Authorized!' @friendship2 = current_user.friend_sent.construct(sent_to_id: params[:user_id], standing: true) @friendship2.save else flash[:risk] = 'Good friend Request may just now not be accredited!' finish redirect_back(fallback_location: root_path)
finish

The accept_friend way updates the friendship document within the Friendship desk environment the standing of the document to true which we used to suggest that the customers are pals. 

As soon as the unique document is up to date and stored a replica document is created. This replica document can have the inverse worth for sent_by_id and sent_to_id. This makes it more straightforward to accomplish friending duties and database exams to decide pals lists.

As an example, John sends Samantha a pal request. Samantha accepts the pal request. Upon accepting the pal request some other pal request is made mechanically. However as a substitute this time it’ll be as though Samantha had despatched John a pal request and John had accredited it.

def decline_friend @friendship = Friendship.find_by(sent_by_id: params[:user_id], sent_to_id: current_user.identification, standing: false) go back except @friendship # go back if no document is located @friendship.damage flash[:good fortune] = 'Good friend Request Declined!' redirect_back(fallback_location: root_path) finish
finish

If a consumer declines a pal request the document is deleted out of the Friendship desk.

Enhancing Routes

config/routes.rb
Rails.application.routes.draw do root 'customers#index' devise_for :customers assets :customers, handiest: %i[index show] do assets :friendships, handiest: %i[create] do assortment do get 'accept_friend' get 'decline_friend' finish finish finish put '/customers/:identification', to: 'customers#update_img' assets :posts, handiest: %i[index new create show destroy] do assets :likes, handiest: %i[create] finish assets :feedback, handiest: %i[new create destroy] do assets :likes, handiest: %i[create] finish # For main points on the DSL to be had inside this report, see https://guides.rubyonrails.org/routing.html
finish

Additionally added a PUT direction hyperlink to the update_img way within the Customers controller. We used PUT as a result of PUT and PATCH requests are used to replace present information.

Perspectives

Alternatively, those are all not obligatory, it’s possible you’ll use your individual customized categories. 

The principle level of this phase is to turn you how you can use the helper purposes and controller strategies to succeed in our objective.

Structure Perspectives

app/perspectives/layouts
We can handiest cross over the routes, strategies and helper strategies used throughout the perspectives. For the HTML layout of the total view you’ll be able to have a look at Spybook’s Github perspectives.

Necessarily those perspectives are view-able on each and every web page, so, that is the place we can create our Flash notifications and Navigation Bar.

Flash Notifications

We create a brand new report titled ‘_flash.html.erb, the _ (underscore) previous the identify of the report dictates that this report might be a render.
app/perspectives/layouts/_flash.html.erb
<% flash.each and every do |message_type, message| %> <%= content_tag(:div, message, magnificence: "alert alert-#") %>
<% finish %>

Since flash is a Hash, flash.each and every do scans via each and every of the important thing, worth pairs. The keys are between :realize:good fortune:error and :alert. The values are the flash message provided.

So content_tag(:div, message, magnificence: “alert alert-#”), creates a brand new div component with it’s TextContent equivalent to the ‘message’ provided with a category equivalent to ‘alert alert-‘message_type’’ provided. ‘message_type’ may also be both realize, good fortune, error or alert.

Navigation Bar

Create a brand new report titled ‘_header.html.erb’.

app/perspectives/layouts/_header.html.erb
<div magnificence= "container mx-auto"> <div magnificence= "row mx-auto"> <div magnificence= "col-auto"> <%= link_to posts_path, magnificence: "textual content-mild nav-hyperlink font- raleway" do%> <i magnificence="fas fa-home fa-2x"></i> Timeline <%finish%> </div>
<!-- ...Code under now not proven -->
The ‘link_to posts_path’ direction merely hyperlinks to the Posts index view

app/perspectives/posts/index.html.erb
<div magnificence= "col-auto"> <%= link_to users_path, magnificence: "textual content-mild nav-hyperlink font-raleway"  do%> <i magnificence="fas fa-users fa-2x"></i> To find Pals <%finish%> </div>
The ‘link_to users_path’ direction merely hyperlinks to the Customers index view

app/perspectives/customers/index.html.erb
<ul magnificence="navbar-nav ml-auto"> <% if user_signed_in? %> <li magnificence="nav-item"> <button magnificence="text-white btn btn-secondary", data- toggle="modal" data-target="#noticeModal"> <i magnificence="fas fa-bell"></i> <%= current_user.notifications.rely%> </button> </li>
<!-- ... -->
The notification’s button will show an icon equipped by way of FontAwesome and the choice of notifications the present consumer has. 

The notifications device defined right here on this instructional is relatively other from the device used on the Github venture.

<div magnificence="dropdown-menu" aria-labelledby="navbarDropdown"> <%= link_to "My Profile", user_path(current_user), magnificence: "dropdown-merchandise font-raleway" %> <%= link_to "Log off", destroy_user_session_path, magnificence: "dropdown-merchandise font-raleway", way: :delete %>
</div>
The ‘link_to user_path(current_user)’ direction hyperlinks to the Customers display view

app/perspectives/customers/display.html.erb

the use of the identification of present consumer to show data relative to that consumer.

The ‘link_to destroy_user_session_path’ direction hyperlinks to a devise periods controller damage way

<% if user_signed_in? %> <%= render 'shared/notifications', object: current_user.notifications%>
<%finish%>

This render on the backside of the web page is important for the notifications button to paintings. Because it lets in a pop as much as seem as soon as clicked which presentations all notifications the consumer has.

Application view

app/perspectives/layouts/application.html.erb
Right here we can render our Flash notifications(render ‘layouts/flash’) and our Navigation bar(render ‘layouts/header’) in addition to upload hyperlinks to GoogleFonts and FontAwesome.
<physique> <%= render 'layouts/header' %> <div magnificence='container'> <br> <%= render 'layouts/flash' %> <%= yield %> </div>
</physique>
<%= yield %>

is placeholder code the place each and every of the opposite perspectives might be displayed.

Subsequent, we can create all of our shared perspectives.

Shared Rendered Modal Perspectives

app/perspectives/shared

Symbol Add Modal

app/perspectives/shared/_imageUploadModal.html.erb
<div magnificence="modal-body"> <h5 magnificence="modal-title" identification="imageUploadModalLabel"> Alternate Profile Image </h5> <%= form_for(object, html:  way: :put ) do |f| %> <%= render "devise/shared/error_messages", useful resource: object %> <div magnificence="box"> <%= f.label :Profile_image %><br /> <%= f.file_field :symbol, settle for: 'symbol/jpeg,symbol/gif,symbol/png' %> </div> <div magnificence="movements"> <%= f.put up "Replace" %> </div> <% finish %>
</div>

Inside the modal we can create a sort which permits the consumer to add any symbol of kind jpeg, gif or png.

<%= form_for(object, html:  way: :put ) do |f| %> <%= render "devise/shared/error_messages", useful resource: object %>
The thing variable you spot getting used goes to be the consumer object as soon as handed as reference by way of the render way as we can see within the Consumer display view.
app/perspectives/customers/display.html.erb
<%= render 'shared/imageUploadModal', object: @consumer%>
app/perspectives/shared/_imageUploadModal.html.erb
<script kind="textual content/javascript">
file.getElementById('user_image').addEventListener('alternate', serve as()  var size_in_megabytes = this.recordsdata[0].length/1024/1024; if (size_in_megabytes > 1) 
);
</script>

This serve as returns an alert when the picture report uploaded is larger than 1 Mb in length.

Notifications Modal

app/perspectives/shared/_notifications.html.erb
<div magnificence="modal-body"> <% object.each and every do |n|%> <!--If Notification kind is a Good friend Request --> <% if n.notice_type == "friendRequest"%> <% consumer = notification_find(n, 'friendRequest')%> <%= "Good friend Request despatched from #" %> <% finish %>

Identical to the Symbol modal render the Notifications modal render is handed an object variable:

app/perspectives/layouts/_header.html.erb
<% if user_signed_in? %> <%= render 'shared/notifications', object: current_user.notifications%>
<%finish%>

This object variable handed is the associative has many hyperlink between the Consumer style and the Notifications style.

Since it is a has many hyperlink and it will probably go back a couple of information throughout the Notifications modal we run an each and every do loop to deal with each and every notification one at a time. This permits us to categorize our notifications and the facility to turn context proper textual content about each and every notification.

app/perspectives/shared/_notifications.html.erb
<!-- ...Code above now not proven -->
<div magnificence="modal-body"> <% object.each and every do |n|%> <!-- If Notification kind is a Good friend Request --> <% if n.notice_type == "friendRequest"%> <% consumer = notification_find(n, 'friendRequest')%> <%= "Good friend Request despatched from #" %> <% finish %> <!-- If Notification kind is a remark --> <% if n.notice_type == "remark"%> <%= link_to post_path(notification_find(n, 'remark')) do %> Anyone commented on your publish <% finish %> <% finish %> <!-- If Notification kind is a cherished publish --> <% if n.notice_type == "like-post"%> <%= link_to post_path(notification_find(n, 'like-post')) do %> Anyone cherished your publish <% finish %> <% finish %> <!-- If Notification kind is a cherished remark --> <% if n.notice_type == "like-comment"%> <%= link_to post_path(notification_find(n, 'like-comment')) do %> Anyone cherished your remark below this publish <% finish %> <% finish %> <br> <% finish %>
</div>

Except the friendship request notification all different notifications supply a hyperlink to the cherished content material the use of the notification_find helper we created within the application_helper report.

Feedback Perspectives

Remark Shape View

app/perspectives/feedback/_form.html.erb
<%= form_for @remark = Remark.new do |f| %>

The feedback variety view is a immediately ahead view which goes to be rendered in a couple of puts. It makes use of the Rails form_for option to create a brand new Remark document and makes an attempt to put up this document the use of the Feedback controller Create way.

Remark Structure View

app/perspectives/feedback/_comment.html.erb

The feedback structure is lovely immediately ahead this is a rendered view which is handed an object parameter which is typically going to be the feedback related to a selected publish.

<%= distance_of_time_in_words(c.created_at, Time.now) %>

It makes use of a Rails way distance_of_time_in_words to show the period of time that has handed from when the publish used to be created to the present time.

<%= render 'likes/like_comments', object: c%>

It additionally renders a Likes view supplying the remark itself as an object variable.

Likes View

app/perspectives/likes/_like_comments.html.erb
<span>
<%= object.likes.rely %> <%= link_to comment_likes_path(object), magnificence: "textual content-mild", way: :publish do %> <% if cherished?(matter = object, kind = 'remark') %> <button magnificence="btn btn-liked size-12"><i magnificence="fas fa-thumbs-up"></i></button> <% else %> <button magnificence="btn btn-neutral size-12"><i magnificence="fas fa-thumbs-up"></i></button> <% finish %>
<% finish %>
Likes
</span>

View makes use of object variable provided to this rendered view to show the volume of likes the specific object has.

A hyperlink is created the use of the comment_likes_path direction which is handed the thing as a parameter. This nested direction hyperlinks to the create way within the Likes controller.

The cherished? helper way which is created within the application_helper report is used to decide the colour of the thumbs up icon.

app/perspectives/likes/_like_posts.html.erb
<span>
<%= object.likes.rely %> <%= link_to post_likes_path(object), magnificence: "textual content-mild", way: :publish do %> <% if cherished?(object, 'publish') %> <button magnificence="btn btn-liked"><i magnificence="fas fa-thumbs-up"></i></button> <% else %> <button magnificence="btn btn-neutral"><i magnificence="fas fa-thumbs-up"></i></button> <% finish %>
<% finish %>
Likes
</span>

The one distinction between the two like perspectives is the direction used. 

<%= link_to post_likes_path(object), magnificence: "textual content-mild", way: :publish do %>

Its additionally price bringing up the ‘way: :publish’ parameter on the link_to way. That is important to create a brand new Like object. Usually link_to is used to GET data.

Posts Perspectives

New Submit View

app/perspectives/posts/new.html.erb

Submit Structure View

app/perspectives/posts/_post_layout.html.erb

This view is very similar to the feedback structure view because it renders feedback, a remark variety and a likes view inside it.

Submit Display View

Create a brand new report titled ‘display.html.erb’.

app/perspectives/posts/display.html.erb
<h1>Submit</h1>
<%= render 'posts/post_layout', object: @publish %>

Timeline — Submit Index View

app/perspectives/posts/index.html.erb
<h1> Timeline - My posts and my pals posts</h1>
<div magnificence="middle font-raleway "> <%= link_to new_post_path, magnificence: "btn btn-secondary" do%> New Submit? <%finish%>
</div> <% @our_posts.each and every do |p|%> <%= render 'posts/post_layout', object: p %>
<% finish %>

Easy view which has a hyperlink to the new publish view

It additionally renders the posts of the present consumer and the consumer’s pals the use of the example variable created within the Posts controller, @our_posts.

Customers Perspectives

To find Pals — Consumer Index View

app/perspectives/customers/new.html.erb

The index view makes use of the example variables created within the Customers controller to categorize all of the customers.

<% except @pals.empty? %>
...
<% except @pending_requests.empty? %>
...
<% except @friend_requests.empty? %>
...
<!-- ...Does not display all code up above -->
<% except @friend_requests.empty? %> <div magnificence="card my-5 py-3 bg-light shadow"> <h2 magnificence="middle pb-3 text-dark border-bottom">Pending Good friend Requests</h2> <% @friend_requests.each and every do |consumer|%> <!-- Displays all customers pal requests has been despatched to --> <div magnificence="d-flex align-items-center mb-2 border-bottom py-2"> <div magnificence="col-auto p-0 pl-5 text-capitalize"> <%= link_to user_path(consumer) do %> <%= consumer.full_name %> <% finish %> </div> <div magnificence="col-auto p-0 px-1">|</div> <div magnificence="col-auto p-0"> <button magnificence= "btn btn-pending shadow" data-toggle="modal" data-target="#decisionModal"> <i magnificence="fas fa-envelope"></i> Pending Good friend Request... </button> </div> </div> <%= render 'friendships/decisionModal', object: consumer %> <br><br> <% finish %> </div>
<% finish %>
<!-- ...Does not display all code down under -->

All Pending Good friend requests are created as buttons which as soon as clicked opens a rendered modal view.

Friendship Determination Modal View

app/perspectives/friendships/_decisionModal.html.erb
<!-- Modal -->
<div magnificence="modal fade" identification="decisionModal" tabindex="-1" function="conversation" aria-labelledby="decisionModal" aria-hidden="true"> <div magnificence="modal-dialog" function="file"> <div magnificence="modal-content"> <div magnificence="modal-body"> <h5 magnificence="modal-title" identification="decisionModalLabel">Good friend Request from <%= "#object.full_name" %></h5> </div> <div magnificence="modal-footer"> <%= link_to accept_friend_user_friendships_path(object) do %> <button kind="button" magnificence="btn btn-accept text-white font-weight-bold"> <i magnificence="fas fa-user-check"></i> Settle for</button> <% finish %> <%= link_to decline_friend_user_friendships_path(object) do %> <button kind="button" magnificence="btn btn-decline text-white font-weight-bold"><i magnificence="fas fa-user-times"></i> Decline</button> <% finish %> </div> </div> </div>
</div>

Good friend request choice modal comprises hyperlinks to the accept_friend and decline_friend strategies from the Friendship Controller.

Consumer Display View

app/perspectives/customers/display.html.erb

Omniauth Facebook Integration

Create an account after which create a brand new app.

Remember of the App ID and the App Secret. You’re going to want those values later on.

  • Fill out the Touch E-mail data
  • Set the Class for the App
  • and Set a Privateness Coverage URL
    Privateness Coverage Generator

Then afterwards you’ll save the adjustments and now you will have to be capable to transfer the app building standing to ‘Reside’ as a substitute of ‘In Construction’.

Configuring Mission for Facebook Use

Should you added the not obligatory gemdotenv-rails’ you’ll be able to use a .env report which you’d create on the root listing to retailer a ‘FACEBOOK_APP_ID’ and ‘FACEBOOK_APP_SECRET’ worth. 

Inside the ‘.env’ report you might put:

FACEBOOK_APP_ID = 'FILL IN WITH YOUR APP ID'
FACEBOOK_APP_SECRET = 'FILL IN WITH YOUR APP SECRET'

Replace Consumer style

Subsequent up, you will have to upload the columns “supplier” (string) and “uid” (string) in your Consumer style.

The use of the under command:

$ rails g migration AddOmniauthToUsers supplier:string uid:string

Then migrate the database:

$ rails db:migrate

Subsequent, you’ll upload two(2) OmniAuth easy methods to Consumer style self.from_omniauth() and self.new_with_session(). And in addition upload further parameters onto the devise way.

app/fashions/consumer.rb
magnificence Consumer < ApplicationRecord has_many :posts has_many :feedback, dependent: :damage has_many :likes, dependent: :damage has_many :friend_sent, class_name: 'Friendship', foreign_key: 'sent_by_id', inverse_of: 'sent_by', dependent: :damage has_many :friend_request, class_name: 'Friendship', foreign_key: 'sent_to_id', inverse_of: 'sent_to', dependent: :damage has_many :pals, ->  merge(Friendship.pals) , via: :friend_sent, supply: :sent_to has_many :pending_requests, ->  merge(Friendship.not_friends) , via: :friend_sent, supply: :sent_to has_many :recieved_requests, ->  merge(Friendship.not_friends) , via: :friend_request, supply: :sent_by has_many :notifications, dependent: :damage mount_uploader :symbol, PictureUploader # Come with default devise modules. Others to be had are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: %i[facebook] validates :fname, duration: , presence: true validates :lname, duration: , presence: true validate :picture_size def full_name "# #" finish # Returns all posts from this consumer's pals and self def friends_and_own_posts myfriends = pals our_posts = [] myfriends.each and every do |f| f.posts.each and every do |p| our_posts << p finish finish posts.each and every do |p| our_posts << p finish our_posts finish def self.from_omniauth(auth) the place(supplier: auth.supplier, uid: auth.uid).first_or_create do |consumer| consumer.e-mail = auth.data.e-mail consumer.password = Devise.friendly_token[0, 20] consumer.fname = auth.data.first_name # assuming the consumer style has a primary identify consumer.lname = auth.data.last_name # assuming the consumer style has a final identify consumer.symbol = auth.data.symbol # assuming the consumer style has a picture # If you're the use of confirmable and the supplier(s) you employ validate emails, # uncomment the road under to skip the affirmation emails. # consumer.skip_confirmation! finish finish def self.new_with_session(params, consultation) tremendous.faucet do |consumer| if (information = consultation['devise.facebook_data'] && consultation['devise.facebook_data']['further']['raw_info']) consumer.e-mail = information['e-mail'] if consumer.e-mail.clean? finish finish finish non-public # Validates the scale of an uploaded image. def picture_size mistakes.upload(:symbol, 'will have to be lower than 1MB') if symbol.length > 1.megabytes finish
finish

We change one of the newly added OmniAuth purposes to paintings with our Consumer style database columns.

def self.from_omniauth(auth) the place(supplier: auth.supplier, uid: auth.uid).first_or_create do |consumer| consumer.e-mail = auth.data.e-mail consumer.password = Devise.friendly_token[0, 20] consumer.fname = auth.data.first_name consumer.lname = auth.data.last_name consumer.symbol = auth.data.symbol finish
finish

We edit the self.from_omniauth() way to make use of our Consumer style’s columns fname, lname and symbol.

Edit Devise Initializer

Subsequent, you want to claim the supplier on your devise.rb by way of including the code under

config/initializers/devise.rb
Devise.setup do |config| # ...Does not display different code up above config.action_mailer.default_url_options =  host: 'localhost', port: 3000  config.omniauth :fb, ENV['FACEBOOK_APP_ID'], ENV['FACEBOOK_APP_SECRET'], token_params:  parse: :json , scope: 'public_profile,e-mail', info_fields: 'e-mail,first_name,last_name,gender,birthday,location,image'
finish

Replace Routes

config /routes.rb
Rails.application.routes.draw do root 'customers#index' devise_for :customers, controllers:  omniauth_callbacks: 'customers/omniauth_callbacks'  assets :customers, handiest: %i[index show] do assets :customers, handiest: %i[index show] do assets :friendships, handiest: %i[create] do assortment do get 'accept_friend' get 'decline_friend' finish finish finish put '/customers/:identification', to: 'customers#update_img' assets :posts, handiest: %i[index new create show destroy] do assets :likes, handiest: %i[create] finish assets :feedback, handiest: %i[new create destroy] do assets :likes, handiest: %i[create] finish # For main points on the DSL to be had inside this report, see https://guides.rubyonrails.org/routing.html
finish
devise_for :customers, controllers:  omniauth_callbacks:'customers/omniauth_callbacks' 

Added the extra parameter for controllers onto the devise_for direction.

Take a look at Omniauth Facebook Login

Run the check server and use the devise generated ‘Sign in with Facebook’ hyperlink equipped to create an account.

After the use of the hyperlink to create an account it’ll upload a brand new product in your Facebook app referred to as ‘Facebook Login’.

You’re going to then click on onto the newly added Facebook Login product and cross into its settings. 

After coming into the settings web page for the app, it is very important upload the two routes created by way of the devise omniauth fb gem. You’re going to upload those routes into the Legitimate OAuth Redirect URIs box and hit save adjustments.

The routes added will typically be the URL of the web page, which in our case the identify of the Heroku app are living hyperlink, plus ‘/customers/auth/fb’ and ‘/customers/auth/fb/callback’

Now you’ll be able to set the Facebook app from Construction mode to Reside mode.

Deployment to Heroku

Merge fresh adjustments to the git hub grasp department.

Create new Heroku application:

$ heroku create

Then push adjustments to Heroku grasp the use of:

$ git push heroku grasp

Then generate database on Heroku application:

$ heroku run rails db:migrate

Set Heroku config variables

The use of terminal Set Facebook App ID

$ heroku config:set FACEBOOK_APP_ID=YOURFACEBOOKAPPID

Set Facebook App Secret

$ heroku config:set FACEBOOK_APP_SECRET=YOURFACEBOOKAPPSECRET

Then open Heroku application

$ heroku open

Keep Tuned!

(Visited 2 times, 1 visits today)

Leave a Reply