In the previous part of the series, I have shown how to quickly create a set of models, controllers, and views, plus some other useful stuff, to implement a tool to support the Holocracy method of running an organization. However, the tool was not much more than a database browser at this time, which is neat enough for ten minutes of work, but let’s go a little further.

Filling roles

For one thing, I have made a conceptual mistake. I have constrained the reference from a Role to a Person so that it cannot be empty (or null). But that does not reflect reality very well. In an organization we often have the situation that a role is defined but it is not filled by a person. Of course, we want to fill it and sometimes there will be someone filling it ad interim, but still I feel I should not exclude the possibility that a specific role is unfilled. So I will remove the not-null constraint from the Person reference in Role. I will do this as a database migration, so that the change is formulated in code and can be reproduced on any production database that I might have.

bin/rails generate migration RemoveNotNullConstraintFromPersonInRoles
Running via Spring preloader in process 888
      invoke  active_record
      create    db/migrate/20210108172101_remove_not_null_constraint_from_person_in_roles.rb

The migration code looks like this. I have already inserted line 3 where I tell Rails to set the null constraint to true.

class RemoveNotNullConstraintFromPersonInRoles < ActiveRecord::Migration[6.0]
  def change
    change_column_null :roles, :person_id, true
  end
end
$ bin/rails db:migrate
== 20210108172101 RemoveNotNullConstraintFromPersonInRoles: migrating =========
-- change_column_null(:roles, :person_id, true)
   -> 0.0776s
== 20210108172101 RemoveNotNullConstraintFromPersonInRoles: migrated (0.0777s)

Running the migration yields the new database schema but I also need to adapt the model by marking the relation optional, and by adjusting the validation so that the UI will not complain about missing people.

class Role < ApplicationRecord
  belongs_to :person, optional: true
  belongs_to :circle

  validates :name, presence: true
  validates :circle, presence: true
end

Next, I will change the input field in the Role for to a selection box, so that the user can choose from the people in known to the tool. Rails has a helper function for this common requirement, called collection_select. Spot it in the code below.

<%= form_with(model: role, local: true) do |form| %>
...

  <div class="field">
    <%= form.label :person_id %>
    <%= form.collection_select :person_id, Person.order(:name), :id, :name, { include_blank: true } %>
  </div>

...
<% end %>

What it means is: Create an HTML select element to fill the person_id attribute. Use the list of all Persons in the database, ordered by their name, as options. Use the id attribute of the selected person but display the name attribute to the user. Include a blank selection to represent that no person currently fills this role. Rails’ helpers save a lot of time.

## Dependent Resources

Up to now, I have treated Roles as if they were completely independent entities. But in reality, every Role belongs to one circle. It does not exist outside its circle, and there should be no way to create roles that do not belong to a pre-existing circle. For this, Rails has the concept of dependent resources. Let’s have a look at the file holo/config/routes.rb that was generated along with our scaffolds:

 Rails.application.routes.draw do
   resources :roles
   resources :circles
   resources :people
   # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
 end

The resources keyword means that Rails will route the usual REST requests to the corresponding controllers, e.g. GET /circles will be routed to the the index method in the CircleController class. To make the roles dependend on circles, I will emply a little Rails syntax:

Rails.application.routes.draw do
  resources :circles do
    resources :roles, shallow: true
  end
  resources :people
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

By putting the roles resources inside the circles resource, we cannot simply call GET /roles/new anymore. We must call GET /circles/:id/roles/new where :id is the ID of a specific circle. The phrase shallow: true means that this only happens for listing all roles (the index method in the controller) and for creating new resources within the scope of that circle. Showing and editing existing roles will not require the long URL because they do not break the link between a role and the circle it belongs to. This is not very important. I just like to do it because it avoids unnecessarily long URLs.

Next, we must adapt the Role controller and its views to reflect the change. The first part of the RolesController class currently looks like this, all generated code:

class RolesController < ApplicationController
  before_action :set_role, only: [:show, :edit, :update, :destroy]

  # GET /roles
  # GET /roles.json
  def index
    @roles = Role.all
  end

...

Now I only want to show the roles that belong to the current circle. The ID of the circle is automatically passed in the request parameters, so I just have to replace Role.all with an appropriate query:

...

  # GET /circle/:circle_id/roles
  # GET /circle/:circle_id/roles.json
  def index
    @circle = Circle.find(params[:circle_id])
    @roles = Role.where(circle: @circle)
  end

...

Note that I have also adapted the comment to reflect the new routes. This is just for my information.

Now I will change the index view in holo/app/views/roles/index.html.erb so the the link to create a new role passes the current circle ID. The old line looked like this:

...


<%= link_to 'New Role', new_role_path %>

But now I have changed it to:

...

<%= link_to 'New Role', new_circle_role_path(@circle) %>

Here, I am using another helper that creates a route for my dependent resource. If you try and hover over the link, your browser will show the generated URL:

A dependent resource

Now, I need to adjust the new method in the RolesController. Here is the generated code:

...

  # GET /roles/new
  def new
    @role = Role.new
  end

...

I will adjust the comment to reflect the new route, and I need to extract the circle from the URL parameter, and then set it to be the circle of the newly created role. Easy enough.

# GET /circles/:circle_id/roles/new
def new
  @circle = Circle.find(params[:circle_id])
  @role = @circle.roles.new
end

Now let’s look at the views. First, the new view still has a link back to the list of roles, and this will not work anymore because the list of roles is now always linked to a circle. So I will change that.

<h1>New Role</h1>

<%= render 'form', circle: @circle, role: @role %>

<%= link_to 'Back', circle_roles_path(@circle) %>

Can you spot the changes? In line 3, I have added another parameter to the form, the @circle variable, so that the form can use it. Also, I have changed the back link to use a different helper function that will return the right URL to the list of roles for this circle.

By the way, the way Rails creates these scaffolds, the same form is used for new entities and to edit existing entities. This is why the actual form is not implemented in the views themselves, like here, but included as what Rails calls a “partial”. Essentially, this is a plain old include.

The form tag itself is created with a form helper method, that needs to know about the fact the resource is dependent on another resource. This is because the HTML FORM tag needs an URL which has to be in the correct format. Fortunately the form helper is smart, so the change is easy.

Generated code:

<%= form_with(model: role, local: true) do |form| %>
...
<% end %>

Adapted code:

<%= form_with(model: [circle, role], local: true) do |form| %>
...
<% end %>

See how I just passed an list of entities instead of the single entity before? This is how Rails makes common tasks easy.

Finally, I will remove the edit field for the circle from the form completely because the user is not intended to change the circle a role belongs to.

  # Delete these lines
  <div class="field">
    <%= form.label :circle_id %>
    <%= form.text_field :circle_id %>
  </div>

When the form is submitted, the create method in the RolesController is called. Here, I need to set the new role’s circle parameter correctly. The original, generated code looks like this:

  # POST /roles
  # POST /roles.json
  def create
    @role = Role.new(role_params)

    respond_to do |format|
      if @role.save
        format.html { redirect_to @role, notice: 'Role was successfully created.' }
        format.json { render :show, status: :created, location: @role }
      else
        format.html { render :new }
        format.json { render json: @role.errors, status: :unprocessable_entity }
      end
    end
  end

I will adjust the comments, extract the circle from the request parameters, and set the circle accordingly; just two more lines at the beginning.

  # POST /circles/:circle_id/roles
  # POST /circles/:circle_id/roles.json
  def create
    @circle = Circle.find(params[:circle_id])
    @role = Role.new(role_params)
    @role.circle = @circle
    ...

When the creation is successful, the browser is redirected to the show method of the controller. This is done with the redirect_to function that you can see in the generated code above. It creates a corresponding HTTP response code (302) with the appropriate URL. However, the generated show view still contains a link back to the list of roles, so we need to fix that the same way as before.

The generated code in holo/app/views/role/show.html.erb:

<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @role.name %>
</p>

<p>
  <strong>Person:</strong>
  <%= @role.person_id %>
</p>

<p>
  <strong>Circle:</strong>
  <%= @role.circle_id %>
</p>

<%= link_to 'Edit', edit_role_path(@role) %> |
<%= link_to 'Back', roles_path %>

I will also fix it so that instead of the circle and person IDs, their names are displayed.

<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @role.name %>
</p>

<p>
  <strong>Person:</strong>
  <%= @role.person&.name %>
</p>

<p>
  <strong>Circle:</strong>
  <%= @role.circle.name %>
</p>

<%= link_to 'Edit', edit_role_path(@role) %> |
<%= link_to 'Back', circle_roles_path(@role.circle) %>

There is something that is easy to overlook. Remember that I changed roles so that they can be assigned to no one? If you check the line where I print the person’s name, I am using the &. operator. This is much like the . that is used to access a data structure in languages as early as C, but with the additional quirk that if the parent element is null (or nil in Ruby), it returns immediately. This is to avoid to have to write out if role.person.nil? role.person.name else "nobody" end every time. Of course, I can still do that if I want to do something specific like actually printing “nobody”. But it is a handy shortcut, a bit like Elm’s Maybe.withDefault, except that is not really much of a shortcut.

To finish this exercise on dependent resources, I will change the edit view for roles. The generated code looks like this:

<h1>Editing Role</h1>

<%= render 'form', role: @role %>

<%= link_to 'Show', @role %> |
<%= link_to 'Back', roles_path %>

Remember I now need to pass a reference to the circle to the form. However, do you also remember the shallow: true option I used. This means, that for editing, I don’t need the full URL but I can just use GET /roles/1/edit instead. Nevertheless, I need to pass in something. This is a common situation, so Rails handles is simply and gracefully. I will simply pass in nil (Roby’s equivalent of a null reference) and it will silently ignore the nesting.

Also, I need to change the roles_path once more, just as above.

<h1>Editing Role</h1>

<%= render 'form', circle: nil, role: @role %>

<%= link_to 'Show', @role %> |
<%= link_to 'Back', circle_roles_path(@role.circle) %>

Finishing off

Now I am finally done with converting the Roles to a dependent resource. To finish off, I will add a little “business logic” to the Circles, so that a set of standard roles is automatically created for each new circle, just like Holocracy demands it. I will add this to the create method of the CircleController.

As a last addition, I will ensure that roles are deleted when their circle is destroyed.

class Circle < ApplicationRecord
  has_many :roles, dependent: :destroy
  has_many :people, through: :roles

  validates :name, presence: true

  before_create do |circle|
    fac = circle.roles.new
    fac.name = "Facilitator"
    sec = circle.roles.new
    sec.name = "Secretary"
  end
end

The option dependent: :destroy ensures that roles are deleted together with the circle. Done. (I may implement some extra precautions later, such as not allowing to delete a circle with people in it.)

The before_create is a callback which is called, well, before the object is created. Here, I create two new roles for the new circle. They will get stored together with the circle. Simple and neat to initialize an object beyond just default values.

Summary

It may seem as if this was a lot of technicalities. But the truth is that I just had to walk through the cycle of request routes and controller actions, including the corresponding views, to ensure everything is passed on correctly. But for a structural change in a full web application, this seems rather simple. I like Rails for things like this. It keeps easy changes easy while complex ones are still possible, unlike many “codeless” tools that make simple things even easier, and complex ones near to impossible.