slim snippets

Sinatra App Template

Tagged sinatra, slim, pry, sprockets  Languages ruby

A quick and dirty template for Sinatra apps with many of the features you can find in Rails.

Supports: * Slim views * Sass stylesheets * Coffee script * Compass * Susy (susy.oddbird.net) * Asset pipeline (Sprockets) * View helpers * A console * I18n * Logging * ActiveRecord+migrations * Debugging with Pry * Flash messages * Class reloading in development mode * Security features such as CSRF, Cross Site Scripting, etc. All provided by Rack::Protection and Sinatra.

Create app.rb:

require 'slim'
require 'i18n'
require 'mysql2'
require 'sprockets'
require 'sprockets-sass'
require 'compass'
require 'susy'
require 'sinatra/base'
require 'sinatra/cookies'
require 'sinatra/content_for'
require 'sinatra/activerecord'
require 'sinatra/partial'
require 'rack-flash'

class App < Sinatra::Base
  enable :sessions, :logging
  register Sinatra::Partial

  use Rack::MethodOverride
  use Rack::Flash, :accessorize => [:info, :error, :success], :sweep => true
  use Rack::Protection::AuthenticityToken # HTML forms now require: input name="authenticity_token" value=session[:csrf] type="hidden"
  set :public_folder, File.dirname(__FILE__) + '/public'
  set :views, File.dirname(__FILE__) + '/app/views'
  set :slim, :layout_engine => :slim, :layout => :'layouts/default', :use_html_safe => true, :pretty => App.environment == :development
  set :partial_template_engine, :slim
  set :session_secret, "25729f31a6bc7c57f8575db9b79ee468...." # SecureRandom.hex(128)
  set :cookie_options, { path: '/'}

  def self.sprockets
    project_root = File.expand_path(File.dirname(__FILE__))
    assets = Sprockets::Environment.new(project_root)
    assets.append_path('app/js')
    assets.append_path('app/css')
    # Twitter Bootstrap...
    assets.append_path('lib/bootstrap/js')
    assets.append_path('lib/bootstrap/css')
    assets
  end

  helpers Sinatra::Cookies
  helpers Sinatra::ContentFor
  helpers do
    def t(*args)
      ::I18n::t(*args)
    end
    def authenticity_token
      session[:csrf] = SecureRandom.hex(128) unless session.has_key?(:csrf)
      %Q{<input type="hidden" name="authenticity_token" value="#{session[:csrf]}"/>}
    end
  end

  configure do
    # Configure logging, WTF
    set :logging, false
    class ::Logger; alias_method :write, :<<; end
    logfile = File.join(App.root, 'log', "#{App.environment}.log")
    # Send STDs to log file
    $stdout.reopen(logfile)
    $stderr.reopen(logfile)
    $stderr.sync = true
    $stdout.sync = true
    # Weekly roll
    log  = Logger.new(logfile, 'weekly')
    log.level = Logger::DEBUG
    # use Rack::CommonLogger, log
    set :log, log

    Compass.add_project_configuration(File.join(Sinatra::Application.root, 'config', 'compass.rb'))
  end

  configure :development do
    require "sinatra/reloader"
    register Sinatra::Reloader
    also_reload 'app/**/*.rb'
    also_reload 'lib/**/*.rb'
    also_reload 'conf/**/*.rb'
    set :raise_errors, true
  end

  [:error, :info, :success].each do |key|
    class_eval "
    def flash_#{key}(key, now=true)
      message(key, :#{key}, now)
    end
    "
  end

  def message(key, type=:notice, now=true)
    hash = now ? flash.now : flash
    hash[type] = I18n.t(key)
  end

  # Set view variables, e.g.:
  # - meta :title, "Page title"
  # Retrieve view variables, e.g.:
  # = meta :title
  def meta(key, value = nil)
    value ? content_for(key) { value } : yield_content(key)
  end

  # Helper method for creating HTML tags:
  # content_tag :a, 'Contact us', href: 'mailto:xxx@xxx.com'
  def content_tag(name, content, attributes = nil)
    name = html_escape(name) unless name.html_safe?
    content = html_escape(content) unless content.html_safe?
    attributes = attributes.map do |name, value|
      value = html_escape(value) unless value.html_safe?
      %Q{#{name}="#{value}"}
    end if attributes && attributes.any?
    start = [name, attributes.join(" ")].reject(&:nil?).join(' ')
    "<#{start}>#{content}</#{name}>"
  end

  def debug_something_with_pry
    Kernel.binding.pry
  end

  error do
    slim :'errors/500'
  end

  not_found do
    slim :'errors/404'
  end

  error ActiveRecord::RecordNotFound do
    slim :'errors/404'
  end

  before do
    I18n.locale = params[:locale] || I18n.default_locale
  end
end

# Require attr_accessible...
ActiveRecord::Base.send(:attr_accessible, nil)

# Move to config/init/db.rb if you like
OpenStruct.new(YAML::load(File.open('config/database.yml'))[App.environment.to_s].symbolize_keys).tap do |config|
  ActiveRecord::Base.establish_connection(
    host: config.host,
    adapter: config.adapter,
    database: config.database,
    username: config.username,
    password: config.password
  )
end

%w(models controllers concerns).each do |name|
  Dir[File.join('app', name, '**/*.rb')].each do |file|
    require_relative file
  end
end

# Move to config/init/i18n.rb if you like
Dir[File.join(App.root, 'config', 'locales', '*.yml')].each do |file|
  I18n.backend.load_translations(file)
end
I18n.default_locale = :en

# Move to app/controllers/root_controller.rb
class RootController < App
  get '/' do
    flash_alert "Thanks for nothing"
    slim :'index'
  end
end

Create Gemfile:

source :rubygems
gem 'thin'
gem 'slim'
gem 'sass'
gem 'sinatra'
gem 'sprockets'
gem 'sprockets-sass'
gem 'compass'
gem 'susy'

gem 'sinatra-partial'
gem 'coffee-script'
gem 'therubyracer'
group :development do
  gem 'sinatra-reloader'
  gem 'sinatra-activerecord'
  gem 'pry'
end
gem 'rack-flash3'
gem 'awesome_print'
gem 'mysql2'

Create Rakefile:

require 'bundler/setup'
require 'sinatra/activerecord/rake'
require 'pry'
require './app'

Dir[File.join('lib', 'tasks', '**', '*.rake')].each do |file|
  import file
end

task :console do
  binding.pry
end

# Asset pipeline (Sprockets)
namespace :assets do
  task :precompile do
    App.sprockets['application.js'].write_to('public/assets/application.js')
    App.sprockets['application.css'].write_to('public/assets/application.css')
  end
end

Create config/database.yml:

development:
  adapter: mysql2
  database: xxx_development
  username: root
  password: 
  host: localhost

Create config.ru:

require './app'
map "/" do
  run RootController
end

map "/assets" do
 run App.sprockets
end

Run:

mkdir -p app/controllers app/models app/concerns app/views/layouts app/css app/js lib tmp log public/img config/init db/migrate lib/tasks
bundle
bundle exec thin start -p 3000

Fix for "undefined method encoding' for nil:NilClass"

Tagged slim, cgi  Languages ruby

This code:

img src="/xyz/#{@house.address}"

Caused this:

undefined method encoding' for nil:NilClass

/usr/local/rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/cgi/util.rb in escape
    encoding = string.encoding

Why? Because address is nil and Slim tried to escape nil with CGI.escape, which doesn't work. The error was reported to be on line x when the error was on line y…

Gulpfile.js example with Slim, Coffeescript, Sass, Browser Sync, Lint

Tagged gulp, slim  Languages coffeescript

A Gulpfile.js example with Slim, Coffeescript, Sass, Browser Sync, Lint. The script is suitable for building e.g. static websites and prototyping mobile apps.

Gulpfile expects your project to be structured in the following way (configurable): - src/css/*.scss,*.css - src/js/*.coffee,*.js - src/*.slim

Output is written to "www" folder.

#
# Gulpfile with:
#
# - Slim
# - Sass
# - Lint
# - Browsersync
# - CSS and HTML compression
#
# Install dependencies:
#
#   $ npm install gulp gulp-concat gulp-uglify event-stream gulp-coffee gulp-sass gulp-cssmin gulp-coffee gulp-coffeelint browser-sync gulp-util gulp-shell
#
# Then start developing:
#
#   $ gulp
#

gulp        = require 'gulp'
concat      = require 'gulp-concat'
es          = require('event-stream')
sass        = require 'gulp-sass'
uglify      = require 'gulp-uglify'
streamqueue = require 'streamqueue' # Preserves file order (vendor...)
coffee      = require 'gulp-coffee'
gutil       = require 'gulp-util'
shell       = require 'gulp-shell'
cssmin      = require 'gulp-cssmin'
coffeelint  = require 'gulp-coffeelint'
browserSync = require 'browser-sync'

isProd = gutil.env.type is 'prod'

sources =
  sass: 'src/css/**/*.scss'
  css: 'src/css/**/*.css'
  html: 'src/**/*.slim'
  js: 'src/js/**/*.js'
  coffee: 'src/js/**/*.coffee'

targets =
  css: 'www/css'
  html: 'www/'
  js: 'www/js'

# Check for errors
gulp.task 'lint', ->
  gulp.src(sources.js)
    .pipe(coffeelint())
    .pipe(coffeelint.reporter())

# Compile Coffeescript
gulp.task 'js', ->
  stream = streamqueue(objectMode: true)
  # Vendor files
  stream.queue(gulp.src(sources.js))
  # App files use Coffee
  stream.queue(gulp.src(sources.coffee).pipe(coffee(bare:true)))
  stream.done()
    .pipe(concat("all.js"))
    .pipe(if isProd then uglify() else gutil.noop())
    .pipe(gulp.dest(targets.js))

# Compile Slim
gulp.task 'slim', ->
  gulp.src(sources.html)
    .pipe(shell(["slimrb -r ./lib/helpers.rb -p <%= file.path %> > ./#{targets.html}/<%= file.relative.replace(\".slim\", \".html\") %>"]))

# Compile CSS
gulp.task 'css', ->
  stream = streamqueue(objectMode: true)
  # Vendor files
  stream.queue(gulp.src(sources.css))
  # App files
  stream.queue(gulp.src(sources.sass).pipe(sass(style: 'expanded', includePaths: ['src/css'], errLogToConsole: true)))
  stream.done()
    .pipe(concat("all.css"))
    .pipe(if isProd then uglify() else gutil.noop())
    .pipe(gulp.dest(targets.css))

# Reload browser
gulp.task 'server', ->
  browserSync.init null,
    open: true
    server:
      baseDir: targets.html
    reloadDelay: 2000 # Prevent white screen of death
    watchOptions:
      debounceDelay: 1000

# Watch files for changes
gulp.task 'watch', ->
  gulp.watch sources.js, ['js']
  gulp.watch sources.css, ['css']
  gulp.watch sources.html, ['slim']
  gulp.watch 'www/**/**', (file) ->
    browserSync.reload(file.path) if file.type is "changed"

# Build everything
gulp.task 'build', ['lint', 'js', 'css', 'slim']

# Start a server and watch for file changes
gulp.task 'default', ['watch', 'server']

Also see A gulp.js template for React.js and ES6 projects