Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ gem 'rubocop', '>= 1.0', '< 2.0'

gem 'devise', '~> 4.9'

gem 'cancancan'

gem 'rails-controller-testing'

gem 'kaminari'
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ GEM
bootsnap (1.17.0)
msgpack (~> 1.2)
builder (3.2.4)
cancancan (3.5.0)
capybara (3.39.2)
addressable
matrix
Expand Down Expand Up @@ -317,6 +318,7 @@ PLATFORMS

DEPENDENCIES
bootsnap
cancancan
capybara
debug
devise (~> 4.9)
Expand Down
30 changes: 19 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ Check the below contents for further details about this project.

# 📗 Contents

- [Description](#description)
- [Instructions](#instructions)
- [Authors](#authors)
- [Future](#future)
- [Contributions](#contributions)
- [Support](#support)
- [Acknowledgements](#acknowledgements)
- [License](#license)
- [🏷️ Topics Blog](#️-topics-blog)
- [📗 Contents](#-contents)
- [📖 Description](#-description)
- [🛠️ Instructions](#️-instructions)
- [👥 Authors](#-authors)
- [🔭 Future](#-future)
- [🤝🏻 Contributions](#-contributions)
- [⭐️ Support](#️-support)
- [🙏🏻 Acknowledgements](#-acknowledgements)
- [📝 License](#-license)

<!-- DESCRIPTION -->

Expand All @@ -32,10 +34,13 @@ User authentication is required first.
Every post contains comments & likes.
Each user displays (`name` / `photo` / `bio`) & number of his `posts`.
Each post displays (`title` / `text`) & number of its `comments` & `likes`.
Any user can delete his own posts & comments.
Pagination through posts using `kaminari` gem.
It is built using `Rails` framework with `Ruby`.
The project is configured to use `PostgreSQL` database.
Unit tests are carried on models & controller requests using `RSpec`.
Authentication using `Devise` gem.
Authorization using `CanCanCan` gem.
Porject is built using `Rails` framework with `Ruby`.
It is configured to use `PostgreSQL` database.
Unit tests are carried on (models / controller / views) using `RSpec`.

📌 **Tech Stack:**
- Programming language is `Ruby`
Expand All @@ -60,6 +65,8 @@ Unit tests are carried on models & controller requests using `RSpec`.
- Implemented request tests on (`index` / `show`) methods for controllers (`UsersController` / `PostsController`)
- Used `Capybara` gem to carry system tests on (`index` / `show`) action methods for controllers (`UsersController` / `PostsController`)
- Built authentication with `Devise` gem before accessing any controller
- Authorized rules with `CanCanCan` gem for users access
- Any user can create (posts / comments / likes) & delete his own (posts / comments)

<p align="right"><a href="#title">back to top</a></p>

Expand Down Expand Up @@ -115,6 +122,7 @@ bundle exec rspec -f d

📌 **Zabihullah:**
- [GitHub](https://github.com/ZabihullahNooriWardak)
- [LinkedIn](https://www.linkedin.com/in/zabih-noori-aa59a924a/)

<p align="right"><a href="#title">back to top</a></p>

Expand Down
25 changes: 20 additions & 5 deletions app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ menu {
ul {
padding: 0;
list-style: none;
margin-top: 10px;
}

a,
Expand Down Expand Up @@ -185,8 +184,8 @@ footer {
}

.user img {
width: 40px;
height: 40px;
width: 45px;
height: 45px;
overflow: hidden;
object-fit: cover;
position: absolute;
Expand Down Expand Up @@ -216,26 +215,42 @@ footer {

.user {
position: relative;
padding-inline-start: 60px;
padding-inline-start: 65px;
}

.post p {
margin-top: 10px;
margin: 10px 0;
}

.post em {
align-self: flex-end;
}

.comment {
display: flex;
gap: 10px;
padding-top: 10px;
align-items: center;
justify-content: space-between;
border-top: 1px solid var(--page-color);
margin-top: 10px;
}

.comment b {
color: var(--title-color);
}

.comment i {
color: inherit;
align-self: center;
font-size: inherit;
margin-inline-end: auto;
}

.comment a {
margin: 0;
}

a:hover,
input[type=submit]:hover {
color: var(--block-color);
Expand Down
11 changes: 11 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :configure_permitted_parameters, if: :devise_controller?

rescue_from ActiveRecord::RecordNotFound do
redirect_to(root_url, alert: 'Record not found')
end
rescue_from CanCan::AccessDenied do |exception|
redirect_to(root_url, alert: exception.message)
end

def index
redirect_to(root_url, alert: 'Invalid path')
end

protected

def configure_permitted_parameters
Expand Down
30 changes: 23 additions & 7 deletions app/controllers/comments_controller.rb
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
class CommentsController < ApplicationController
load_and_authorize_resource

before_action :set_comment, only: [:destroy]
before_action :set_path, only: %i[new create]

def new
@comment = Comment.new
@path = user_post_path(current_user, params[:post_id])
end

def create
@comment = Comment.new(parameters)
@comment.user = current_user
@comment = current_user.comments.build(comment_params)
@comment.post = Post.find(params[:post_id])

if @comment.save
redirect_to(user_post_path(@comment.user, @comment.post))
redirect_to user_post_path(@comment.user, @comment.post), notice: 'Comment successfully created'
else
@path = user_post_path(current_user, params[:post_id])
flash.now[:errors] = @comment.errors.full_messages
render(:new, status: :unprocessable_entity)
render :new, status: :unprocessable_entity
end
end

def destroy
@comment.destroy
redirect_to user_post_path(@comment.user, @comment.post), alert: 'Comment successfully deleted'
end

private

def parameters
def set_comment
@comment = Comment.find(params[:id])
end

def set_path
@path = user_post_path(current_user, params[:post_id])
end

def comment_params
params.require(:comment).permit(:text)
end
end
2 changes: 2 additions & 0 deletions app/controllers/likes_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class LikesController < ApplicationController
load_and_authorize_resource

def new
@like = Like.new(post_id: params[:post_id])
@like.user = current_user
Expand Down
12 changes: 10 additions & 2 deletions app/controllers/posts_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class PostsController < ApplicationController
load_and_authorize_resource

def index
@user = User.find(params[:user_id])
@posts = @user.posts.includes(:comments).where(author_id: params[:user_id]).order(id: :desc).page(params[:page])
Expand All @@ -9,7 +11,7 @@ def show
end

def create
@post = Post.new(parameters)
@post = Post.new(post_params)
@post.user = current_user
@post.comments_counter = @post.likes_counter = 0
if @post.save
Expand All @@ -20,9 +22,15 @@ def create
end
end

def destroy
@post = Post.find(params[:id])
@post.destroy
redirect_to(user_posts_path(@post.user), alert: 'Post successfully deleted')
end

private

def parameters
def post_params
params.require(:post).permit(:title, :text)
end
end
2 changes: 2 additions & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class UsersController < ApplicationController
load_and_authorize_resource

def index
@users = User.all
end
Expand Down
18 changes: 18 additions & 0 deletions app/models/ability.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class Ability
include CanCan::Ability

def initialize(user)
can(:read, :all)
return unless user.present?

can(:create, Post)
can(:create, Comment)
can(:create, Like)
can(:destroy, Post, author_id: user.id)
can(:destroy, Comment, user:)
return unless user.admin?

can(:destroy, Post)
can(:destroy, Comment)
end
end
9 changes: 7 additions & 2 deletions app/models/comment.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post
after_save :counter
after_save :addition
after_destroy :subtraction

private

def counter
def addition
post.increment!(:comments_counter)
end

def subtraction
post.decrement!(:comments_counter)
end
end
4 changes: 2 additions & 2 deletions app/models/like.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
class Like < ApplicationRecord
belongs_to :user
belongs_to :post
after_save :counter
after_save :addition

private

def counter
def addition
post.increment!(:likes_counter)
end
end
13 changes: 9 additions & 4 deletions app/models/post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ class Post < ApplicationRecord
validates :comments_counter, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :likes_counter, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
belongs_to :user, foreign_key: :author_id
has_many :comments
has_many :likes
after_save :counter
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
after_save :addition
after_destroy :subtraction
paginates_per 3

def recent_comments
Expand All @@ -18,7 +19,11 @@ def author=(author)

private

def counter
def addition
user.increment!(:posts_counter)
end

def subtraction
user.decrement!(:posts_counter)
end
end
4 changes: 4 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ class User < ApplicationRecord
def recent_posts
posts.order(id: :desc).limit(3)
end

def admin?
role == 'admin'
end
end
4 changes: 3 additions & 1 deletion app/views/comments/_comment.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<li class="comment">
<b><%= comment.user.name %>:</b>&ensp;<%= comment.text %>
<b><%= comment.user.name %>:</b>
<i><%= comment.text %></i>
<%= link_to('Delete', user_post_comment_path(comment.user, comment.post, comment), data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' }) if can?(:destroy, comment) %>
</li>
13 changes: 7 additions & 6 deletions app/views/posts/_post.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
<h2><%= post.title %> by <%= post.user.name %></h2>
<p><%= post.text %></p>
<i>Comments: <%= post.comments_counter %> Likes: <%= post.likes_counter %></i>
<% if defined?(actions) %>
<%= render(partial: 'partials/errors') %>
<i>
<%= link_to('Comment', new_user_post_comment_path(post.user, post)) %>
<%= link_to('Like', new_user_post_like_path(post.user, post)) %>
<% if defined?(actions) %>
<%= link_to('Comment', new_user_post_comment_path(post.user, post)) %>
<%= link_to('Like', new_user_post_like_path(post.user, post)) %>
<% else %>
<%= link_to('Details', user_post_path(post.user.id, post.id)) %>
<% end %>
<%= link_to('Delete', user_post_path(post.user, post), data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' }) if can?(:destroy, post) %>
</i>
<% else %>
<%= link_to('Details', user_post_path(post.user.id, post.id)) %>
<% end %>
<% if defined?(comments) && post.comments.length > 0 %>
<h3>Comments</h3>
<ul>
Expand Down
5 changes: 3 additions & 2 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
# root "posts#index"
root "users#index"
resources :users, only: [:index, :show] do
resources :posts, only: [:index, :show, :new, :create] do
resources :posts, only: [:index, :show, :new, :create, :destroy] do
get "/page/:page", action: :index, on: :collection
resources :comments, only: [:new, :create]
resources :comments, only: [:new, :create, :destroy]
resources :likes, only: [:new]
end
end
get "*path", to: "application#index"
end
Loading