Creating Custom Actions via Button Click for Admins
Learn how to develop buttons that perform custom actions, such as approving items in the database and moving them down the approval workflow.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

In this video, we are going to create the functionality for the "approve" and "review" buttons on our admin homepage.

It's fairly straightforward for the "review" button, as it should just take the admin to edit page. So, simply change the path to edit_post_path and pass pending approval to it, just like this:

<!-- app/views/static/_pending_approval.html.erb -->

<div class='col-md-6 column'>
  <%= link_to 'Review', edit_post_path(pending_approval), class: 'btn btn-warning btn-block' %>
</div>

As for "approve", we want the admin to have the ability to approve a post just by clicking it. This is going to be a little bit more complex than "review". So, we'll have to create a test for it. Before that, let's create a new spec file in our features folder, and call it homepage_spec.rb.

In this file, we are going to begin by including the rails_helper, and then create a describe block called homepage. To test this, we need a post and an admin user, so we're creating both using FactoryGirl. Next, we want the test case to visit the homepage and then, click on the "approve" button. We need an ID for this though, so we're going to add an id for the "approve" button in our _pending_approval.html.erb file.

<!-- app/views/static/_pending_approval.html.erb -->

<div class='col-md-6 column'>
  <%= link_to 'Approve', approve_post_path(pending_approval), class: 'btn btn-success btn-block', id: "approve_#{pending_approval.id}" %>
</div>

So, now we want the test to expect the post to have a status of approved.

# spec/features/homepage_spec.rb

require 'rails_helper'

describe 'Homepage' do
  it 'allows the admin to approve posts from the homepage' do
    post = FactoryGirl.create(:post)
    admin_user = FactoryGirl.create(:admin_user)
    login_as(admin_user, :scope => :user)

    visit root_path

    click_on("approve_#{post.id}")

    expect(post.reload.status).to eq('approved')
  end
end

As expected, the test fails, and that's what we want.

To fix, we have to do a few things. First, open the routes.rb file and add a new route inside the resources block.

# config/routes.rb

resources :posts do
  member do
    get :approve
  end
end

In this code, we are saying posts should have a new route called "approve." Let's quickly check it in the console. Run rake routes | grep approve, and you can see the new route.

large

Next, open the posts_controller.rb file, and create a new method called approve. Also, we need to add :approve to the before_action callback. In the approve method, we simply have to set the status as "approved" and redirect the user to homepage. We should also have a notice that says the post has been approved.

# app/controllers/posts_controller.rb

def approve
  @post.approved!
  redirect_to root_path, notice: "The post has been approved"
end

Lastly, go to _pending_approval.html.erb and change the path to approve_post_path and send pending_approval as its parameter.

<!-- app/views/static/_pending_approval.html.erb -->

<div class='col-md-6 column'>
  <%= link_to 'Approve', approve_post_path(pending_approval), class: 'btn btn-success btn-block', id: "approve_#{pending_approval.id}" %>
</div>

Let's run rspec and see if everything works. And it's all fine.

Open the browser and click on the "approve" button. It works great!

Before we wrap up, I want to check if this can be hacked. Say an employee who is not an admin can find the post id, type it in the browser like this: "localhost:3000/posts/15/approve", and this would be approved. That's definitely something we need to fix it because we don't want any employee doing that.

To fix this, go to posts_controller.rb, and call the authorize method, like this:

# app/controllers/posts_controller.rb

def approve
  authorize @post
  @post.approved!
  redirect_to root_path, notice: "The post has been approved"
end

Also, we have to add the approve method to our policy, and just call the admin method from it. Since we already have a method that checks if the user is an admin user, it makes no sense to duplicate that code.

# app/policies/post_policy.rb

def approve?
  admin?
end

Now, if you try doing the same hacking action as before, you'll be redirected to the homepage and you'll get a notice saying that you're not authorized to perform this function.

Resources