Creating Simple Blogging Platform - Building Posts

By:, On:

This tutorial assumes you have already completed:

  1. Install Ruby on Rails
  2. Create Ruby on Rails application
  3. Create Static Pages - without this deploy will not work
  4. Install Git
  5. Create Remote Git Repository - optional but recommended
  6. Deploy application to Heroku
  7. Manage users with Devise
  8. How to add Twitter Bootstrap to Ruby on Rails application - Advised
  9. Creating Simple Blogging Platform - Creating Model
  10. Creating Simple Blogging Platform - Routes
  11. Creating Simple Blogging Platform - Displaying Posts

Now that we have a way to display the posts, we can go ahead and start creating them using forms.

Step 1: Create "New" Action

We'll begin by adding "new" action to our controller:

/app/controllers/posts_controller.rb


class PostsController < ApplicationController

    def index
        @posts = Post.all
    end

    def show
        @post = Post.find(params[:id])
    end

    def new
    end

end

Step 1.2: "Instantiate" Post in Controller

This time inside of the action we will "instantiate" new post and save it into variable. Just as we did before in the command line:

/app/controllers/posts_controller.rb

def new
    @post = Post.new
end

Step 1.3: Create HTML File

We can now proceed with creating a template for new posts. Inside /app/views/posts/ folder create new file and call it "new.html.erb" and add the following code:

/app/views/posts/new.html.erb


    <%= form_for @post do |f| %>
        <div class="form-group">
            <%= f.label :title, class: 'control-label' %>
            <%= f.text_field :title, class: 'form-control' %>
        </div>

        <div class="form-group">
            <%= f.label :body, class: 'control-label' %>
            <%= f.text_area :body, class: 'form-control', rows: 6 %>
        </div>

        <%= f.submit 'Save', class: 'btn btn-primary' %>
    <% end %>

Step 1.4: Add Link

Let's put the link to this page on our index.

/app/views/posts/index.html.erb


    <h1>This is a list of posts</h1>
    <%= link_to 'New Post', new_post_path %>
    <ul>
        <% @posts.each do |p| %>
            <li>
                <h3><%= link_to p.title, post_path(p.id) %></h3>
                <p><%= p.body %></p>
            </li>
        <% end %>
    </ul>

We can now navigate to the page. If you try to submit the form, it will still crash since it wants to post data to "create" action that we haven't create.

Step 2: Create action

Open posts_controller.rb and the code from line 16 to line 30:

/app/controllers/posts_controller.rb


class PostsController < ApplicationController

    def index
        @posts = Post.all
    end

    def show
        @post = Post.find(params[:id])
    end

    def new
        @post = Post.new
    end

    def create
        @post = Post.new(allowed_params)

        if @post.save
            flash[:success] = "Created new post"
            redirect_to @post
        else
            render 'new'
        end
    end

    private
        def allowed_params
            params.require(:post).permit(:title, :body)
        end
end

We wrote quite a few lines of code. First we need to look at the code under private declaration (everything under private is only available inside the controller - security feature). We defined allowed_params here. This is a feature that came with Rails 4 as a improved security standard. We won't be going now into details of why it's done the way it is. What you need to remember is that you have to add all parameters you are including in the form to "permitted" params. Otherwise, they won't be saved upon submission.

Now, let's look at the actual action. In the first line you can see:


@post = Post.new(allowed_params)

We created variable @post and are using new method on Post class. As arguments we're passing allowed_params we have defined under private declaration.

Method new doesn't save our post to the database. Instead, it stores the post in its memory until we decide to save it. We will attempt to do that in the next step. If the save attempt fails, @post.save will return false and action will re-render "new" page, if action succeeds we will receive notification that we were successful and action will redirect us to show page of the @post.

Why would the "save" fail? Perhaps the title is empty while we require some content there or maybe user wants to submit post with an empty body.

At the moment we have no way to reject those kinds of submissions. You can try it out; our app will save any kind of record at this point. No body or title. That's not good so we need to fix that.

Step 2.2: Add Validations in Model

At this point, we will go back to our model and add some validations to it.

/app/models/post.rb


class Post < ActiveRecord::Base
    validates :title, presence: true, length: { minimum: 3 }
    validates :body, presence: true, length: { minimum: 30 }
end

This will make sure that both post's title and body are present and have at least 3 and 30 characters respectively.

Step 2.3: Create New Files

Now, we need to make sure our users get notified about errors if there are any. To do that, we need to build mechanism to display error messages inside the form. Inside /app/views create new folder and call it "shared". Inside of that folder create new file and call it "_error_messages.html.erb". Make sure you add "_" at the beginning of the name of the file. In the new file, copy-paste the following code:

/app/views/shared/_error_messages.html.erb


<% if object.errors.any? %>
    <div id="error_explanation">
        <div class="alert alert-danger alert-dismissable">
            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
            <p><strong>This form contains <%= pluralize(object.errors.count, 'errors') %>.</strong></p>
            <ul>
                <% object.errors.full_messages.each do |msg| %>
                <li><%= msg %></li>
                <% end %>
            </ul>
        </div>
    </div>
<% end %>

Now, we can go back to our form and render this "partial" there. Code is build in a way that error container will appear only if an error occurs.

/app/views/posts/new.html.erb


    <%= form_for @post do |f| %>
        <%= render 'shared/error_messages', object: f.object %>
        <div class="form-group">
            <%= f.label :title, class: 'control-label' %>
            <%= f.text_field :title, class: 'form-control' %>
        </div>

        <div class="form-group>
            <%= f.label :body, class: 'control-label' %>
            <%= f.text_area :body, class: 'form-control', rows: 6 %>
        </div>

        <%= f.submit 'Save', class: 'btn btn-primary' %>
    <% end %>

Give it a try.

Step 3: Post ownership

The only problem with the setup above is that there has been no way for us to record who is the author of the post. Right now anyone can come to our website and create posts, even when they are not logged in. This is obviously not the behavior we're looking for. We want users to create account first to be able to post. Also, for each post, we want to display who the author is.

To reference user in the post record we need to add new column to the posts table in the database. This can be easily done with rails generator for migrations. In bash(terminal window):

bash


$ rails generate migration add_user_id_to_posts user_id:integer

Notice how we pass arguments to the generator - user_id:integer. Rails will also pick up the name of the table to alter: posts.


      invoke  active_record
      create    db/migrate/20140512101508_add_user_id_to_posts.rb

Let's have a look the newly created migration file

db/migrate/20140512101508_add_user_id_to_posts.rb


class AddUserIdToPosts < ActiveRecord::Migration
  def change
    add_column :posts, :user_id, :integer
  end
end

Step 3.2: Run Migrate

We can now migrate the database with rake task:

bash


$ rake db:migrate

== 20140512101508 AddUserIdToPosts: migrating =================================
-- add_column(:posts, :user_id, :integer)
   -> 0.0047s
== 20140512101508 AddUserIdToPosts: migrated (0.0048s) ========================

Step 3.3: Modify Post and Users Files

Now, we need to let our application know about the existing relationship between posts and users. Add line 4 after class User < ActiveRecord::Base.

app/models/user.rb


class User < ActiveRecord::Base

  has_many :posts, dependent: :destroy

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

Add line 4 in post.rb:

app/models/post.rb


class Post < ActiveRecord::Base

  belongs_to :user

  validates :title, presence: true, length: { minimum: 3 }
  validates :body, presence: true, length: { minimum: 30 }
end

Those two lines has_many and belongs_to give us ability to create posts "through" user which will automatically inject user_id into post record. To see this works, let's change posts controller to take advantage of that:

app/controllers/posts_controller.rb


  def create

    @user = current_user
    @post = @user.posts.build(allowed_params)

    if @post.save
      flash[:success] = "Created new post"
      redirect_to @post
    else
      render 'new'
    end
  end

And also add author to our views:

app/views/posts/index.html


    <ul>
        <% @posts.each do |p| %>
            <li>
                <h3><%= link_to p.title, post_path(p.id) %></h3>
                <p><small><strong>By: </strong><%= p.user.email %></small></p>
                <p><%= p.body %></p>
            </li>
        <% end %>
    </ul>

app/views/posts/show.html


    <h1><%= @post.title %></h1>
    <p><small><strong>By: </strong><%= @post.user.email %></small></p>
    <p><%= @post.body %></p>

Step 4: Run Rails Console

If you try reloading your page, you'll see that it crashes. It's because the posts we've created before didn't contain any user_id. We need to clear the database. Open rails console in terminal:

bash


$ rails console

2.1.1 :001 > Post.destroy_all

Step 5: Use Devise

Before we start creating posts, we need to make one more adjustment. Right now, we're using current_user for creating posts. As we already learned current_user is only present if user is logged in. What if a user who isn't log in tries to submit a new post? The application will crash. Fortunately, the fix is very easy with Devise.

app/controllers/posts_controller.rb


class PostsController < ApplicationController

    before_filter :authenticate_user!, except: [:show, :index]

    # existing code
end

:authenticate_user!is a method provided by Devise. It will allow only logged in users to all actions, except the ones we specified with except. But it doesn't stop there. If a user tries to access restricted action, devise will redirect them to the login page. After they either login or register, they will be taken back to the place they wanted to access at the first place. Go ahead and play with it

Editing Posts

If you login you will be able to mark this tutorial as finished to track your progress



Comment

You can login to comment