Rapidly create interactive UIs in pure Ruby

Escape the frontend Hustle // Plug into Rails // Extend with Vue.js

Learn more


Fullstack Ruby

Use your favorite language to create both backend logic and reusable frontend components.

Enhanced Maintainability

Greatly reduce the complexity in your codebase - less potential for bugs and more time for shipping features.

Not a Code Generator

Unlike similar projects, we neither use Opal nor one-time-generate JavaScript/HTML code.

keyboard_arrow_down

Matestack's Why: Simplify Web Application Development

In order to create dynamic, 'app-like' web applications, developers started to create fullblown Javascript applications and reduced Rails to a pure JSON API. Using this common approach, compared to the classic single-repo MVC structure, we increased the complexity in our development by introducing a separate full-blown frontend framework. Implementing two separate systems (backend-api, frontent-app) is a pain: Two different code bases, two repositories to maintain, two different deployment schedules, two test environments, two everything...! And than add native app development for iOS and Android and probably a fancy microservice-oriented backend architecture on top of that! Beeing a small dev team, we decided not to adopt this modern web development complexity and decided to create... matestack!

Matestack merges the back- and frontend-layer and automates the data flow between those two. On average, we write 50% less code, are much faster in our development, suffer less cognetive load, onboard junior devs way faster and simply have way more fun. Thanks to the reality-proven flexible architecture, we don't loose any possibility to extend our systems on the back or frontend. It's just Ruby and Vue.js!



Dashboard on a laptop
keyboard_arrow_down

Plug into Rails

Matestack is designed as an alternative to the classic Rails view layer. All other concepts of Rails remain untouched. A HTTP request is processed the classic "Rails way". Inside a controller action, you can add a matestack helper, referencing a matestack page class. A matestack page consists of multiple components and is written in pure Ruby, as seen on the next section. Using Trailblazer's "Cell" concept, the matestack page renders HTML which is then transferred to the browser. Within the browser, pure Vue.js takes care of optional dynamic behaviour.



config/routes.rb

Rails.application.routes.draw do

    get 'my_route', to: 'example#my_frist_page', as: 'my_first_page'

end

app/controllers/example_controller.rb

class ExampleController < ApplicationController

  include Matestack::Ui::Core::ApplicationHelper

  def my_first_page
    responder_for(Pages::MyApp::MyFirstPage)
  end


end

keyboard_arrow_down

Create UIs in Ruby Classes

Matestacks UI concept consists of three major building blocks: "Component" (reusable UI element), "page" (equivalent to a classic view) and "app" (used similarly to the classic layout). Pages, components and apps are pure Ruby classes defining the UI through a response method.



Below you see the page class which has been referenced in the previous section. It renders a list by calling matestacks core components 'ul' and 'li' wrapped in a 'div'. In order to limit the nesting level of the main response method, a matestack partial - defined in a separate Ruby method - is used. Data is resolved in a 'prepare' method which is called before rendering.

app/matestack/pages/my_app/my_first_page.rb

class Pages::MyApp::MyFirstPage < Matestack::Ui::Page

  def prepare
    @technologies = ["Rails", "Vue.js", "Trailblazer", "Rspec", "Capybara"]
  end

  def response
    components{
      div id: "my-first-page" do
        ul class: "list" do
          @technologies.each do |technology|
            partial :my_list_item, technology
          end
        end
      end
    }
  end

  def my_list_item technology
    partial {
      li class: "list-item" do
       plain technology
     end
    }
  end

end

Like the classic view layer, a page (view) gets normally yielded in an app (layout):

app/matestack/apps/my_app.rb

class Apps::MyApp < Matestack::Ui::App

  def response
    components{
      header do
        heading size: 1, text: "My App"
      end
      main do
        page_content # page gets yielded here!
      end
      footer do
        plain "That's it!"
      end
    }
  end

end
keyboard_arrow_down

Create a Single Page Application without Javascript

A Single Page Application (SPA) usually is loaded once and handles all user interactions dynamically by calling backend APIs. This gives the user the ofted desired "app-feeling". In contrast, the Rails View Layer only offers the static request/response mode to render content. Matestack fixes that without adding the complexity that a SPA written in JavaScript usually brings with itself. Matestack's dynamic 'transition' core component simply performs dynamic transitions between pages without full page reload, given that both pages are wrapped by the same app layout. You only have to create multiple pages and add the 'transition' components to their app like so:



app/matestack/apps/my_app.rb

class Apps::MyApp < Matestack::Ui::App

  def response
    components{
      header do
        heading size: 1, text: "My App"
      end
      nav do
        transition path: :my_first_page_path do
          button text: "Page 1"
        end
        transition path: :my_second_page_path do
          button text: "Page 2"
        end
      end
      main do
        page_content # pages are dynamically yielded here when buttons are clicked!
      end
      footer do
        plain "That's it!"
      end
    }
  end

end

app/matestack/pages/my_app/my_first_page.rb

class Pages::MyApp::MyFirstPage < Matestack::Ui::Page

  def response
    components{
      div id: "div-on-page-1" do
        plain "My First Page"
      end
    }
  end

end

app/matestack/pages/my_app/my_second_page.rb

class Pages::MyApp::MySecondPage < Matestack::Ui::Page

  def response
    components{
      div id: "div-on-page-2" do
        plain "My Second Page"
      end
    }
  end

end
keyboard_arrow_down

Handle User Interactions without Javascript

We love to add dynamic behaviour to our UIs. But we don't enjoy adding Javascript again and again if we want something to react dynamically on client-side events. That's why we've created a library of dynamic components handling these clientside dynamics for us. We just tell them what to do in our Ruby classes.



Below, you see a page rendering a form and a list of the latest 5 instances of an Active Record model 'MyModel'. When the form is successfully submitted, the list gets rerendered without a full page reload. The Vue.js part of matestack's async component only requests a fresh version of the list from the server and rerenders this specific part. Same applies for the delete action which is performed with matestack's action component. If the form contains invalid data, matestack's form_input component automatically renders Active Record validation error messages. Additionally, an error messages is shown for 5 seconds.

All of this dynamic behaviour is implemented without writing a single line of Javascript.

app/matestack/pages/my_app/my_first_page.rb

class Pages::MyApp::MyFirstPage < Matestack::Ui::Page

  def prepare
    @my_model = MyModel.new
  end

  def response
    components {
      form my_form_config, :include do
        form_input key: :some_model_attribute, type: :text
        form_submit do
          button text: "Submit me!"
        end
      end
      async show_on: "form_has_errors", hide_after: 5000 do
        plain "Data could not be submitted, please check form"
      end
      partial :latest_5_list
    }
  end

  def latest_5_list
    partial {
      async rerender_on: "rerender_list" do
        ul do
          MyModel.last(5).each do |my_model_instance|
            li do
              span class: "item", text: my_model_instance.some_model_attribute
              action my_delete_config(my_model_instance.id) do
                button text: "delete"
              end
            end
          end
        end
      end
    }
  end

  # component configurations as methods returning a hash

  def my_form_config
    {
      for: @my_model,
      method: :post,
      path: :some_rails_action_path,
      success: {
        emit: "rerender_list"
      },
      failure: {
        emit: "form_has_errors"
      }
    }
  end

  def my_delete_config instance_id
    {
      method: :delete,
      path: :some_rails_action_path,
      params: {
        id: instance_id
      },
      success: {
        emit: "rerender_list"
      }
    }
  end

end
keyboard_arrow_down

Create custom Components

We've talked a lot about matestacks core components. But what about creating your own ones? It's quite straightforward. After you have identified recurring elements on your UI, you put them into a custom component. Defined once, you can use them across all your UI and reduce A LOT OF repeated markup code! Trust us, this is fun! Below, you can see an example of a custom component and its usage to avoid markup repetition:



app/matestack/components/bar.rb

class Components::Bar < Matestack::Ui::StaticComponent

  def response
    components {
      div class: "some" do
        div class: "crazy" do
          div class: "markup" do
            div class: "I don't want to write more than" do
              div class: "once" do
                plain @options[:content]
              end
            end
          end
        end
      end
    }
  end

end

Use your own components within apps, other components or pages like shown below:

app/matestack/pages/my_app/my_first_page.rb

class Pages::MyApp::MyFirstPage < Matestack::Ui::Page

  def response
    components{
      #...
      custom_bar content: "hello world!"
    }
  end

end
keyboard_arrow_down

Extend with Vue.js

And what about some custom dynamic behaviour? No problem: Just create your own dynamic components with your own Vue.js code on top and use them right next to all other components. Sharing an Vue.js event bus, you can even communicate with all other components, or simply do whatever you want to do. In the end, it's just Rails and Vue.js, so there are no limits!



app/matestack/components/foo.rb

class Components::Foo < Matestack::Ui::DynamicComponent

  def response
      components {
        div id: "my-component" do
          plain "I'm a fancy dynamic component! Call me {{dynamic_value}}!"
        end
      }
    end

end

app/matestack/components/foo.js

MatestackUiCore.Vue.component('custom-foo', {
  mixins: [MatestackUiCore.componentMixin],
  data: function data() {
    return {
      dynamic_value: ""
    };
  },
  mounted(){
    const self = this;
    setTimeout(function () {
      self.dynamic_value = "bar"
    }, 1000);
  }
});

app/matestack/pages/my_app/my_first_page.rb

class Pages::MyApp::MyFirstPage < Matestack::Ui::Page

  def response
    components{
      #...
      custom_foo
    }
  end

end

You've seen enough?

Check out the documentation and start right away

Get Started

A growing team of core contributors improves matestack every day
Become one of them!

jonasjabari

Core Contributor

pascalwengerter

Core Contributor

michaelrevans

Core Contributor

cameronnorman

Core Contributor

bdlb77

Core Contributor

marcoroth

Core Contributor

stiwwelll

Core Contributor

lumisce

Core Contributor

borref

Core Contributor

sigfriedCub1990

Core Contributor

GrantBarry

Core Contributor

mayanktap

Core Contributor

Manukam

Core Contributor

citizen428

Core Contributor

tae8838

Core Contributor

3wille

Core Contributor

MarcoBomfim

Core Contributor

Community-driven

Made by people like you for people like you! You need a feature which is not covered yet? Let the community know or implement it yourself and create your first PR!

Quality-controlled

Test driven development and throughout documentation are most important to us. We aim for full test coverage. If something breaks, our CI will let us know!

Contributor-friendly

We welcome contributors of every kind. Junior or senior level - it doesn't matter. Now is the best time to get involved and have real impact!

Say Hello!

Jump into our Gitter Chat and let us know if you face any issues, have a feature request or want to join the community!

Gitter Chat