DEV Community

Renzo Diaz
Renzo Diaz

Posted on • Edited on

How to Architect Rails Project - Part 2

Modules
In the previous article, we've done setting up our Surface module
How to Architect Rails Project - Part 1

In this article, we'll cover the following features:

  • Module Admin
  • Layouts
  • Front-End Scaffolding

So, let's kick off...

For our Admin module, we want to get these routes

'domain.com/admins' # Overview, basic reports, charts
'domain.com/admins/blog' # Articles CRUD
'domain.com/admins/work' # Work CRUD

Let's add them to our routes.

# config/routes.rb
Rails.application.routes.draw do
  ...
  namespace :admins do
    resources :blogs
    resources :works
  end
end

Why admins and not admin in singular? Because this module was thought to have multiple user types, users that only can publish articles and won't have permissions to publish works.

Moving on, we need to create the views for our Admin module according to our routes.

Create these files inside app/views folder

app/views
  ├── admins/blogs
  │   └── index.html.erb
  │   └── show.html.erb
  ├── admins/works
  │   └── index.html.erb
  │   └── show.html.erb
  └── layouts 
      └── admin.html.erb

Let's add our markup to admin.html.erb layout, for now, let's copy the content from application.html.erb. Our layout would look like this...

<!DOCTYPE html>
<html>
  <head>
    <title>Portfolio</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
    <%= render "partials/header" %>
    <%= yield %>
  </body>
</html>

Cool right? We have our Admin layout.

Hold on your horses... We are still using the assets from our Surface module (application), we want to isolate Admin/Surface and make them to not depend on each other, so, let's isolate our Admin assets out of the Surface module.

Create these files under app/assets/stylesheets...

app/assets/stylesheets
  ├── admin/base
  │   └── _base.scss
  │   └── _variables.scss
  ├── admin/components
  │   └── _header.scss
  │   └── _sidebar.scss
  └── admin.scss

So our admin.scss would look like this...

// Base theming
@import "admin/base/variables";
@import "admin/base/base";

// Components
@import "admin/components/header";
@import "admin/components/sidebar";

Next, add this line to our base_controller.rb under controller/admins folder

class Admins::BaseController < ApplicationController
  layout 'admin'
end

We need also to tell rails that we want to use admin.css|admin.js in our initializer, so add the following line to config/initializers/assets.rb

Rails.application.config.assets.precompile += %w( admin.js admin.css )

Now let's change our admin.html.erb layout to use admin.css and remove the current header stylesheet which comes from the Surface module.

<!DOCTYPE html>
<html>
  <head>
    <title>Portfolio</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag 'admin', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Well done! now if we run rails s and navigate to http://localhost:3000/admins/blogs we should see a blank page. If we inspect elements in the browser we'll see that our styles are coming from admin and not application anymore.

Alright, Renzo, this is cool but I don't see any component and styles on the page yet. Besides the javascript still comes from the Surface module.

Before deep into the components and styles, let's add an Admin module for our javascript files, this is pretty much the same as what we did with styles.

So, let's create an admin.js file under app/javascript/packs folder and add the following lines...

// app/javascript/packs/admin.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()

Change <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> to <%= javascript_pack_tag 'admin', 'data-turbolinks-track': 'reload' %> in our admin.html.erb layout

<!DOCTYPE html>
<html>
  <head>
    <title>Portfolio</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag 'admin', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'admin', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Let's restart our server and see what we got, we should see the same blank page if we navigate to http://localhost:3000/admins/blogs. If we inspect the page we'll see that now our javascript comes from admin.

Now we have our js/css isolated from the Surface module, let's add Bulma to our project and start building our components.

Add Bulma gem to our Gemfile at the bottom

gem "bulma-rails", "~> 0.8.0"

Then run bundle install in the terminal.

In this article we are going to use Bulma in both modules Surface/Admin, just to not make this article too long. Remember that we can also isolate to use Bulma for the Admin and any other framework for our Surface.

Now let's build our layout markup correctly for the Admin module.

<!Doctype html>
<html>
...
  <body>
    <%= render "patials/admin/header" %>
    <div class="columns is-fullheight">
      <div class="column is-2 is-sidebar-menu is-hidden-mobile">
        <%= render "partials/admin/sidebar" %>
      </div>
      <div class="column is-main-content">
        <%= yield %>
      </div>
    </div>
  </body>
</html>

Create a admin folder under app/views/partials add these files...

app/views/partials/admin/_header.html.erb

<nav class="navbar">
  <div class="navbar-brand">
    <a class="navbar-item" href="#">
      Logo
    </a> 
    <div class="navbar-burger burger" data-target="navMenubd-example">
      <span></span>
      <span></span>
      <span></span>
    </div>
  </div>

  <div id="navMenubd-example" class="navbar-menu">
    <div class="navbar-start">
    </div>

    <div class="navbar-end">
      <div class="navbar-item has-dropdown is-hoverable">
        <a class="navbar-link">
          <figure class="avatar image is-32x32">
            <img class="is-rounded" src="https://bulma.io/images/placeholders/32x32.png" alt=""/>
          </figure>
          username
        </a>

        <div class="navbar-dropdown is-right">
          <a class="navbar-item">
            Logout
          </a>
        </div>
      </div>
    </div>
  </div>
</nav>

app/views/partials/admin/_sidebar.html.erb

<aside class="menu">
  <p class="menu-label">
    General
  </p>
  <ul class="menu-list">
    <li><%= link_to 'Overview', admins_blogs_path %></li>
  </ul>
  <p class="menu-label">
    Administration
  </p>
  <ul class="menu-list">
    <li>
      <%= link_to 'My Work', admins_works_path %>
    </li>
    <li><%= link_to 'My Work', admins_blogs_path %></li>
    <li><a>Users</a></li>
  </ul>
</aside>

Add some styles

app/assets/stylesheets/admin/base/_variables.scss

$body-size: 16px !default;

// Responsiveness
// 960, 1152, and 1344 have been chosen because they are divisible by both 12 and 16
$tablet: 769px !default;
// 960px container + 40px;
$desktop: 1000px !default;
// 1152px container + 40;
$widescreen: 1192px !default;
// 1344px container + 40;
$fullhd: 1384px !default;

$white: #fff !default;
$orange: hsl(17, 83%, 57%);
$primary: $orange !default;
$primary-invert: $white !default;

// Link colors
$link: $primary !default;
$link-invert: $white !default;
$navbar-height: 3.25rem;

app/assets/stylesheets/admin/base/_base.scss

body {
    overflow-x: hidden;
}
.columns {
    &.is-fullheight {
        min-height: calc(100vh - ( #{$navbar-height} - .75rem ) );
        max-height: calc(100vh - ( #{$navbar-height} - .75rem ) );
        height: calc(100vh - ( #{$navbar-height} - .75rem ) );
        display: flex;
        flex-direction: row;
        justify-content: stretch;

        .column {
            overflow-y: auto;
        }
    }
}    
.is-main-content {
    background: $grey-lighter;
    padding: 1rem  2.5rem 1rem 2rem;
}

app/assets/stylesheets/admin/components/_sidebar.scss

.is-sidebar-menu {
    padding: 2.5rem;
    background: $grey-dark;
    li {
        a {
            color: $white;
        }
    }
}

app/assets/stylesheets/admin/components/_header.scss

.navbar {
    .navbar-item {
        .avatar {
            margin-right: .65rem;
            .is-rounded {
                max-height: 100%;
            }
        }
    }
}

Alright, if we restart our browser at this point and navigate to http://localhost:3000/admins/blogs our dashboard should look like this.

Portfolio | Admin

If you got any error through this setup, feel free to drop me a line dev.renzo.diaz@gmail.com I’ll try to answer all your doubts asap.

In part 1 of this article, I mentioned this part 2 will cover CRUDS and Authentication, and React as well.

We'll, we will cover those points in part3, I don't want to make this article too long and don't mix with Rails back-end stuff.

Buy me a coffee

Top comments (0)