Developing a new module of Gobierto

Gobierto modules implement well defined and isolated features of the application. Examples of modules are:

  • Gobierto Budgets: municipalities budgets visualization.
  • Gobierto People: senior officials official information and agenda publication.
  • Gobierto Observatory: indicators and statics of a municipality.
  • Gobierto Plans: plans

Modules can be activated or disabled by the manager, but their code will be included in all the installations of Gobierto.

This page describes the steps needed to create a new module and integrate it in the application. After following this steps don't forget to review the module checklist at the end of this section.

Module basic attributes

Every module has a name and defines a namespace. These attributes are defined in config/application.yml:

default: &default
      name: Gobierto Development
      namespace: GobiertoDevelopment
      name: Gobierto Budgets
      namespace: GobiertoBudgets

When you create a module, you must define the root_path in config/routes.rb

# Gobierto People module
namespace :gobierto_people, path: "/" do
  constraints do
    get "cargos-y-agendas" => "welcome#index", as: :root

Also, in the module file you should define a method self.root_path, which receives the current site as an argument:

def self.root_path(current_site)
  if current_site.gobierto_budgets_settings && current_site.gobierto_budgets_settings.settings["budgets_elaboration"]

Nowadays, we have 5 modules (Gobierto Budgets, Gobierto People, Gobierto Participation, Gobierto Observatory and Gobierto Indicators) that have root_path to be the home page.

default: &default
      name: Gobierto Budgets
      namespace: GobiertoBudgets
      name: Gobierto People
      namespace: GobiertoPeople
      name: Gobierto Participation
      namespace: GobiertoParticipation
      name: Gobierto Observatory
      namespace: GobiertoObservatory
      name: Gobierto Indicators
      namespace: GobiertoIndicators
      name: Gobierto Plans
      namespace: GobiertoPlans

This namespace is applied to models, assets, helpers, controllers, views, I18n keys, tests, and the routes. This guide covers the steps you need to follow on each of those resources to enable the new module.

If the module needs special configuration, create an entry in the application.yml file with the name of the module:

    gifts_service_url: <%= ENV["PEOPLE_GIFTS_SERVICE_URL"] %>
    travels_service_url: <%= ENV["PEOPLE_TRAVELS_SERVICE_URL"] %>


If your module implements models, create a folder to store them with the module name, and remember to declare the class under the Ruby module name.

For example:

# app/models/gobierto_people/person.rb

require_dependency "gobierto_people"

module GobiertoPeople
  class Person < ApplicationRecord
    include ::GobiertoCommon::DynamicContent
    include User::Subscribable

In case your model implements a relationship with other model outside the module, you shouldn't include the module name in the name of the relationship for readability reasons, unless there is a name conflict.


# preferred way
class User < AppplicationRecord
   has_many :posts, class_name: 'GobiertoPublications::Post'


# we don't like this
class User < AppplicationRecord
   has_many :gobierto_publications_posts

Define a table name prefix for your module in app/models/<name of your module>. For example:

# app/models/gobierto_people.rb

module GobiertoPeople
  def self.table_name_prefix

In the module you can declare the following class methods:

  • table_name_prefix
  • classes_with_vocabularies
  • classes_with_custom_fields
  • searchable_models
  • module_submodules
  • custom_engine_resources
  • doc_url

If you create unit tests, define them in a folder with the module name, i.e. test/models/gobierto_people/person_test.rb


Declare your controllers in a specific folder for the module, and declare your classes under the Ruby module namespace.

Also, declare an ApplicationController inside the new module, to define the layout.

For example:

# app/controllers/gobierto_people/application_controller.rb

module GobiertoPeople
  class ApplicationController < ::ApplicationController
    include User::SessionHelper

    layout "gobierto_people/layouts/application"

Admin controllers

The new module might have admin actions. Declare these actions in controllers under GobiertoAdmin module. Each admin controller needs to be protected by two filters:

  • module_enabled!: checks if the module is enabled in the site configuration
  • module_allowed!: checks if the current admin has permissions on the current module

For example:

before_action { module_enabled!(current_site, "GobiertoBudgetConsultations") }
before_action { module_allowed!(current_admin, "GobiertoBudgetConsultations") }

In GobiertoAdmin::BaseController a default_modules_home_paths helper method is defined with the default home path of each module. Regular admins are redirected to the home path of their first module enabled and these paths are also used to generate the links for each module in the application layout.

# app/controllers/gobierto_admin/base_controller.rb

def default_modules_home_paths
  @default_modules_home_paths ||= {
    # ...
    gobierto_people: admin_people_people_path,
    # ...


Declare your views in a specific folder for the module. Include a layouts folder to define the module layout. Use the nested layout syntax of Rails.

At least include:

  1. The javascript application file of your module
  2. Custom breadcrumb items
  3. A render to the main layout
# app/views/gobierto_people/layouts/application.html.erb

<% content_for :javascript_module_link do %>
  <%= javascript_packs_with_chunks_tag 'module', 'data-turbolinks-track' => true  %>
<% end %>

<% content_for :stylesheet_module_link do %>
  <%= stylesheet_packs_with_chunks_tag 'module', 'data-turbolinks-track' => true  %>
<% end %>

<%= render template: "layouts/application" %>

Also, you need to define a couple of files for the menus:

  • _navigation.main.html.erb

  • _navigation.sub.html.erb

Admin menu

If the admin has the module enabled and an entry is included in the GobiertoAdmin::BaseController#default_modules_home_paths described in the previous section, a new item is added automatically to the admin menu. Remember to add translations for the module name (see I18n keys section).

Also you can create manually a new link adding the necessary permissions in app/views/gobierto_admin/layouts/application.html.erb. Example:

# app/views/gobierto_admin/layouts/application.html.erb

<% if managing_site? %>
    <%= link_to t('.edit_site'), edit_admin_site_path(current_site) %>
        <% if current_admin.can_edit_vocabularies? %>
          <li><%= link_to t(".vocabularies"), admin_common_vocabularies_path %></li>
        <% end %>
<% end %>


Module routes must be declared under a namespace specific for the module. Example:

namespace :gobierto_budgets, path: '', module: 'gobierto_budgets' do
  constraints do
    get 'site' => 'sites#show'


GobiertoExports is a module that exposes a page where the user can download data in a reusable format (JSON and CSV). This page is composed by the exports exposed by each module. If your module wants to expose some exports in this page you need to add a folder named exports inside your module views folder with two partials:

  • _nav_item.html.erb with the link to include in the module submenu, for example:
<%= link_to t("gobierto_people.layouts.application.title"), gobierto_exports_root_path(anchor: 'section-people') %>
  • _index.html.erb containing the html to include in the open data page, for example:
<div class="pure-g data_block" id="section-people">

  <div class="pure-u-1 pure-u-md-7-24">
    <h2><%= t("gobierto_people.layouts.application.title") %></h2>

  <div class="pure-u-1 pure-u-md-17-24 main_content">

    <div class="data_item">
      <h3><%= t("gobierto_people.exports.index.people.title") %></h3>
      <%= link_to 'CSV', gobierto_people_people_path(format: :csv), class: 'button small' %>
      <%= link_to 'JSON', gobierto_people_people_path(format: :json), class: 'button small' %>
      <p><%= t("gobierto_people.exports.index.people.description") %></p>

    <div class="data_item">
      <h3><%= t("") %></h3>
      <%= link_to 'CSV', gobierto_people_events_path(format: :csv), class: 'button small' %>
      <%= link_to 'JSON', gobierto_people_events_path(format: :json), class: 'button small' %>
      <p><%= t("") %></p>



Expose some endpoints in json and csv format, you can use a block like this

respond_to do |format|
  format.json { render json: @events }
  format.csv  { render csv:, filename: 'events' }

For the json format put your module serializers into app/serializers/<module_name>.

For the csv define use the GobiertoExports::CSVRenderer with a relation and define two methods in the corresponding module, a class method named csv_columns that returns the csv headers and an instance method named as_csv that returns that record as an array of values to include in th csv.


def self.csv_columns
  [:id, :name, :email, :charge, :bio, :bio_url, :avatar_url, :category, :political_group, :party, :created_at, :updated_at]

def as_csv
  political_group_name = political_group.try(:name)

  [id, name, email, charge, bio, bio_url, avatar_url, category, political_group_name, party, created_at, updated_at]


Gobierto has an activities log that save the admin events in the application. For example, to save this
information we must generate into app/pub_sub/ the publisher and the subscriber to events like
created page, updated page or deleted page.

module Publishers
  class GobiertoCmsPageActivity
    include Publisher

    self.pub_sub_namespace = 'activities/gobierto_cms_pages'
module Subscribers
  class GobiertoCmsPageActivity < ::Subscribers::Base
    def page_created(event)
      create_activity_from_event(event, 'gobierto_cms.page_created')

    def page_updated(event)
      create_activity_from_event(event, 'gobierto_cms.page_updated')

    def page_deleted(event)
      create_activity_from_event(event, 'gobierto_cms.page_deleted')


    def create_activity_from_event(event, action)
      Activity.create! subject: event.payload[:subject],
                       author: event.payload[:author],
                       subject_ip: event.payload[:ip],
                       action: action,
                       site_id: event.payload[:site_id],
                       admin_activity: true

If you want the activities to be viewed from the "Activity log", you should make
sure to mark:

admin_activity: true

These events have to be called from the controller with track_create_activity:

# app/controllers/gobierto_admin/gobierto_participation/issues_controller.rb

module GobiertoAdmin
  module GobiertoParticipation
    class IssuesController < BaseController


      def create
        @issue_form =


            notice: t(".success")
          render :new_modal, layout: false and return if request.xhr?
          render :new



      def track_create_activity
        Publishers::GobiertoParticipationIssueActivity.broadcast_event("issues.issue_created", default_activity_params.merge({subject: @issue_form.issue}))



Declare a file module-<name of your module>.scss in the Rails folder app/assets/stylesheets.


Create a specific folder for your module, and create an application.js file. If the module needs vendor libraries add them to vendor/assets/javascripts.

I18n keys

Declare a folder for the module in config/locales/, but only controllers and views can be defined on it. Models and routes must be defined outside the module (this is because how Rails uses the namespace of I18n).

To add module name translations to be used in admin menu layout use the module name removing the "gobierto_" prefix as key. For example, for the gobierto_people module just add:

# config/locales/gobierto_admin/views/layouts/en.yml
      people: Officers and agendas
    notifications: Notifications


In case your module needs migrations, every migration you define needs to be preffixed with the namespace. Example:

rails g migration gobierto_budgets_add_budget_line_description


There are many type of tests, just create a subfolder with the name of the module depending on the type of test.

Admin permissions

In app/models/gobierto_admin/permission/ a subclass of Admin::Permission will be implemented, defining a scope, which will limit the access.


module GobiertoAdmin
  class Permission::GobiertoBudgetConsultations < Permission
    default_scope -> do
      where(namespace: "site_module", resource_name: "gobierto_budget_consultations")


Sometimes modules need some database data to exist. For example, a configuration entry, a list of DynamicContentBlocks preconfigured. For that reason, we have created a seeds structure and two seeds runner classes:

  • ModuleSeeder: seeds a module
  • ModuleSiteSeeder: seeds a module for a specific Gobierto installation. It uses the attribute from config/application.yml

Both seed types can be found in db/seeds/modules and db/seeds/sites.

These seeds are executed when a module is activated in a site. De-activating the module doesn't run the seeder. Keep this in mind in order to write idempotent scripts.

Here's a template of the seed class:

module GobiertoSeeds
  class Recipe
      # Your code goes here


Does the new module expose resources to be subscribed to? Examples of subscribable resources are people agendas, or a participatory process. If so you need to add it to the constant MODULES_WITH_NOTIFICATIONS.

Search concern

If your model implements any resource that should be searchable, you just need expose the entities in the main module model under searchable_models class method.

For example, GobiertoPeople module exposes these models:

  def self.searchable_models
    [ GobiertoPeople::Person, GobiertoPeople::PersonPost, GobiertoPeople::PersonStatement ]

Once made a model searchable, include the translations of the model names in the module app/javascript/lib/shared/modules/module-search.js

      case 'GobiertoPeople::Person':
        return I18n.t("");
      case 'GobiertoPeople::PersonPost':
        return I18n.t("");

Module checklist

When you implement a new module, don't forget to check it against this list of items to verify the implementation is complete:

  • routes: did you add a root path for the module?
  • layout: is the module properly integrated in the layout and the navigation menus?
  • application configuration: did you enabled the module in the application.yml? Did you update this file in Ansible?
  • scoping: did you scope the database tables and the Ruby code in the proper scopes?
  • admin permissions: did you configure permission for regular admins?
      • module configured: did you protect the module actions with the module_enabled filter?
  • javascript: did you create a new javascript module?
  • data export: is the module generating data that should be exportable? Did you include it in the exports sections?
  • admin sidebar: is the module properly distributed in the admin sidebar?
  • admin activities: does the module generates comprehensive activities for admins?
  • documentation: have you documented the module in the Admin manual?
  • search: are there items in the module that should be enabled for search?
  • seeds: did you add new values for this module in the seeds?
  • doc: does the module has documentation? Have you linked it in the doc_url method of the module?