Skip to content
Dave Strus edited this page Jul 16, 2015 · 1 revision

In this unit, we will cover the following topics:

  • Partials
  • Private controller methods

In order to edit existing posts, we'll need an edit method in PostsController. Like show, it doesn't need to do anything apart from assigning the existing record to an instance variable.

app/controllers/posts_controller.rb

  def edit
    @post = Post.find params[:id]
  end

As for the view, at this point it doesn't need to be any different from new.html.erb. We may want to have some language or other elements unique to each page at some point, so rather than just rendering :show from the edit action, we'll extract the existing form into a partial, and render that partial in both new.html.erb and edit.html.erb

Create a new partial: app/views/posts/_form.html.erb

Paste the contents of new.html.erb into the new partial.

app/views/posts/_form.html.erb

<%= form_for @post do |f| %>
  <fieldset>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </fieldset>

  <% if f.object.link? -%>
    <fieldset>
      <%= f.label :link %>
      <%= f.text_field :link %>
    </fieldset>
  <% end -%>

  <% if f.object.text? -%>
    <fieldset>
      <%= f.label :body %>
      <%= f.text_area :body %>
    </fieldset>
  <% end -%>

  <%= f.hidden_field :post_type %>
  <%= f.submit class: 'btn btn-default' %>
<% end %>

Replace the contents of new.html.erb with a single render expression.

app/views/posts/new.html.erb

<%= render 'form' %>

Now create edit.html.erb with the same render expression.

app/views/posts/edit.html.erb

<%= render 'form' %>

Let's give ourselves a way to reach the edit page now.

On posts/index, let's add a list of "action" links for each post. For the moment, the only "action" will be edit.

app/views/posts/index.html.erb

  <li>
    <div class="title"><a href="<%= post_url(post) %>"><%= post.title %></a></div>
    <div class="tagline" title="<%= post.created_at %>">submitted <%= time_ago_in_words post.created_at %> ago</div>
    <ul class="actions">
      <li><%= link_to 'edit', edit_post_path(post) %></li>
    </ul>
  </li>

Now let's do something similar on pages/show. We'll place it below the body.

app/views/posts/show.html.erb

<article class="post">
    <div class="title"><a href="<%= @post.link %>" target="_blank"><%= @post.title %></a></div>
    <div class="tagline" title="<%= @post.created_at %>">submitted <%= time_ago_in_words @post.created_at %> ago</div>
    <section class="body"><%= @post.body %></section>
    <ul class="actions">
      <li><%= link_to 'edit', edit_post_path(@post) %></li>
    </ul>
</article>

This will look pretty awful if we don't add some styles, so throw this at the bottom of the stylesheet:

app/assets/stylesheets/style.scss

ul.posts, article {
  ul.actions {
    padding: 1px 0;
    list-style-type: none;
    li {
      display: inline-block;
      padding-right: 4px;
      font-size: x-small;
      line-height: 1.6em;
      white-space: nowrap;
      a {
        color: #888;
        font-weight: bold;
        padding: 0 1px;
        text-decoration: none;
      }
    }
  }
}

Now that we have our edit links, let's try them out. Be sure to try from both posts/index and posts/show. Notice that our FormHelper changes the text on the submit button.

If you go as far as submitting the edit form, you'll see a jarring reminder that we have no update method in our controller. Let's add that now.

LAB

Implement PostsController#update.

SOLUTION

To start, we'll find the existing post. Unlike in create, where we used a local variable post until we actually needed an instance variable, we're just going to use an instance variable from the get-go.

app/controllers/posts_controller.rb

  def update
    @post = Post.find params[:id]
  end

Next we'll attempt to update the post from params. Once again, we'll explicitly permit each attribute.

app/controllers/posts_controller.rb

    @post.update params.require(:post).permit(:title, :link, :body, :post_type)

Speaking of which, in the interest of not repeating ourselves let's extract that chain of methods on params into a private method. At the bottom of the controller, add the following:

app/controllers/posts_controller.rb

  private

  def post_params
    params.require(:post).permit(:title, :link, :body, :post_type)
  end

Now we can just call post_params in both create and update.

app/controllers/posts_controller.rb

12  def create
13    post = Post.new post_params
# ...
31  def update
32    @post = Post.find params[:id]
33    @post.update post_params

As we did in create, we're going to want to handle update failures differently from successful updates. So let's throw that @post.update into an if condition and proceed with redirects, renders, and flash messages as before. The only real differences: We may want to change the specific language in our flash message, and we want to render :edit upon failure, not :new.

app/controllers/posts_controller.rb

  def update
    @post = Post.find params[:id]
    if @post.update post_params
      redirect_to posts_path, flash: { notice: 'Your post was updated successfully.' }
    else
      flash.now[:error] = @post.errors.full_messages
      render :edit
    end
  end

When you can successfully update posts in various ways, can trigger all of the appropriate flash messages, and you make sure you didn't break create, it's time to commit.

We load a post record based on params[:id] in several of our controller actions. Let's add another private method to do this.

  def find_post
    @post = Post.find params[:id]
  end

Rather than directly calling this method from within each appropriate controller action, we can add a before_action (formerly before_filter in Rails 4+) to call the method before specific actions.

before_action :find_post, only: [:show, :edit, :update]

We can then remove @post = Post.find params[:id] from the individual controller actions.

$ git add .
$ git commit -m "Edit and update posts."