Skip to content

Backend Training: Filter

Nelson Lee edited this page Nov 30, 2017 · 4 revisions

🔖 : font-awesome-rails

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.

Date Range Filter

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')
                | &nbsp;
                = date
        li
          = link_to posts_path(range: 'past') do
            = fa_icon('chevron-right')
            | &nbsp;
            = 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. post#index

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. post#index

Category Filter

🔖 : act_as_taggable_on

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')
              | &nbsp;
              = category.name
        li
          = link_to posts_path do
            = fa_icon('chevron-right')
            | &nbsp;
            = 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. post#index

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..

↪️ Next Section: Show Page

Clone this wiki locally