Converting ERB to Slim

I looked around and there was seemingly no easy way to convert Rails apps to use Slim instead of ERB. There was a gem out there, but it didn’t seem to work for me.

The general process I used was to first convert ERB to Haml using Haml’s haml2html. Next, convert Haml to Slim using haml2slim.

Convert ERB to Haml

Ensure you have Haml installed, preferably using your Gemfile if using Bundler. You can probably remove Haml when you are done with this process.

I got much inspiration from this article on the conversion process.

# to test what you will convert
find . -name '*html.erb' | \
xargs ruby -e 'ARGV.each { |i| puts "html2haml -r #{i} #{i.sub(/erb$/,"haml")}"}'

# to convert
find . -name '*html.erb' | \
xargs ruby -e 'ARGV.each { |i| puts "html2haml -r #{i} #{i.sub(/erb$/,"haml")}"}' | bash

Somewhere in this process the whitespace gets mostly stripped out. This is unfortunate, but should take less time overall to fix than converting over by hand.

Then, assuming you are using version control (not sure how much longer I will believe that this is not universal), remove the .erb files.

rm -rf "**/*.html.erb" # works in zsh at least...

This is important because Rails will look at your *.erb files first, so if you don’t delete them then the results of running html2haml won’t be seen.

Fix anything that is broken. In my case it was some illegal nesting and bad indentation problems in a couple of partials, as well as some other things that needed to be changed.

Look through the app in the browser if your tests don’t cover all of the views and for a sanity check. You may have to restart your server if you installed Haml.

Convert Haml to Slim

Ensure you have Slim installed, preferably using your Gemfile if using Bundler. Run gem install haml2slim (we don’t need this in the Gemfile since it’s for one-time use not in the application.)

Slim template files have the ending extension .slim, like their counterparts ERB (.erb) and Haml (.haml).

Again, we use a converter.

find . -name '*html.haml' | xargs ruby -e 'ARGV.each { |i| puts "haml2slim #{i} #{i.sub(/haml$/,"slim")}"}' | bash

Again, we delete the extra files (*/.html.haml this time).

Again, we fix anything that is broken.

Now hopefully your app should be fully converted to use Slim! Suddenly a puppy appears and the sun comes out from behind the clouds.

Bundler see what updates are available

The command to see what gems have more recent versions and can be updated is bundle outdated. This will show you all gems that could be upgraded, but unlike bundle update, will not actually update your gems (changing Gemfile.lock and installing new gems.)

It will output something like:

$ bundle outdated          
Updating git://github.com/panozzaj/andand.git
Fetching gem metadata from http://rubygems.org/.......
Fetching gem metadata from http://rubygems.org/..

Outdated gems included in the bundle:
  * ZenTest (4.6.2 > 4.5.0)
  * activesupport (3.1.1 > 3.0.9)
  * builder (3.0.0 > 2.1.2)
  * i18n (0.6.0 > 0.5.0)
  * activemodel (3.1.1 > 3.0.9)
  * erubis (2.7.0 > 2.6.6)
  * rack (1.3.4 > 1.2.4)
  * rack-mount (0.8.3 > 0.6.14)
  * rack-test (0.6.1 > 0.5.7)
  * actionpack (3.1.1 > 3.0.9)
  * mail (2.3.0 > 2.2.19)
  * actionmailer (3.1.1 > 3.0.9)
  * arel (2.2.1 > 2.0.10)
  * activerecord (3.1.1 > 3.0.9)
  * activeresource (3.1.1 > 3.0.9)
  * devise (1.4.2 2a5ad46 > 1.4.2 e76ba05)
  * railties (3.1.1 > 3.0.9)
  * rails (3.1.1 > 3.0.9)
  * sequel (3.28.0 > 3.20.0)
  * sinatra (1.3.1 > 1.0)

To break it down, “sinatra (1.3.1 > 1.0)” means that I have version 1.0 and version 1.3.1 is available.

Potential stumbling blocks

Getting a ‘Could not find task “outdated”.’ error message when trying to run bundle outdated?

At the time of this writing you need to have the right version of Bundler installed with gem install bundler --pre. This installs Bundler version 1.1.rc, which should also be much faster than earlier versions of Bundler. I would expect this portion of this post to be outdated in the near future.

Making Recommendations with Apache Mahout Presentation

Last month I gave a presentation about making recommendations with Apache Mahout. Since the presentation, Manning books has released the final version of their book, Mahout in Action, which should be an even better resource than the book that I was using for my slides and presentation. Here are the slides:

(Having trouble seeing the slides? Try here.)

Slim "Failure/Error: render ActionView::Template::Error: Unexpected end of file"

I was getting an error from Slim template saying:

Failure/Error: render
ActionView::Template::Error:
  Unexpected end of file
    my_file.html.slim, Line 15

, where the file had only 15 lines

I searched for the error and found:

# https://github.com/stonean/slim/blob/master/lib/slim/parser.rb
def parse_broken_line
  broken_line = @line.strip
  while broken_line[-1] == ?\\
    next_line || syntax_error!('Unexpected end of file')
    broken_line << "\n" << @line.strip
  end
  broken_line
end

What this means is that I had an extra trailing slash on one of the lines in a Slim escape that indicates that there are multiple lines. Slim was expecting another line, but didn’t see it and instead saw the end of the file. Removing the extra trailing slash fixed the problem.

How I Patched Devise to Force Login for Twitter and Facebook

Here were some of the things that I had to do to Devise to get things working correctly with Twitter and Facebook. YMMV, as this was six months ago and I had some special requirements potentially.

# config/initializers/omniauth_patch.rb
# see http://stackoverflow.com/questions/1960957
module OmniAuth
  module Strategies
    # override authorize path to force user to login each time
    class Twitter < OmniAuth::Strategies::OAuth
      def initialize(app, consumer_key = nil, consumer_secret = nil, options = {}, &block;)
        client_options = {
          :site => 'https://api.twitter.com'
        }

        client_options[:authorize_path] = '/oauth/authorize'
        super(app, :twitter, consumer_key, consumer_secret, client_options, options)
      end
    end
  end
end
# config/environments.rb
config.omniauth :twitter, 'XXXXXXXXXXXXX', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

# Created multiple Facebook apps for testing because you can only have
# one per domain. One way to lessen these would be to set up staging.myapp.com, etc.
# since Facebook respects subdomains as being on the same domain. Would still need one for
# localhost testing though (unless I set up hosts file differently?)
# This seems simplest for now though.
id, secret = case ENV['RACK_ENV']
when 'production'
  ['XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX']
when 'staging'
  ['XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX']
else
  ['XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'] # localhost
end
config.omniauth :facebook, id, secret, { :display => :touch }

The last line ensures that we use the facebook touch view because I was only targeting mobile devices (iPhone). I’m not sure how we would easily do this at runtime besides further patching. Basically this limits us because we have to use the touch even though we want to use the web version on the web.

When signing out, I want to ensure that I sign out of all of the services that I might be signed into. I do a bit of a hack in the sign out view with:

Not you? #{link_to 'Sign out', "http://m.facebook.com/logout.php?confirm=1&next;=#{destroy_user_session_url}"}

Basically we ask facebook to sign us out first, and then go into the normal sign out.