SPA-like Apps in pure Ruby
Concept of apps, pages and components
Apps are similiar to layouts in Rails. Apps wrap pages with provided content, like a top navigation or a sidebar as displayed in the above image. You will always have at least one app in your Rails application, but you can have as much as you want. For example you could have a public app, which wraps your landing, contact, about pages with a top navigation and a private app, which wraps management pages with a sidebar and different top navigation.
As we already mentioned pages it is time to explain them. Pages are similiar to Rails views. A controller action usually renders a view, with matestack a controller action usually renders a page.
Lastly there are components. Components can be compared to Rails partials. They can be used to structure content and define reusable ui parts. In the above image components are represented with a green background. There is one component containing a text block which is reused three times on the page and another component containing a button, which could be reused on other pages. So with components you can create reusable content blocks to structure your ui and keep it dry. Components can not only be used inside pages, but also in apps and other components.
Apps, pages and components will live in a
matestack folder inside your
app directory. Apps, pages and components can be created as you like, but we recommend to namespace apps and pages. Imagine we have an online shop with a shop app and a management app. The corresponding folder structure should look like this:
app/matestack/ | ____________shop/ ___ ___ app.rb ___ ____________pages/ ___ ___ ___ home.rb | ____________management/ ___ ___ app.rb ___ ____________pages/ ___ ___ ___ dashboard.rb
Having taken a look at the overall concept of matestacks apps, pages and components and how they work together, we can take a deeper look at each one of them.
As mentioned above apps wrap pages with provided content. But what advantages to we get from using an app with pages?
Apps can transition between pages corresponding to the same app. What does transition between pages mean? It means that an app can switch between pages without reloading the apps content. For example your navigation will not be reloaded, but only the page content, improving the user experience. With pure Rails views and layouts the browser would do a full reload of the website. With matestack only the content that should change is requested, rendered and replaced asynchronously without a full reload, providing a more app like or single page application like feeling.
Creating an app
Creating an app is easy. Let's create the shop app from the example at the beginning. As mentioned we recommend namespacing your apps and store pages, which in this case means we create our shop app under the namespace shop. Create a file
app/matestack/shop/app.rb which will contain our app code.
class Shop::App < Matestack::Ui::App def response heading text: 'Matestack Shop' yield_page end end
Apps need to inherit from
Matestack::Ui::App and implement a
response method. The
response method contains the output which will be rendered. In our case it would add an "h1" saying "Matestack Shop" above the pages. The
yield_page method determines where inside the app the page would be rendered.
yield_page enables the app to transition between pages.
How the call of
heading text: "Matestack Shop" works will be explained later in this guide.
As you might have read in the installation guide you need to have a rails layout containing a html element with "matestack-ui" as class name. This is required because matestack uses Vue.js and we mount to this class name. Because we do not yet support writing "html, head, meta" and other tags that are used outside the body in matestack you need at least one layout file. But we recommend using one layout file for each app.
Accessing data in apps
You can access controller instance variables, helpers (for example devise current_user) and all rails helpers that are available in views also in apps.
For example rails routes helper for a navigation.
class Shop::App < Matestack::Ui::App def response div class: 'navigation' do transition path: root_path, text: 'Home' transition path: products_path, text: 'Products' end end end
transition works in detail will be explained later in this guide. But for now it is enough to say, that it is similiar to a link with the difference that the app transitions to the other route without a website reload.
Pages come close to rails views, but give you the ability to transition between them without full website reloads when used with an app.
Creating a page
Let's create the home page from the online shop example. Therefore we create a file
class Shop::Pages::Home < Matestack::Ui::Page def response heading size: 2, text: 'A few products you may like' Product.limit(5).each do |product| paragraph text: product.name small text: product.price end end end
Pages need to inherit from
Matestack::Ui::Page and like apps implement a
response method. Within the
response method we implement our UI which will be rendered when we call
render Shop::Pages::Home. Our home page will render a "h2" saying "A few products you may like" followed by five sets of "p" and "small" tags containing the product name and price. With matestack you write pure ruby instead of html and therefore can leverage all of rubys and rails features.
Again, how the
small calls work will be explained later in this guide.
Let's create another page, where we will give you an example what you could do because you're writing uis in pure ruby.
class Shop::Pages::Products::Index < Matestack::Ui::Page def response banner do heading text: 'New in the matestack shop' end banner do heading text: 'Sale' paragrap text: 'Now until tommorow' end Product.all.each do |product| product_teaser product.name, product.price end end private def banner(&block) div class: 'banner' do yield end end def product_teaser(name, price) paragraph text: name, small text: prive end end
The possibilities are endless. Our
banner method takes in a block and sourrounds it with a "div". This could be useful if you have complex repeating markup which is only relevant on this page and has different content inside. Our
product_teaser(name, price) methods renders a "p" and "small" tag with the given name and price. But to be clear, we would recommend creating a component for product teaser. You will learn more about components in a second. There is much more you can do. For example inheriting from your own page.