Peter Nguyen
Peter Nguyen

Reputation: 359

How to Combine many similar methods and views into one

I want to combine similar methods and views into one, but still keep the url name, like the following:

Home/recommends/categories/shopping
Home/recommends/categories/nightview
Home/recommends/categories/food
Home/recommends/categories/area

I don't want to use params like "?something=xyz" in url.

In routes.rb:

resources :recommends, only: :index do
  collection do
    resources :categories, only: :show, controller: 'recommends' do
      collection do
        get :food
        get :area
        get :shopping
        get :nightview
      end
    end
  end
end

In controllers:

def food
  set_paginator
  @recommends = UserRecommend.where(category: "food").order('created_at desc').offset(@offset).limit(@limit).all
  @number_of_recommends = UserRecommend.where(category: "food").count
end

def area
  set_paginator
  @recommends = UserRecommend.where(category: "area").order('created_at desc').offset(@offset).limit(@limit).all
  @number_of_recommends = UserRecommend.where(category: "area").count
end

...

In views I have:

food.html.slim
area.html.slim
shopping.slim
nightview.slim

Which are using the same code, just different names in h1:

h1
  | Shopping ( or Area or Food... )
  = " (#{@number_of_recommends})"

= render partial: "layouts/paginator",
  locals: { total_items: @number_of_recommends, per_page: @limit, current_page: @page }

= render partial: "table", locals: { recommends: @recommends }

Can anyone help me refactor this code?

Upvotes: 1

Views: 36

Answers (1)

user229044
user229044

Reputation: 239240

You can (and should) have a single route, a single action, and a single view. The key is to make the variable portion of your URL into an actual variable. You do this using dynamic segments.

First, a single route. There is no need to use resources if you're not actually generating multiple RESTful actions:

get "/recommends/categories/:category" => "categories#show"

You can add criteria on what is allowed for the :category segment:

get "/recommends/categories/:category" => "categories#show", category: /food|area|shopping|nightview/

Next, a single action:

class CategoriesController < ApplicationController
  before_action :set_paginator

  def show
    # params[:category] is "food"/"area"/etc
    categories = UserRecommend.where(category: params[:category]).order('created_at desc')
    @recommends = categories.offset(@offset).limit(@limit)
    @number_of_recommends = categories.count
  end
end

Finally, a single view:

# app/views/categories/show.slim
h1
  = params[:category].capitalize
  = " (#{@number_of_recommends})"

= render partial: "layouts/paginator",
  locals: { total_items: @number_of_recommends, per_page: @limit, current_page: @page }

= render partial: "table", locals: { recommends: @recommends }

I would consider it better to use localization to turn the params[:category] into a title, which would give you more control, rather than relying on simple capitalization of the URL segment:

# app/views/categories/show.slim
h1
  = t params[:category]

And:

# config/locals/en.yml
en:
  categories:
    show:
      food: 'Food'
      area: 'Area'
      nightview: 'Night View'

Upvotes: 3

Related Questions