Expanded Encapsulation Through Our Models

Update a Record

Let's first look at the current controller code for updating a post:

class ApplicationController < ActionController::Base

  # ...

  def update_post
    update_query = <<-SQL
      UPDATE posts
      SET title      = ?,
          body       = ?,
          author     = ?
      WHERE posts.id = ?
    SQL
    connection.execute(update_query, params['title'], params['body'], params['author'], params['id'])

    redirect_to '/list_posts'
  end
end

Updating an existing post is essentially saving it back to the database with new values. So ideally, we'd like to be able to do the following:


# ...
def update_post
  post = Post.find(params['id'])
  post.set_attributes('title' => params['title'], 'body' => params['body'], 'author' => params['author'])
  post.save

  redirect_to '/list_posts'
end

# ...

We first retrieve the post from the database with its id passed through the URL, then set its attribute values with user inputs, and in the end save the post back to the database with updated values.

However, if we call save on an existing Post, it will create a new post in the database… with the INSERT SQL command. This is not what we want. When we update a record, it's always an existing record already in the database. When we try to update the record in the database, we should use the UPDATE SQL command instead of INSERT.

The way to tell if a record is a new record or not is to check its id column - if it's nil then it's a new record that has never been saved into the database; if it's not nil then it's an existing record since the id column is automatically generated by the database.

We'll adjust our model code to either INSERT or UPDATE records based on whether the post is a new or existing one.

### app/models/post.rb ###

class Post
  attr_reader :id, :title, :body, :author, :created_at

  # ...

  def new_record?
    id.nil?
  end

  def save
    if new_record?
      insert
    else
      update
    end
  end

  def insert
    insert_query = <<-SQL
      INSERT INTO posts (title, body, author, created_at)
      VALUES (?, ?, ?, ?)
    SQL

    connection.execute insert_query,
      title,
      body,
      author,
      Date.current.to_s
  end

  def update
    update_query = <<-SQL
      UPDATE posts
      SET title      = ?,
          body       = ?,
          author     = ?
      WHERE posts.id = ?
    SQL

    connection.execute update_query,
      title,
      body,
      author,
      id
  end

  # ...

end

First, we change Post#save to react conditionally, based on whether or not it's a new_record?. If it is, it calls insert, if not, update.

We also need to implement the set_attributes method for the Post class, and it's pretty straightforward. We can even refactor the the initialize method to call set_attributes method to remove some duplication.

app/models/post.rb

class Post
  attr_reader :id, :title, :body, :author, :created_at

  def initialize(attributes={})
    set_attributes(attributes)
  end

  def set_attributes(attributes)
    @id = attributes['id'] if new_record?
    @title = attributes['title']
    @body = attributes['body']
    @author = attributes['author']
    @created_at ||= attributes['created_at']
  end

  # ...

end

Note that we have made some changes within set_attributes. The id is only set if this Post is a new record. We always set the attributes for title, body and author, but we only conditionally set created_at. We do this because we only want to set created_at once: when the Post object is first created.

Delete a Post Model

The rest of this chapter will comprise of code related to deleting and listing posts. We want to write code that allows us to delete and list records using our models. The business logic and interactions with the database should be done through our model. The workflow will be similar to code we previously worked on in this chapter.

First we'll write the code for deleting a post. Remember, we want to have database logic inside our model; this applies to deleting a post as well.

# app/models/post.rb

class Post
  attr_reader :id, :title, :body, :author, :created_at

  # ...

  # used in application#delete_post
  def destroy
    connection.execute "DELETE FROM posts WHERE posts.id = ?", id
  end
end

Finally, we'll use that code and our Post model in the delete_post action.

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base

  # ...

  def delete_post
    post = Post.find(params['id'])
    post.destroy

    redirect_to '/list_posts'
  end

  # ...

end

List Posts

Now that we have updated our code for deleting a post, let's update our code for listing all posts. The various methods we'll need for grabbing those records from the DB (through our model) should also be implemented here.

To accomplish this task, we'll define a class method in the Post class that executes the SQL command to return all the posts in a hash, then we'll use map to create an array of Post objects from that.

# app/models/post.rb

class Post
  attr_reader :id, :title, :body, :author, :created_at

  # ...

  def self.all
    post_hashes = connection.execute("SELECT * FROM posts")
    post_hashes.map do |post_hash|
      Post.new(post_hash)
    end
  end

  # ...
end
# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  def list_posts
    posts = Post.all

    render 'application/list_posts', locals: { posts: posts }
  end
end

Remember, we'll now want to use our Post model methods in this view. That includes the getters for the various states of a Post model.

<!-- app/views/application/list_posts.html.erb -->

<html>
  <body>

    <div class="posts">
      <% posts.each do |post| %>
        <div class="post">
          <h2 class="title">
            <a href="/show_post/<%= post.id %>">
              <%= post.title %>
            </a>
          </h2>

          <small class="meta">
            <span class="author">by <%= post.author %> -</span>
            <em class="created_at"><%= post.created_at %></em>

            <a href="/edit_post/<%= post.id %>">Edit</a>

            <form method="post" action="/delete_post/<%= post.id %>" style='display: inline'>
              <input type="submit" value="Delete" />
            </form>
          </small>
        </div>
        <hr />
      <% end %>
    </div>

    <a href="/new_post">New Post</a>

  </body>
</html>

Now that we've done all that, our blog app has all the conveniences that a model gives us, at least for our posts. In the next chapter, we'll add in some validations, and we'll show how they work in the process.