Fiddling Around with Sinatra, Part II

Tweet

In the last article, we managed to build an app that allowed users to show off their HTML, CSS and JavaScript skills by creating ‘riddles’ – small snippets of HTML, CSS and JavaScript. As an added bonus it also allowed users to use SCSS for styling and Markdown for the HTML. In this part we’re going to add some improvements to the app to make it more like the sites it clones.

iFrames

The first thing that we’ll do is place each riddle in it’s own iframe. This means that the riddle will effectively be in it’s own HTML document. This has the advantage of making each riddle behave exactly as it would if it was a self contained web page, so users will be able to load external librares and web fonts which wasn’t possible before.

To implement an iframe, we need to change the show view so it looks like this:

We need a route handler for the iframe. In the src attribute of the iframe we used the route “/#{@riddle.id}” which is basically just the id of the Riddle. We need this so we can find the Riddle in the database, here’s the route handler that we need:

This gets the Riddle that corresponds to the id given from the database. We then display a new view called ‘riddle’. Notice that the layout is set to be false, if it wasn’t then our layout would be displayed again in the iframe, which isn’t what we want. We want the iframe to have it’s own HTML head and body. These will go directly in the view, as you can see below:

As you can see, we’re now using inline style and script tags to display the CSS and JavaScript. This is to avoid having to hit the database each time. And because each riddle has its own CSS and JavaScript, using inline styles and scripts will be fine. This means that we won’t need routes for them, so we can remove the following route handlers:

We also need to remove the following lines that link to them in our layout:

If you test this out, it won’t really look any different from before, but now we’re placing all the code into an iframe. You might notice that the iframe by default looks really small. This can be solved by adding the following line to our @@styles view:

Editing Riddles

The next feature to add is allowing users to edit Riddles. If somebody sees something they like then it would be good to allow them to make some changes and alteration. First of all, let’s add a button that allows users to edit a riddle when they’re on a riddle page. We’ll put it in the layout, right next to the ‘New Riddle’ button, but we only want it to show if there is a riddle to actually edit. This code should do it:

We need to check if there is a @riddle object, but also if it has an id. This will restrict it to only using riddles that have been saved as they are only allocated an id after being saved (there is a @riddle object on the ‘new’ page where we wouldn’t want an edit button). Now we can click to edit a riddle we need to actually implement allowing users to edit them.

The problem is, we don’t want to actually update the actual riddle itself … people won’t want their good work changed by somebody else! And we don’t want to get into all the hassle of having users. The solution is simple – just create a new riddle with using the the attributes of the riddle we want to edit. This can be donein DataMapper using the following code:

Notice that we want to set the id to nil as this is an auto-incrementing field and will be set automatically by the database when the new riddle is saved, giving it its own unique url. All we need to do to make this happen is add the following route handler to our code:

This refers to the url that we used in the edit button’s link. First of all we get the riddle that we wish to edit from the database. Then we ‘clone’ it as a new unsaved object and then display the new view, which is simply the form. Unfortunately this will just show blank fields at the moment, so we need to make a small alteration to the form so that the fields are populated with any default values:

Start Your Engines

At the start of part 1, I mentioned that Sinatra uses Tilt to implement lots of different view engines. We haven’t really used this yet. It would be cool if we let users choose which engine they wanted to use to display the HTML, use a preprocessor for CSS, or use CoffeScript instead of JavaScript. Sinatra makes this unbelieavably easy to achieve. First of all we need to require the relevant gems for all the view engines we plan to use. Add the following to the top of main.rb:

The RedCloth gem is used to process Textile and the v8 gem embeds the v8 javascript engine in Ruby, which is requried for CoffeeScript support. All the others are hopefully self-explanatory.

Now we need to modify our database to include some fields to store which view engines to use. To do this, we first need to update the Riddle class to include these new properties:

To make sure these changes are made to our database, all we need to do is use the DataMapper’s powerful migrations feature using irb. Type the following into a terminal:

$ irb ruby-1.9.2-p180 :001 > require './main' ruby-1.9.2-p180 :001 > Riddle.auto_migrate! 

It is important to note that using the auto_migrate! method will destroy all of the riddles currently stored in the database. If you don’t want this to happen you can use the auto_upgrade! method instead.

Next we need to modify our form to show a dropdown menu that allows users to select which engine they want to use:

This is a bit long and painfully hard-coded, but does the job we want for now. What is important is the value attribute with every option. This will be the value that is stored in the database and it’s important because in most cases this is the same as the helper method that Sinatra uses to render a view using that particular engine. As you’ll see, this makes rendering a riddle using a particular engine extremely easy. All we need to do now is modify the iframe code in the riddle view to the following:

Let’s take a closer look at how we are rendering the HTML. The following line will use the html_engine property of the riddle to render the HTML using the correct engine:

== send(@riddle.html_engine, @riddle.html) 

The send method is used to invoke the method that is stored in the html_engine property. This is why the name of the values in the form was so important as they have to match the method names exactly. I also cheated a little bit as there isn’t an html helper method, but as I noted in part 1, markdown will just display raw HTML just fine anyway, which is why if somebody selects the HTML option, it is actually ‘markdown’ that is stored in the database field.

We have to do things a bit differently for the CSS and JavaScript. Sinatra doesn’t have a specific helper for CSS or JavaScript, but since they are already in <style> and <script> tags respectively we can just serve it straight if the user selects ‘css’ or ‘javascript’ from the menu. Otherwise, if they choose a CSS preprocessor or CoffeScript, then the value stored in the database field matches the name of the Sinatra helper, so we can use the send method in a similar way as when selecting the HTML engine, as you can see in the snippet below that shows how the CSS code is inserted into the style block:

Finishing Up

We’re virtually there! All we need to do is tidy up a bit and add a bit of style to make everything look a bit nicer. Here are the styles that I used:

And that’s pretty much it! It works quite nicely and we’re still well under 160 lines of code (including views and CSS)! You can see all the code on GitHub repo and the finished app is running on Heroku.

Here are a few additions that could be made to make it that probably wouldn’t be too hard to accomplish:

  • Include the option to include some JavaScript libraries in a Riddle (this could easily be done by simply linking to a CDN link for each respective JS library).
  • Include a live preview of a riddle as the user types it out (this could be done using JQuery and some Ajax)
  • Post riddles as gists on github (This gem would help with that)
  • The ability to share and embed riddles (the use of iframes makes this very easy to do)
  • Code highlighting in the textareas
  • … and the styling could be nicer!

Feel free to fork the GitHub repo and add any of these features!

I hope you’ve found this useful and also found some useful things about how Sinatra works. Please leave any feedback or ideas in comments below.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Ismael G Marin

    Thank you Darren, I really enjoy this serie, you write amazing articles, just one thing i think is easier to have just one line in the requeriments instead of:

    require ‘sinatra’

    %w[sinatra RedCloth coffee-script v8 liquid markaby less haml redcarpet sass slim dm-core dm-migrations].each {|a| require a}

  • rsludge

    Very useful article, thank you!

    I think, you forgot to mention to add @riddle = Riddle.new to new route.

  • illnino

    DAZ, is it the send method a helper from Sinatra? Any doc? Thanks.

    == send(@riddle.html_engine, @riddle.html)