Render markdown articles in Rails
Table of Contents
- Install Phlex
Pages
,Components
, andApplicationLayout
- Auto-load components
- Pages
- Your first custom components
- TOC
Previous: Set up a new Ruby on Rails project
Article 2 in Series: Build a blog with Ruby on Rails
This guide explains my set-up for using Phlex components. However, Phlex components are just Plain Ruby Objects, and this object-oriented approach to the View layer of our MVC is extremely flexible. Feel free to modify this system to suit your needs
Install Phlex
-
Install the Phlex gem. We want the beta version.
bundle add phlex-rails --version=2.0.0.rc1
-
Run the install generator
bundle exec rails generate phlex:install
Pages
, Components
, and ApplicationLayout
Set up a Component
base class
All our components will inherit from this class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# /app/views/components/_base.rb
# frozen_string_literal: true
class Components::Base < Phlex::HTML
include Phlex::Rails::Helpers::Routes
include Phlex::Rails::Helpers::AssetPath
include Phlex::Rails::Helpers::ImageTag
include Phlex::Rails::Helpers::URLFor
include InlineSvg::ActionView::Helpers
if Rails.env.development?
def before_template
comment { "Before #{self.class.name}" }
super
end
end
end
Set up a Page
base class
This class inherits from our Component
class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# /app/views/pages/_base.rb
# frozen_string_literal: true
class Pages::Base < Components::Base
PageInfo = Data.define(:title)
def around_template
render layout.new(page_info) do
super
end
end
def page_info
PageInfo.new(
title: page_title
)
end
end
Add a layout
Our pages can specify a layout. We only need one, but you’re free to write more
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# /app/views/layouts/application_layout.rb
class ApplicationLayout < Components::Base
include Phlex::Rails::Layout
include Phlex::Rails::Helpers::CurrentPage
include Phlex::Rails::Helpers::T
def initialize(page_info)
@page_info = page_info
end
def view_template
doctype
html(lang: "en") do
head do
title {
t("site.title") + " - " + @page_info.title
}
meta(
name: "viewport",
content: "width=device-width,initial-scale=1")
end
body {
header do
Navbar do |navbar|
navbar.logo do
inline_svg_tag("logo/logo_2.svg",
alt: "",
role: "img",
width: "200",
height: "auto")
end
navbar.links do
a(href: root_path) { "Home" }
a(href: articles_path) { "Blog" }
a(href: about_path) { "About" }
a(href: contact_path) { "Contact" }
end
end
end
main do
yield
end
footer do
p { "Footer" }
end
}
end
end
end
Explanation:
* Here you can see how HTML elements are rendered by passing a block to the element method with the content that should be rendered inside.
* I have included a call to render a custom component Navbar
which we will define below.
* Inside this block we are using Navbar
s private links
method. We determine the order these elements show up, but our component determines how to render its links
method
* We are also using a custom component Link
which we will define below
* Our page content is rendered inside main
via yield
Auto-load components
Folder structure:
my_app_root/
├─ app/
│ ├─ views/
│ │ ├─ components/
│ │ │ ├─ link/
│ │ │ │ ├─ link.rb
│ │ │ ├─ navbar/
│ │ │ │ ├─ navbar.rb
│ │ │ ├─ _base.rb
│ │ ├─ layouts/
│ │ │ ├─ application_layout.rb
│ │ ├─ pages/
│ │ │ ├─ _base.rb
│ │ │ ├─ home.rb
│ │ │ ├─ about.rb
│ │ │ ├─ contact.rb
Pages
and Components
initializer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# frozen_string_literal: true
module Pages
end
module Components
extend Phlex::Kit
end
Rails.autoloaders.main.push_dir(
"#{Rails.root}/app/views/pages",
namespace: Pages
)
Rails.autoloaders.main.push_dir(
"#{Rails.root}/app/views/components",
namespace: Components
)
Rails
.autoloaders
.main
.collapse(
"#{Rails.root}/app/views/components/*"
)
Explanation:
* Push all files directly inside /app/views/pages
into the Pages
module
* Push all files inside /app/views/components/
into the Components
module, but look one level deep inside this directory
Modify autoload_paths
The following code fragment is simplified for convenience. Add the relevant lines to your file
1
2
3
4
5
6
7
8
9
10
11
12
# /config/application.rb
module App
class Application < Rails::Application
config
.autoload_paths << "#{root}/app/views/layouts"
config
.autoload_paths << "#{root}/app/views/components"
config
.autoload_paths << "#{root}/app/views/pages"
end
end
Pages
Set up a PagesController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# /app/controllers/pages_controller.rb
class PagesController < ApplicationController
def home
render Pages::Home.new
end
def about
render Pages::About.new
end
def contact
render Pages::Contact.new
end
end
Write some placeholder pages
1
2
3
4
5
6
7
8
9
10
11
# /app/views/pages/home.rb
class Pages::Home < Pages::Base
def page_title = "Home"
def layout = ApplicationLayout
def view_template(&)
h1 { "Home" }
end
end
Do the same for other static pages
Your first custom components
Navbar
1
2
3
4
5
6
7
8
9
10
11
12
13
# /app/view/components/navbar/navbar.rb
class Components::Navbar < Components::Base
def view_template(&)
div(class: "container") do
div(class: "nav", &)
end
end
def links(&)
nav(class: "links", &)
end
end
Link
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# /app/view/components/link/link.rb
class Components::Link < Components::Base
def initialize(href:, is_disabled: false)
@href = href
@is_disabled = is_disabled
super()
end
def view_template(&)
tag do
yield
end
end
def tag(&)
if @is_disabled
span(class: [ ("link"),
("link--disabled") ]) do
yield
end
else
a(class: "link", href: @href) do
yield
end
end
end
end
This should give you an idea of what’s possible with Phlex components. In our application layout we are initializing our Navbar
component with Link
instances. We are giving these links an is_disabled
argument that is based on whether the current route matches the link target. The Link
component encapsulates the business logic for rendering disabled links. Pretty cool, right?