Hello, and welcome to the latest episode of the blog! Today we’ll be discussing my latest portfolio project, FlatCollab.
FlatCollab (live version on Heroku / code on Github) is a tool to help make finding partners for and working on group projects a little easier. You can post a public call for partners on a project or answer someone else’s call to join their team, and then use a shared task list to figure out how to split up the various parts of the project.
All in all, it’s a pretty cool app. But you’re not here to hear about the finished app (though you can check it out on Heroku). You’re here to hear about the cool tricks I used to build it.
We mostly see these used as delete buttons in the Learn curriculum, but they’re also really useful when you want to allow the user to do things like claim one task from a shared list. I’m going to walk through the process of creating that “Claim Task” button as an example.
The first thing you need when creating a single-button form is a route. The button needs somewhere to send its information, and a controller action to handle it. My routes file looked like this:
resources :teams do
I could have used the built-in Rails
update action… but I had plans for that route already, since I also wanted an edit form. I needed a custom route for my “Claim Task” button.
That route also needed to contain as much of the information needed to perform the action as possible — in this case the team id and task id, so I could find the correct record to update.
I could have included these things as hidden fields, but that’s not the best security practice — anyone who knows how to use Chrome Dev Tools can mess with your params. Much better to draw this information directly from the route.
So, with all that in mind, I updated my
resources :teams do
patch '/teams/:team_id/tasks/:id/claim', to: "tasks#claim", as:
Then I defined the
claim action in the Tasks controller:
current_team = Team.find(params[:team_id])
current_task = current_team.tasks.find(params[:id])
Which called on a custom attribute writer in the Task model to assign the user and change the task.status from “unclaimed” to “claimed”:
self.users << user
self.status = "claimed"
And then redirected back to the tasks index page.
Now I had my routes and controller actions set up, it was time for the most important step — the visible button itself.
To create the form and button, I tried a couple of different Rails helpers.
button_to didn’t have the flexibility I wanted, and
form_for’s automatic routing made it difficult to get the form to route properly - it kept wanting to send its request to
tasks#update instead of my custom action,
tasks#claim. Eventually, I decided to step down one level of abstraction and rely on good old
<%= form_tag claim_task_path(team, task), method: "patch", class: "ui inline form" do %>
<%= submit_tag "Claim Task", class: "mini ui green button" %>
<% end %>
Now, when a user clicks that button, the form takes over and sends a
patch request to
claim_task_path, which goes to the
tasks#claim controller action, updating the record, then refreshing the page to show the update.
Devise comes with a lot of magic out of the box, from its ready-made views to its predefined controller actions. But sometimes with a gem like that, you love everything… except maybe you want to change one little thing.
For me, it was user registrations. By default, Devise signs users up with an email and password, but I wanted them to have a name as well (it’s awkward to use an email address in user dialogues!).
Normally, this would be a relatively straightforward tweak — generate a migration to add a column to the
users table, update the form, update the
user_params to allow
I could do the first two, easily. But because Devise’s controllers are hidden away, it wasn’t immediately obvious how to go about altering the
Luckily, I remembered to check the Devise documentation before I lost much time trying to figure it out.
Turns out Devise magic doesn’t end with views and controllers — it also comes with a sophisticated set of configuration options, and excellent documentation.
All I needed to do was change one little option in the
user_params, and Devise has a built-in way to do just that, using a
before_action filter in the
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
This code hooks into the Devise controllers and adds
:name to the list of permitted keys in the strong params. Just like that, my users had names!
Like all of the software I’ve built so far, this Rails app has been an incredible learning experience. I can only hope that all of my projects provide me with such insight and education.
Originally published at clarehorton.com on December 13, 2016.