2

I want to make a feature to show posts with specific categories, A, B, C..., where there can be more than one category. Currently, I can add only one.

How can I add more and show them?

This is the migration:

class CreateCategories < ActiveRecord::Migration[6.0]
  def change
    create_table :categories do |t|
      t.string :name
      t.text :description
      t.boolean :display_in_navbar, default: true

      t.timestamps
    end
  end
end


class CreatePosts < ActiveRecord::Migration[6.0]
  def change
    create_table :posts do |t|
      t.string :title
      t.text :body
      t.string :author
      t.boolean :featured
      t.integer :likes
      t.string :source
      t.references :category, null: false, foreign_key: true

      t.timestamps
    end
  end
end

My models:

class Category < ApplicationRecord
  has_and_belongs_to_many :posts
end


class Post < ApplicationRecord
  has_and_belongs_to :categories
end

And the view:

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Body</th>
      <th>Author</th>
      <th>Featured</th>
      <th>Likes</th>
      <th>Source</th>
      <th>Category</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.title %></td>
        <td><%= post.body %></td>
        <td><%= post.author %></td>
        <td><%= post.featured %></td>
        <td><%= post.likes %></td>
        <td><%= post.source %></td>
        <td><%= post.category.name %></td>
        <td><%= link_to 'Show', post %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

This is _form.html.erb:

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Body</th>
      <th>Author</th>
      <th>Featured</th>
      <th>Likes</th>
      <th>Source</th>
      <th>Category</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.title %></td>
        <td><%= post.body %></td>
        <td><%= post.author %></td>
        <td><%= post.featured %></td>
        <td><%= post.likes %></td>
        <td><%= post.source %></td>
        <td><%= post.category.name %></td>
        <td><%= link_to 'Show', post %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

and _post.json.jbuilder:

json.extract! post, :id, :title, :body, :author, :featured, :likes, :source, :category_id, :created_at, :updated_at
json.url post_url(post, format: :json)

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Bongoja
  • 27
  • 6
  • Welcome to SO. Please see "[ask]", "[Stack Overflow question checklist](https://meta.stackoverflow.com/questions/260648)" and _all their linked pages_; They'll help you understand how to ask questions more clearly and concisely. – the Tin Man May 18 '20 at 19:01

2 Answers2

4

Looking at the has_and_belongs_to_many guide, notice the references don't go into categories nor posts, but to a join table in the middle, in this case categories_posts.

create_table :categories_posts, id: false do |t|
  t.belongs_to :category
  t.belongs_to :post
end

Or use create_join_table to do the equivalent.

create_join_table :categories, :posts do |t|
  t.index :category_id
  t.index :post_id
end

And remove the reference to category in the posts table.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • so do I need to generate another model called CategoryPost? – Bongoja May 18 '20 at 22:18
  • 1
    @Bongoja Yes. Follow the instructions in the guide that are linked at the beginning of the answer. A many-to-many relationship requires a join table. See these answers for more about setting up table relationships in SQL: https://stackoverflow.com/questions/7296846/how-to-implement-one-to-one-one-to-many-and-many-to-many-relationships-while-de – Schwern May 18 '20 at 22:20
1

Run rails g migration CreatePostCategory post:references category:references and remove reference of category from post rails g migration RemoveCategoryFromPosts category:references, this will look like as below.

class RemoveCategoryFromPost < ActiveRecord::Migration
  def change
    remove_reference :posts, :category, index: true, foreign_key: true
  end
end

then run rails db:migrate. This will work for you.

Model will remain same.

class Category < ApplicationRecord
  has_and_belongs_to_many :posts
end


class Post < ApplicationRecord
  has_and_belongs_to :categories
end