Writing a Blog Engine in Phoenix and Elixir: Part 6, Markdown Support

Written by diamondgfx | Published 2015/11/09
Tech Story Tags: elixir | phoenix | web-development

TLDRvia the TL;DR App

Latest Update: 08/02/2016

Previous Post in this series

Part 5: Adding ExMachina

Current Versions

As of the time of writing this, the current versions of our applications are:

  • Elixir: v1.3.1
  • Phoenix: v1.2.0
  • Ecto: v2.0.2
  • ExMachina: v1.0.2
  • Earmark: 1.0.1

If you are reading this and these are not the latest, let me know and I’ll update this tutorial accordingly.

Where We Left Off

We’ve now added roles, users, and posts to our blogging platform, fixed up some bugs, upgraded some dependencies, and fixed our seeds. Our platform is really nice and stable and has a good set of base functionality. If you haven’t been following along or missed a step along the way, you can continue with the “11092015” branch of https://github.com/Diamond/pxblog.

Implementing Markdown support

Given that we’re tech people building a blog, one that we may or may not be using, we should probably allow people to write posts in something other than plaintext support. Markdown happens to be a language that works great for blogging and is really simple to support in Elixir, so let’s implement Markdown support using a third party library called Earmark. We start off simply by adding a dependency to our application in the deps function in mix.exs:

{:earmark, "~> 1.0.1"}

And then we’ll run mix do deps.get, compile to make sure we added it successfully to our application. If all goes well, we can move right on to adding support into our templates and views.

Updating our views

We’ll start by providing a convenient method to convert a post into Markdown. We don’t want to have to write Earmark.to_html(data) all over the place, and if we ever decide to support any of Earmark’s other features and options, we’d have to rewrite that code over and over. Instead, we’ll implement a function in web/views/post_view.ex that will give us a single place to modify if we ever want to change that. Open that file up and add the following function:

def markdown(body) dobody|> Earmark.to_html|> rawend

We have the input text, body, that we’re piping into Earmark’s “to_html” function. Since we’re expecting html out that we can place into our post, so we’ll use raw here. I know what you’re thinking: “yikes, isn’t raw terribly unsafe?” The answer is yes, it absolutely is. Trouble is, we need to output in raw html from Earmark, so we need to get this data rendered. We also don’t want to end up having to strip the same tags every single time we render this out, so let’s go into web/models/post.ex and add some code that will strip it out:

def changeset(struct, params \\ %{}) dostruct|> cast(params, [:title, :body])|> validate_required([:title, :body])|> strip_unsafe_body(params)end

defp strip_unsafe_body(model, %{"body" => nil}) domodelend

defp strip_unsafe_body(model, %{"body" => body}) do{:safe, clean_body} = Phoenix.HTML.html_escape(body)model |> put_change(:body, clean_body)end

defp strip_unsafe_body(model, _) domodelend

We added a decent amount of code here, so let’s discuss what we’re doing. The first change we made is at the top, in our changeset function. We added a new call, strip_unsafe_body(model, …). This will pattern match into one of three possible functions. The first includes a body parameter that is nil; the second is the method that includes a body parameter which we have to strip it out, and the third is a final catch-all.

We use the put_change function to replace the body change with a stripped version, which calls out to Phoenix.HTML’s “html_escape” function. This function takes in text, and then returns out a tuple of {:safe, cleaned_up_body} on success.

Writing this code with no tests makes me queasy, especially with what we’re doing here, so let’s test it!

Writing tests for our code stripper

The first thing we’re going to need to do, since we’re depending on these model actions to keep our code safe, is make sure they’re tested appropriately!

defmodule Pxblog.PostTest douse Pxblog.ModelCase

alias Pxblog.Post

@valid_attrs %{body: "some content", title: "some content"}@invalid_attrs %{}

test "changeset with valid attributes" dochangeset = Post.changeset(%Post{}, @valid_attrs)assert changeset.valid?end

test "changeset with invalid attributes" dochangeset = Post.changeset(%Post{}, @invalid_attrs)refute changeset.valid?end

test "when the body includes a script tag" dochangeset = Post.changeset(%Post{}, %{@valid_attrs | body: "Hello <script type='javascript'>alert('foo');</script>"})refute String.match? get_change(changeset, :body), ~r{<script>}end

test "when the body includes an iframe tag" dochangeset = Post.changeset(%Post{}, %{@valid_attrs | body: "Hello <iframe src='http://google.com'></iframe>"})refute String.match? get_change(changeset, :body), ~r{<iframe>}end

test "body includes no stripped tags" dochangeset = Post.changeset(%Post{}, @valid_attrs)assert get_change(changeset, :body) == @valid_attrs[:body]endend

The first thing we have to modify is our @valid_attrs declaration at the top. When we receive data from the controller it’s going to be in the form of string-based keys, not atoms, and it will cause our tests to fail unless we modify that.

Next, we import in Ecto.Changeset, but only the get_change/2 function, which we need to pull the translated value out of the changeset. Finally, we write four tests:

  1. When our body has a script tag in it
  2. When our body has an iframe tag in it
  3. When our body has none of the invalid tags in it

Re-run our test suite and we should be green. We have one more test to write and then some a few basic UI changes and then we are complete!

Writing our tests for the Markdown helper

We should also write a quick test for our markdown helper function that we wrote in our Post View module. The good news is this should be a very simple test to write.

Create test/view/post_view_test.exs and fill it with the following:

defmodule Pxblog.PostViewTest douse Pxblog.ConnCase, async: true

test "converts markdown to html" do{:safe, result} = Pxblog.PostView.markdown("**bold me**")assert String.contains? result, "<strong>bold me</strong>"end

test "leaves text with no markdown alone" do{:safe, result} = Pxblog.PostView.markdown("leave me alone")assert String.contains? result, "leave me alone"endend

Run our tests, verify that all is green, and we’re done with updating our tests for this new functionality!

Updating our UI

Right now, we have the ability to write up full posts but the UI doesn’t really support us editing anything larger than a single line. We’ll modify the standard form to include a text area instead of a textbox and we’ll also give it a shiny, better editor.

Open up web/templates/post/form.html.eex and add an id parameter and give it an id of “body-editor”. The finished line should look like this:

<%= textarea f, :body, class: "form-control", id: "body-editor" %>

Finally, add the following code to the bottom of your form’s template (web/templates/post/form.html.eex):

<link rel="stylesheet" href="//cdn.jsdelivr.net/simplemde/latest/simplemde.min.css"><script src="//cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script><script>var simplemde = new SimpleMDE();</script>

Reload our UI and we should see a very clean interface for writing new posts! This is starting to look like a proper blogging platform and everything!

Finally, open up web/templates/post/show.html.eex and change the line that displays the post’s body and change it to use the markdown(body) function we defined in the view.

<%= markdown(@post.body) %>

Conclusion

We now have a better means of editing/formatting our posts and the editor looks much, much nicer! We’ll probably also want to fix up the layout at some point, and there still isn’t any way to add comments, but commenting actually would be a fun way to implement support for channels, so we’ll hold off for now until we’re ready to build that support in!

If you want to see the completed version of this code, just grab the “add_markdown_support” branch of our repo.

Next Post In This Series

Writing A Blog Engine In Phoenix And Elixir: Part 7, Adding Comments Support

Check out my new book!

Hey everyone! If you liked what you read here and want to learn more with me, check out my new book on Elixir and Phoenix web development:

Phoenix Web Development | PACKT Books_Learn to build a high-performance functional prototype of a voting web application from scratch using Elixir and…_www.packtpub.com

I’m really excited to finally be bringing this project to the world! It’s written in the same style as my other tutorials where we will be building the scaffold of a full project from start to finish, even covering some of the trickier topics like file uploads, Twitter/Google OAuth logins, and APIs!


Published by HackerNoon on 2015/11/09