My aim is to organize uploaded posts by categories. These categories are shown on the nav bar and if you click one of the category's names, you will see the posts assigned with the category. When uploading a post, you can assign it with multiple categories too. So, I think this is like many posts can have many categories and many categories can have many posts.
This is how I want my posts organized by categories
However, I cannot set things right in my posts_controller.rb, posts/index.html.erb, posts/show.html.erb, and _navigation.html.erb
post.rb
class Post < ActiveRecord::Base
#This validates presence of title, and makes sure that the length is not more than 140 words
validates :title, presence: true, length: {maximum: 140}
#This validates presence of body
validates :body, presence: true
has_many :categorizations
has_many :categories, :through => :categorizations
end
category.rb
class Category < ApplicationRecord
has_many :categorizations
has_many :posts, :through => :categorizations
end
categorization.rb
class Categorization < ApplicationRecord
belongs_to :post
belongs_to :category
end
Then, here are these controller and views I am confused with:
posts_controller.rb
class PostsController < ApplicationController
before_action :find_post, only: [:edit, :update, :show, :delete]
before_action :authenticate_admin!, except: [:index, :show]
# Index action to render all posts
def index
if params.has_key?(:category)
@category = Category.find_by_name(params[:category])
@posts = Post.where(category: @category)
else
@posts = Post.all
end
end
# New action for creating post
def new
@post = Post.new
end
# Create action saves the post into database
def create
@post = Post.new(post_params)
if @post.save
flash[:notice] = "Successfully created post!"
redirect_to post_path(@post)
else
flash[:alert] = "Error creating new post!"
render :new
end
end
# Edit action retrives the post and renders the edit page
def edit
end
# Update action updates the post with the new information
def update
@post = Post.find(params[:id])
if @post.update_attributes(post_params)
flash[:notice] = "Successfully updated post!"
redirect_to posts_path(@posts)
else
flash[:alert] = "Error updating post!"
render :edit
end
end
# The show action renders the individual post after retrieving the the id
def show
end
# The destroy action removes the post permanently from the database
def destroy
@post = Post.find(params[:id])
if @post.present?
@post.destroy
flash[:notice] = "Successfully deleted post!"
redirect_to posts_path
else
flash[:alert] = "Error updating post!"
end
end
private
def post_params
params.require(:post).permit(:title, :body, category_ids: [])
end
def find_post
@post = Post.find(params[:id])
end
end
index.html.erb
<div class="container">
<div class="col-sm-10 col-sm-offset-1 col-xs-12">
<% @posts.each do |post| %>
<div class="col-xs-12 text-center">
<div class="text-center">
<h2><%= post.title %></h2>
<h6><%= post.created_at.strftime('%b %d, %Y') %></h6>
</div>
<div>
<%= raw post.body.truncate(358) %>
</div>
<div class="text-center">
<%= link_to "READ MORE", post_path(post) %>
</div>
<% if admin_signed_in? %>
<%= link_to "Show", post_path(post), class: "btn btn-primary" %>
<%= link_to "Edit", edit_post_path(post), class: "btn btn-default" %>
<%= link_to "Delete", post_path(post), class: "btn btn-danger", data: {:confirm => "Are you sure?"}, method: :delete %>
<% end %>
<hr />
</div>
<% end %>
</div>
</div>
show.html.erb
<div class="col-sm-11 col-xs-12 blog-content">
<h2 class="text-center"><%= @post.title %></h2>
<h1 class="text-center"><%= @category.name %></h1>
<h5 class="text-center"><%= @post.created_at.strftime('%b %d, %Y') %></h5>
<div class="text-center"><%= raw @post.body %></div>
</div>
_navigation.html.erb(part of it)
<ul class="nav navbar-nav navbar-left">
<% Category.all.each do |cat| %>
<li class="text-center"><%= link_to cat.name, posts_path(category: cat.name) %></li>
<% end %>
</ul>
Just in case, schema.rb
ActiveRecord::Schema.define(version: 2018_11_07_082317) do
create_table "admins", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "avatar"
t.index ["email"], name: "index_admins_on_email", unique: true
t.index ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true
end
create_table "categories", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "categories_posts", id: false, force: :cascade do |t|
t.integer "category_id"
t.integer "post_id"
end
create_table "categorizations", force: :cascade do |t|
t.integer "post_id"
t.integer "category_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "ckeditor_assets", force: :cascade do |t|
t.string "data_file_name", null: false
t.string "data_content_type"
t.integer "data_file_size"
t.string "type", limit: 30
t.integer "width"
t.integer "height"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["type"], name: "index_ckeditor_assets_on_type"
end
create_table "posts", force: :cascade do |t|
t.string "title"
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
In show view you have
<h1 class="text-center"><%= @category.name %></h1>
But you don't define @category
in show action. If you want to list categories, it should be
<h1 class="text-center"><%= @post.categories.pluck(:name).join(', ') %></h1>
Btw, looks like you have useless table categories_posts
in the schema.rb
Update:
About index action - you should change query for @posts
, since the post doesn't have a category column, but he has categories
association:
def index
if params.has_key?(:category)
# you can remove @category defining if you don't need it somewhere in view
@category = Category.find_by_name(params[:category])
@posts = Post.joins(:categories).where(categories: { name: params[:category] } )
else
@posts = Post.all
end
end
Note, it is better to use id for the query, not name, searching by id is faster. You need to change link in navbar to link_to cat.name, posts_path(category: cat.id)
for it and replace name
with id
in the query. And it is better to move the whole query to named scope in the Post model.