-
Notifications
You must be signed in to change notification settings - Fork 0
Backend Training: Filter
Next, we need to add the year filter to the posts. It has the following criteria.
- By default we should only show the posts in the current month and year.
- When clicking on the year link it should show the posts in that month and year.
- The month and year links should only occur if there are posts in that date range, and should only list out maximum latest 4 months, along with a view past posts link.
Add scopes
# app/models/post.rb
class Post < ApplicationRecord
.....
scope :recent, -> { where('published_date >= ?', Time.zone.now - 4.months) }
scope :past, -> { where('published_date <= ?', Time.zone.now - 4.months) }
scope :by_published_date, -> { order(published_date: :desc) }
scope :published, lambda {
where('published = ? AND published_date <= ?', true, Time.zone.now)
}
scope :published_in, lambda { |date|
where(published_date: date.beginning_of_month..date.end_of_month).
by_published_date
}
......
end
Change controller
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :find_dates
before_action :set_default_params, only: %i[index]
def index
@posts = Post.published
filter_posts_by_range
@posts = @posts.by_published_date.
paginate(page: params[:page], per_page: 9)
end
def show
@post = Post.published.friendly.find params[:id]
end
private
def find_dates
@dates = Post.published.recent.by_published_date.
pluck(:published_date)&.map { |x| x.strftime('%B, %Y') }&.uniq
end
def filter_posts_by_range
@posts =
case params[:range]
when 'past' then @posts.past
else @posts.recent.published_in Time.zone.parse(params[:date])
end
end
def set_default_params
params[:date] ||= most_recent_post_date
params[:range] ||= 'recent'
end
def most_recent_post_date
post = Post.by_published_date.first
date = post.present? ? post.published_date : Time.zone.now
date.strftime('%B, %Y')
end
end
Here we've added the date_range filter for posts along with default params so when it first renders without any params it will list out the latest posts in the current month and year.
Add dates links to aside
# app/views/posts/_aside.slim
aside
.panel.panel-default
.panel-heading.text-uppercase
= t('.archives.title')
.panel-body
ul
- @dates.each do |date|
li
= link_to posts_path(date: date) do
= fa_icon('chevron-right')
|
= date
li
= link_to posts_path(range: 'past') do
= fa_icon('chevron-right')
|
= t('.archives.past')
p.s. fa_icon is a helper method provided by font-awesome-rails gem(doc)
Add Translation
# config/locales/en.yml
en:
......
posts:
index:
title: News
aside:
archives:
title: Archives
past: All Past Newsm
Refresh the browser Command ⌘+r, and it should look like.
Let's test and see if we meet the requirement
- The month and year links should only occur if there are posts in that date range, and should only list out maximum latest 4 months, along with a view past posts link.
$ rails c
irb(main):007:0> 5.times { |i| Post.all.sample.update(published_date: Time.zone.now - i.months) }
Refresh the browser Command ⌘+r, and you should see.
Let's add categories to the posts. For this we will use act_as_taggable_on gem(doc)
Run act_as_taggable_on migrations
$ rake acts_as_taggable_on_engine:install:migrations
$ rake db:migrate
Hook it up with post
# app/models/post.rb
class Post < ApplicationRecord
extend FriendlyId
friendly_id :slug_candidates, use: %i[slugged finders]
acts_as_taggable_on :categories
......
end
Test it out
$ rails c
irb(main):004:0> Post.first.category_list.add('awesome')
Post Load (0.5ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT $1 [["LIMIT", 1]]
ActsAsTaggableOn::Tagging Load (0.4ms) SELECT "taggings".* FROM "taggings" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 [["taggable_id", 70], ["taggable_type", "Post"]]
ActsAsTaggableOn::Tag Load (0.7ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'categories' AND taggings.tagger_id IS NULL) [["taggable_id", 70], ["taggable_type", "Post"]]
=> ["awesome"]
Add categories to seed file
# db/seeds/posts.rb
Post.delete_all
categories = []
5.times do
categories << Faker::Lorem.word
end
20.times do
Post.create(
title: Faker::Lorem.sentence(1),
content: Faker::Lorem.paragraph(20),
category_list: categories.sample(1 + rand(categories.count)),
published: true
)
end
Now run the seed
$ rake db:seed:posts
Add category filters to controller
class PostsController < ApplicationController
before_action :find_dates, :find_categories
before_action :set_default_params, only: %i[index]
def index
@posts = Post.published
filter_posts
@posts = @posts.by_published_date.
paginate(page: params[:page], per_page: 9)
end
def show
@post = Post.published.friendly.find params[:id]
end
private
def filter_posts
params[:category].present? ? filter_posts_by_categories : filter_posts_by_range
end
def find_categories
@categories = Post.published.category_counts
end
def filter_posts_by_categories
category = params[:category]
return if category.blank?
@posts = @posts.tagged_with category
end
......
end
Add categories to aside
# app/views/posts/_aside.slim
aside
.panel.panel-default
.....
.panel.panel-default
.panel-heading.text-uppercase
= t('.categories.title')
.panel-body
ul
- @categories.each do |category|
li
= link_to posts_path(category: category.name) do
= fa_icon('chevron-right')
|
= category.name
li
= link_to posts_path do
= fa_icon('chevron-right')
|
= t('.categories.all')
Add translations
# config/locales/en.yml
en:
......
posts:
index:
title: News
aside:
archives:
title: Archives
past: All Past News
categories:
title: Categories
all: Uncategorised
Refresh the browser Command ⌘+r, and now you should be able to filter posts by either month & year or category.
Let's commit and continue
$ git add -A
$ git commit -m 'Post filters with categories and month + year'
Fix overcommit errors and commit again to continue..