Create categories for your Rails app

Implementing categories in Ruby on Rails

Swaathi Kakarla
Sudo vs Root

--

It’s easier than you think!

We’ve been working with Ruby on Rails for about 20 days now, and it is honestly the most efficient and secure framework we’ve ever come across. The learning curve was a bit steep in the beginning, but we overcame that with the huge community support and tutorials available for ROR. We’re actually quite proud that we built a product with ROR having absolutely no experience in it. We did it! And so can you ☺

One module I was stuck with for quite some time was building hierarchies or categories. There are some gems that do this, like acts_as_taggable_on and act_as_tree. But I wanted something from scratch. Well it took me a lot of time to find the bits and pieces I needed, and when I did, I wanted to share it with the rest of the world.

Okay. Enough of backstory. Let’s get to it.

With categories, you need to select one model which behaves as your category and “has many” products and a product model that “belongs to” a single category. This tutorial teaches you how to do a one-to-many relationship category, but if you need, you can modify this to reflect a many-to-many relationship too.

If you haven’t done so already, create a scaffold for Category and Product. Open up your Rails console and type,

rails g scaffold product name:string price:string category_id

and,

rails g scaffold category name:string desc:text

Category_id column in the Product model acts a foreign key between Product and Category.

Note: If you already have a model and you want to add a hierarchy to it, then add this column to your model instead of creating a new scaffold. Use,

rails generate migration AddColumnsToProducts category_id —force

Then do a,

rake db:migrate

Now you’ve created your two models. It’s time to define the relationship between them. Open up your Product model and add,

#app/models/product.rb
belongs_to :category

And in your Category model,

#app/models/category.rb
has_many :products

Things don’t end right there. You want to modify your submission form for a new Product to include a way for the user to specify what category the new product belongs to. The best way to do this is to provide the user a drop down of all the available categories. We’ll be using the select_tag field available in Ruby to provide the user a drop down. There are many other field s for this, and you can read about them here.

Here’s what select_tag is all about,

The #select_tag will create the surrounding tag while the #options_for_select gives #select_tag the array of options it needs. #options_for_select expects a very specific input — an array of arrays which provide the text for the dropdown option and the value it represents. So options_for_select([[“choice1",1],[“choice2",2]]) creates a couple of option tags, one for each choice. This is great, because that’s exactly what #select_tag expects for its second argument. The only wrinkle here is that you need to convert your @categories collection, which has full Category objects, into a simple array with just name and value. That’s easy using #map!

Open up your Products controller and add this to your New method.

#app/controllers/products_controller.rb
def new @product = Product.new @categories = Category.all.map{|c| [ c.name, c.id ] }
end

And then in your Products view, add this to your _form.html.erb.

app/views/products/_form.html.erb
<%= select_tag(:category_id, options_for_select(@categories), :prompt => “Select one!”) %>

Note: If you created a scaffold, Rails automatically creates a text field for you to enter category_id, since we are creating a drop down for the same, you can remove it.

Here’s how the form submission should look.

What this select tag does is, reads the @categories that you created in the New method, and displays it as an option in the drop down. Once a user selects what category he wants, the form communicates withe the Create method of your Product controller and passes this information via URL. The information is saved in a parameter called “category_id”, which is your first parameter in select_tag. So in your Create method, just retrieve that information and store it.

Open up your Product controller again, and add this to your Create controller,

def create 
@product = Product.new(product_params)
@product.category_id = params[:category_id]
respond_to do |format|
if @product.save
format.html { redirect_to @product, notice: ‘Product was successfully created.’ }
format.json { render :show, status: :created, location: @product }
else
format.html { render :new }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end

Note: You can watch your Rails console when creating a new post, you’ll be able to see the response to the create controller which contains a parameter called category_id with information.

Another important module we should care about when changing the New and Create controller is the Edit and Update controller. If you ever want to edit the category of a product, this would be helpful.

Open up your Edit method and add,

#app/controllers/product_controller.rb
@categories = Category.all.map{|c| [ c.name, c.id ] }

And in your Update method add,

@product.category_id = params[:category_id]

And there you have it!

But we’re still not quite there yet. We need to modify the views now.

Add this to your Index of Products,

#app/views/products/index.html.erb
<td><%= link_to Category.find(product.category_id).name, category_path(product.category_id) %></td>
And there you have it.

We’re providing a link to the category show page in each listing of a product. You can easily identify the name of the category using the find method available for all models.
We’re also providing the path to the Show of Category by passing the ID of the category we want to display.

Now, you want to display all the products under a certain Category. To do this, open up your Show file of Category and add,

#app/views/categories/show.html.erb
<h3> Products under this category </h3>
<% Product.where(category_id: @category.id).each do |product| %>
<li> <%= product.name %> </li>
<% end %>

We’re querying the table with a simple where clause, to retrieve all products with the same category ID. And then displaying it. Just like this.

Ta da!

This post originally appeared on the Skcript blog. Hear us make some noise near the equator. ;)

--

--

Swaathi Kakarla
Sudo vs Root

Co-Founder and CTO @SkcriptHQ | Loves to code | GDG-CHN Organizer | https://swaathi.com