Using Sinatra with Asset Pipeline and RailsAssets

Update

I've updated fetching gems from a specific bundler group. The previous version added vendor/assets path from all gems present in the Gemfile.

Background

Recently I've been thinking about different ways to seperate the view layer from Rails. With the recent hype for yeoman and grunt I've decided to give it a try. Trying to configure my default frontend stack (slim, bootstrap, coffee) made my head hurt very quickly. With my mind confused, I've began searching for a different alternative, preferably something based on Ruby.

Rack App

So I've created a new Rails project and began stripping it from ActiveModel, ActiveRecord, ActionController, etc. so only ActionView was left. Then I started to remove directories, gems and it occurred to me - 'What the hell am I doing?'. Instead of ripping Rails apart, I can start a clean Rack app and build from there.

So I mashed up a basic config.ru file (based on Heroku's tutorial).

Sinatra and Asset Pipeline

Next I wanted to have something similar to the Asset Pipeline in Rails. I scratched my head for about two hours, and decided, that I don't have time to figure out, how to do this in Rack (I'm lazy by nature), so I've searched for a bootstrapped solution. Naturally, I picked sinatra. I've created a basic app with Bundler:

├── Gemfile
├── Gemfile.lock
├── app.rb
├── config.ru
└── views
    └── index.slim

I've added my preferred frontend gems, alongside some utility gems, to the Gemfile:

source 'https://rubygems.org'

gem 'sinatra'  
gem 'slim'  
gem 'sass'  
gem 'puma'  
gem 'coffee-script'  
gem 'pry'  

And I created a basic config.ru file:

require 'rubygems'  
require 'bundler'  
Bundler.require  
run App  

In order to get some sort of asset pipeline, I searched for a way to integrate sprockets with sinatra. The result of that search was the sinatra-asset-pipeline gem.

Following the instructions in the README file, I've set up a basic layout, with a Sass and CoffeScript manifest file:

├── Gemfile
├── Gemfile.lock
├── Rakefile
├── app.rb
├── assets
│   ├── javascripts
│   │   └── app.coffee
│   └── stylesheets
│       └── app.css.sass
├── config.ru
└── views
    └── index.slim

In the view I've used the provided helpers, to include the assets:

doctype 5  
html  
  head
    title Sinatra with Asset Pipeline
    script src="#{javascript_path 'app'}"
    link rel="stylesheet" href="#{stylesheet_path 'app'}"

  body
    h1 Hello, World!

Enter RailsAssets

Recently, monterail released RailsAssets. If you're not familiar with RailsAssets, it's a way to specify bower packages in your Gemfile (for more info, go to the RailsAssets homepage).

I'm making heavy use of RailsAssets in my projects, and naturally I tried to use it inside my new app. Unfortunately this didn't work. I've added rails-assets-angular to my Gemfile and included in my app.coffee file. When I started the app, and visited the website, this is what greeted me:

Sprockets::FileNotFound at / couldn't find file 'angular'. Well that sucks. I didn't expect it to work, because I knew that, RailsAssets, go figure, only works with Rails, because it wraps all the assets inside a Rails Engine. That way all the vendor/assets inside the gem, are available in the Rails application.

I quickly found a way to modify the asset load path inside the sinatra-asset-pipeline gem:

set :assets_prefix, %w(assets vendor/assets)  

The question was, how to include the vendor/assets path, of the individual gems?

Bundler to the rescue

I've refactored my Gemfile to include all the asset gems inside a group:

source 'https://rubygems.org'  
source 'https://rails-assets.org'

group :core do  
  gem 'sinatra'
  gem 'slim'
  gem 'sass'
  gem 'bootstrap-sass', '~> 3.1.1'
  gem 'sinatra-asset-pipeline'
  gem 'puma'
  gem 'coffee-script'
  gem 'pry'
end

group :assets do  
  gem 'rails-assets-angular'
end

The next hard part was: how to fetch all the gems inside the :assets group?
The solution wasn't so obvious, I spent well over 2 hours browsing bundler's documentation to achieve this (If you have a better way than the one presented below, please, leave a comment).

require 'rubygems'  
require 'bundler'  
Bundler.require(:core, :assets)  
require 'sinatra/asset_pipeline'  
require './app'  
assets_path = %w(assets vendor/assets) + Bundler.definition.dependencies.map do |dep|  
  [dep.to_spec.full_gem_path, 'vendor', 'assets'].join('/') if dep.groups.include? :assets
end.compact

App.set :assets_prefix, assets_path  
App.register Sinatra::AssetPipeline  
run App

This solution is buttugly, but it works (for the moment). With this I finally could do:

#= require angular

Summary

The bootstrap-sass gem didn't have this problem, and worked out of the box. This is beacuse bootstrap-sass adds it's vendor/assets into Sass#load_paths (link to source). If we comment out that line of code, it wouldn't work, just as rails-assets-angular didn't. If you try to require the js file, it will fail just as rails-assets-angular, because javascripts are not added to the load path.

That's all! Stay tuned for a follow up post, with some refactoring and configuration patterns.