653 snippets – displaying 1–30

How to parse a cronline/crontab/cron with Ruby

You can use the rufus-scheduler gem to parse cronline/crontab/cron with Ruby.

Gemfile:

gem 'rufus-scheduler'

Example:

require 'rufus/scheduler/cronline'
cron = Rufus::Scheduler::CronLine.new("00 00 1 * *") # 00:00 every first day of the month
#
next_time = cron.next_time(Time.current)
previous_time = cron.previous_time(Time.current)

rufus-scheduler’s cronline implementation:
https://github.com/jmettraux/rufus-scheduler/blob/master/lib/rufus/scheduler/cronline.rb

See tests for details and syntax:
https://github.com/jmettraux/rufus-scheduler/blob/master/spec/cronline_spec.rb

Cron configuration examples.

Playing with Ruby and Rails Time and Time Zones

Checklist

What you need to know to handle date and time correctly in Ruby and Rails is, at least:

  • the input’s time zone when parsing time and date.
  • the user’s time zone when calculating time and date in his/her time zone.
  • Ruby and Rails date and time handling. Use Time.current and Time.zone (Rails) to avoid using the computer’s time zone (Ruby).

Things to remember

  • The correct time zone is needed when you’re accessing date components like e.g. year, month, day, hour, start of day, start of month, etc
  • Rails takes care of time and date handling inside the application. It’s when you accept input or want to output data that you need to tread carefully.
  • Cache carefully. Maybe you need to display date and time using JavaScripts.
  • Daylight savings, plus some countries or states don’t even have DST, but might have had 100 years ago…
  • The difference between e.g. Time.now and Time.current.
  • Possible timezones: application, user, server, and database timezone
  • APIs should output ISO-8601

require 'tzinfo'
tokyo = TZInfo::Timezone.get('Asia/Tokyo')

#
# When it's 12:00 in Tokyo, what hour is it in UTC?
#
time = tokyo.local_to_utc Time.local(2014, 01, 01, 12, 00) # => 2014-01-01 03:00:00 UTC
utc_hour = time.hour # => 3
utc_time = time.utc # => 2014-01-01 03:00:00 UTC

#
# When it's 12:00 in UTC, what hour is it in Tokyo?
#
time = tokyo.utc_to_local Time.local(2014, 01, 01, 12, 00) # => 2014-01-01 21:00:00 UTC
tokyo_hour = time.hour # => 21
tokyo_time = tokyo.local_to_utc(time) # => 2014-01-01 12:00:00 UTC

#
# Convert Tokyo time to local time, i.e. convert user's local time to our local time
#
tokyo.now # => Tue, 18 Nov 2014 03:02:28 JST +09:00
tokyo.now.utc # => 2014-11-17 18:04:20 UTC
# local_to_utc => -0900
tokyo.local_to_utc tokyo.now # => 2014-11-17 18:03:02 UTC
# utc_to_local => +0900
tokyo.utc_to_local tokyo.now # => 2014-11-18 12:03:17 UTC
Time.parse Time.find_zone!('Tokyo').now.to_s

#
# Parse input from Helsinki and input from Stockholm when input contains no time zone information
#
a = Time.find_zone!('Helsinki').parse('201411121415')
b = Time.find_zone!('Stockholm').parse('201411121315')
a == b

a = Time.use_zone 'Helsinki' do
  Time.parse('201411121415')
end
b = Time.use_zone 'Stockholm' do
  Time.zone.parse('201411121315')
end
a == b # OK

#
# Using the tzinfo library directly
#
TZInfo::Timezone.all_country_zones
zone = TZInfo::Timezone.get('Africa/Harare')

#
# Rails time zones
#
ActiveSupport::TimeZone.zones_map.values.map(&:name).sort

#
# Daylight savings, anyone?
#
tokyo = TZInfo::Timezone.get('Asia/Tokyo')
tokyo.current_period.utc_offset # Doesn't include DST
tokyo.current_period.utc_total_offset # Includes DST

Time.use_zone 'Helsinki' do
  Time.local(2014, 10, 26).dst? # => true
  Time.local(2014, 10, 27).dst? # => false
end

Time.use_zone 'Pacific Time (US & Canada)' do
  # 00:00 01.06.2014 in Los Angeles, during DST
  now = Time.zone.local(2014, 6, 1)
  puts now.dst? # => true
  offset = now.utc_offset
  puts offset / 1.hour # => -7
  # 00:00 01.12.2014 in Los Angeles, after DST
  now = Time.zone.local(2014, 12, 1)
  puts now.dst? # => false
  offset = now.utc_offset
  puts offset / 1.hour # => -8
end

#
# Beginning of day in other time zone
#
Time.use_zone 'Hawaii' do
  now = Time.local(2014, 10, 27)
  puts now.beginning_of_day.dst? # false, Hawaii doesn't follow DST although the rest of the U.S.A. does.
  puts now.beginning_of_day.utc # => 2014-10-26 22:00:00 UTC
  puts now.beginning_of_day # => 2014-10-27 00:00:00 +0200
end

#
# Misc stuff
#

Time.now.formatted_offset # => "+02:00"

def without_timezone
  times = [
    Time.now,
    Time.zone.now,
    Time.current,
    Time.now.utc
  ]

  times.each do |time|
    puts times[0].to_i == time.to_i # All OK
  end
end

def with_timezone
  times = nil
  Time.use_zone('Tokyo') do
    times = [
      Time.now,
      Time.zone.now,
      Time.current,
      Time.now.utc
    ]
  end
  Time.use_zone('Stockholm') do
    times.each do |time|
      puts times[0].to_i == time.to_i # All OK
    end
  end
end

def parse_time
  times = nil
  time = '201411121415'
  Time.use_zone('Tokyo') do
    times = [
      Time.parse(time),
      Time.parse(time).in_time_zone,
      Time.zone.parse(time).in_time_zone, # ERROR: 7 hour time difference
      Time.zone.parse(time) # ERROR: 7 hour time difference
    ]
  end
  Time.use_zone('Stockholm') do
    times.each do |time|
      binding.pry unless times[0].to_i == time.to_i
    end
  end
end

without_timezone
with_timezone
parse_time

Also see Working with time zones in Ruby on Rails.

How to run only one specific test with Rspec

Focusing on one or more tests with Rspec

You can tell Rspec to run only tests tagged with “focus: true” by running Rspec with the —tag focus switch:

rspec ./spec/controllers/concerns/session_expiry_spec.rb --tag focus

This would run e.g. only this test which is marked with “focus: true”:

it "resets session if session has expired", focus: true do
...
end

Integrating with Guard

Use this configuration to integrate this workflow with Guard:

guard 'rspec', cli: '--tag focus --format d --color', all_after_pass: false, all_on_start: false do
...
end

We also want to run all tests when there are no focused tests. We therefore add this to spec/spec_helper.rb:

RSpec.configure do |config|
  # This is dangerous with CI integration e.g. Jenkins
  # config.filter_run focus: true
  config.run_all_when_everything_filtered = true
end

Rspec configuration file

One alternative is to enable the focus option in the Rspec configuration file:

RSpec.configure do |config|
  config.filter_run :focus => true
  config.run_all_when_everything_filtered = true

However, this would be dangerous when running tests with e.g. Jenkins and when someone forgets to remove “focus: true”.

I18n for ActiveRecord Model Attributes

This code will translate the AR model’s title attribute using Rails’ I18n library:

class Link < ActiveRecord::Base
  I18N_ATTRIBUTES = [ :title ]
  I18N_ATTRIBUTES.each do |attr|
    class_eval <<-EORUBY, __FILE__, __LINE__ + 1
      def #{attr}
        I18n.t(self[:#{attr}], default: self[:#{attr}])
      end
    EORUBY
  end
end

If a translation is not defined, the code will fall back to use the attribute’s original value.

Example with translation defined:

Translation file (config/locales/en.yml):

en:
  views:
    index:
      title: Hello

Model code (app/models/link.rb):

link.title = 'views.index.title'
# Uses string from config/locales/en.yml
link.title => "Hello"

Example without translation defined

link = Link.new title: 'Hello'
# Fall back to specified value, because no translation is defined
link.title => "Hello"

How to draw centered text on an HTML canvas

Draw centered text on an HTML canvas:

canvas = ...
// Device dependent pixel ratio, also changes when zooming
pixelRatio = window.devicePixelRatio || 1
// Centered text
ctx.textAlign = "center"
ctx.textBaseline = "middle"
// Font size
fontSize = 20
// Adaptive bold font
ctx.font = 'bold ' + Math.round(fontSize/pixelRatio) + 'px Oswald'
// Measure text width
textRect = ctx.measureText(text)
// Figure out where to position the text
x = (canvas.width / 2) / pixelRatio
y = ((canvas.height / 2) - (fontSize*2)) / pixelRatio
// Draw the text
ctx.fillText("Hello world" , x, y)

The code adapts the font size and text position to fit the selected zoom level (e.g. 200%) and the current device (e.g. mobile).

How to Use the JSON and HStore Postgres Data Types With Rails 4

First enable the hstore Postgres extension. In this example we define both a JSON and an HStore column:

class Schema < ActiveRecord::Migration
  def change
    enable_extension "hstore"
    create_table :links do |t|
      t.hstore :data
      t.column :settings, :json
    end
  end
end

Next, we specify accessors for the data that we will be stored in the JSON and HStore columns:

class Link < ActiveRecord::Base
  # Use hstore for text-only data:
  store :data, :name, :url, :description
  # Use JSON to support string, number, object, array, true, false, null
  store :settings, :update_interval, :created_at, :updated_at

We can now use the defined ActiveRecord attributes to store and access JSON and HStore data:

Link.create! name: 'Google', url: 'http://google.com', description: 'Ad company', update_interval: 1.day, created_at: Time.now.utc

Querying the data is where you’ll see the biggest differences. Two examples:

# hstore
Link.where("data -> 'name' = ?", 'Google')
# json
Link.where("CAST(settings->>'update_interval' as integer) = ?", 1.day.to_s)

Notes for Postgres 9.3:

  • HStore can store only text. Nested data is not supported.
  • JSON supports the following types: string, number, object, array, true, false, null. For example, date and time types are not supported. Nested data is supported.

Rails prepend_view_path

Good

class ApplicationController < ActionController::Base
  prepend_view_path 'x/y/z/views'
end

Bad

# config/initializer/view_path.rb
ApplicationController.prepend_view_path 'x/y/z/views'

In development mode (Rails 3.x) this doesn’t seem to work under some circumstances, i.e. the prepended path is lost after Rails’ reloads after you modify certain files.

How to Use Rails I18n With JavaScript/CoffeeScript

Option 1: Include all translations

Your app/assets/javascripts/i18n.js.coffee.erb file:

root = exports ? this
I18n = <%= I18n.backend.send(:translations).to_json.html_safe %>
$ ->
  root.I18n = I18n[$('body').data('lang')]

Option 2: Include a limited set of translations

Your app/assets/javascripts/i18n.js.coffee.erb file:

root = exports ? this

I18n =
<%
I18n.available_locales.each do |lang|
  I18n.with_locale lang do
%>
  <%= lang %>:
    text: "<%= I18n.t("js.text") %>"
<%
  end
end
%>

$ ->
  root.I18n = I18n[$('body').data('lang')]

Include the Selected Locale in the Rails Layout

The view defines the user’s selected locale:

<body data-lang="<%= I18n.locale %>">
...
</body>

How to Translate JavaScript Strings

We can now translate strings in a JavaScript file:

# remember to use $(document).ready...
alert(I18n.text)

How to Create and Configure a Static Website Hosted on Amazon S3

This will create two S3 buckets and configure them for hosting a static website:

aws s3 mb s3://xxx.com
aws s3 mb s3://www.xxx.com

aws s3api put-bucket-website --bucket www.xxx.com --website-configuration '{
    "RedirectAllRequestsTo": {
        "HostName": "xxx.com"
    }
}'

aws s3api put-bucket-website --bucket xxx.com --website-configuration '{
    "IndexDocument": {
        "Suffix": "index.html"
    },
    "ErrorDocument": {
        "Key": "404/index.html"
    }
}'

aws s3api get-bucket-website --bucket www.xxx.com
aws s3api get-bucket-website --bucket xxx.com

Next, allow anyone to access the files:

aws s3api put-bucket-policy --bucket www.xxx.com --policy '{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AddPerm",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::www.xxx.com/*"
    }
  ]
}'

Next, upload your website to S3:

aws s3 sync build s3://www.xxx.com --acl public-read --delete

In the above example all files are located in a folder named build.

Done. Your website can be found at http://<bucket name>.s3.amazonaws.com/index.html.

Note:

  • if you’re serious about hosting your website, use AWS Cloudfront or Cloudflare + S3
  • the “www” URL redirects to the “non-www” URL

Automatic Instantiation of Web Components Written in JavaScript/Coffeescript

componentschristianVersion 9

Your app.js.coffee:

$ ->
  for component in $('.web-component')
    component = $(component)
    class_name = component.data('class')
    id = "#" + component.attr('id')
    component_class = eval.call(window, class_name)
    if component_class
      console.debug("Creating #{class_name}")
      try
        new component_class(id)
      catch error
        console.log "Failed to create #{class_name}"
    else
      throw "UI component '#{class_name}' does not exist."

Declare a web component in your view:

<div id="alerts" class="web-component" data-class="App.Alerts"/>

Write your web component, e.g. app/assets/javascripts/components/alert.js.coffee:

root = exports ? this
# declared elsewhere
# root.App = {}

class Alerts
....

root.App.Alerts = Alerts

Render Rabl Views Anywhere (views, jobs, mails)

A helper for rendering Rabl views anywhere, e.g. background jobs, views, mails, etc:

def render_rabl(locals, view, options = {})
  scope = Object.new
  locals.each do |key, value|
    scope.instance_variable_set :"@#{key}", value
  end
  options = { format: :json, scope: scope }.merge(options)
  Rabl.render(nil, view, options)
end

Usage:

locals = { users: User.all }
view = 'users/index'
render_rabl locals, view

A Reusable JavaScript Modal Dialog Based on Bootstrap Modal

The Coffeescript class:

root = exports ? this

class Modal
  constructor: (options = {}) ->
    @options = options
    @id = options.id ? 'modal-dialog-' + Math.round((Math.random() * 1000))
    @dom_id = "##{@id}"
    @onPrimaryButton = options.onPrimaryButton ? ->
    @onSecondaryButton = options.onSecondaryButton ? ->
    @template = options.template ? HandlebarsTemplates['modal'](id: @id)
    @render()

  render: ->
    $('body').append(@template)
    @modal = $(@dom_id)
    _this = this
    @modal.find('.btn-primary').on 'click', ->
      _this.onPrimaryButton(this)
    @modal.find('.btn-secondary').on 'click', ->
      _this.onSecondaryButton(this)

  setButtonText: (primary, secondary) ->
    @modal.find('.btn-primary').html(primary)
    @modal.find('.btn-secondary').html(secondary)

  setTitle: (title) ->
    @modal.find('.title').html(title)

  setBody: (body) ->
    @modal.find('.body').html(body)

  show: ->
    @modal.modal('show')

  hide: ->
    @modal.modal('hide')

root.Modal = Modal

The view (bootstrap 2.3.2):

<div class="modal hide fade" id="{{id}}">
  <div class="modal-header">
    <button aria-hidden="true" class="close" data-dismiss="modal" type="button"> &times;</button>
    <h3 class="title"></h3>
  </div>
  <div class="modal-body body">
  </div>
  <div class="modal-footer">
    <button class="btn btn-secondary" data-dismiss="modal">Cancel</button>
    <button class="btn btn-primary" data-dismiss="modal">Ok</button>
  </div>
</div>

Usage, e.g.:

class Notifications
  constructor:  ->
    @modal = new Modal
      onPrimaryButton: $.proxy(@onItemDelete, this)
      onSecondaryButton: $.proxy(@onItemRead, this)
  onItemDelete: ->
    # do something when primary button was clicked
  onItemRead: ->
    # do something when secondary button was clicked

Selecting a JavaScript Chart Library

Which JavaScript library should I use for generating charts? Here’s a list of popular JavaScript libraries for generating charts. Remember to check the license before selecting one…

http://chartjs.org

http://c3js.org/

  • based on D3.js
  • +3000 followers on Github
  • +150 issues on Github

http://d3js.org/

  • +30 000 followers on Github
  • +150 issues on Github
  • unlimited possibilities
  • unlimited complexity
  • unlimited popularity
  • you need to write your own reusable d3.js components
  • supports Firefox, Chrome, Safari, Opera, IE9+, Android and iOS. Parts of D3 may work in older browsers.

http://nvd3.org/

  • based on D3.js
  • +3000 followers on Github
  • +250 issues on Github

http://dimplejs.org/

  • based on D3.js
  • +900 followers on Github
  • +30 issues on Github

https://github.com/fastly/epoch

  • 3500 Github followers
  • 30 Github issues

https://github.com/gionkunz/chartist-js

  • 4000 Github followers
  • 10 Github issues

https://github.com/tenXer/xcharts/

  • 1500 Github followers
  • 30 Github issues
  • based on D3.js

http://www.flotcharts.org/

  • Works with Internet Explorer 6+, Chrome, Firefox 2+, Safari 3+ and Opera 9.5+
  • requires jQuery
  • +3500 followers on Github
  • +250 issues on Github

http://g.raphaeljs.com/

  • +1400 followers on Github
  • +110 issues on Github
  • supports Firefox 3.0+, Safari 3.0+, Opera 9.5+ and Internet Explorer 6.0+.

http://www.fusioncharts.com/

  • “23 000 customers”
  • price is “as much as you can pay”

http://www.highcharts.com/

  • Free for non-commercial projects
  • HTML5
  • “tens of thousands of developers”
  • supports line, spline, area, areaspline, column, bar, pie, scatter, angular gauges, arearange, areasplinerange, columnrange and polar chart

https://github.com/ankane/chartkick

  • Uses Google charts behind the scenes

http://metricsgraphicsjs.org/

  • Based on d3.js
  • 385 followers on Github
  • 18 issues on Github
  • “optimized for visualizing and laying out time-series data”

Getting Started With React.js and Rails

Why use React.js?

  • React ~35kb vs. Amber.js ~100kb
  • Easy to learn compared to Ember.js and Angular.js
  • Supports server-side rendering
  • Great performance
  • Angular.js documentation is horrible
  • Angular.js is the new EJB?

Why shouldn’t you use React.js?

  • React.js is difficult to integrate with JS libraries that move or create DOM elements, e.g. jQuery UI
  • React.js is still evolving. Expect API changes to break your app.

Installation

Gemfile:

gem 'active_model_serializers' # You probably want this gem too...
gem 'react-rails', '~> 1.0.0.pre', github: 'reactjs/react-rails'

From the console run:

bundle
rails g react:install

Make sure you have this in app/assets/javascript/application.js:

# ...
#= require react
# For the <div data-react-class...> syntax
#= require react_ujs
#= require_tree components

Add this to config/environments/production.rb

Rails.config.react.variant = :production

And this to config/environments/development.rb

Rails.config.react.variant = :development

Hello World

Create app/assets/javascripts/components/hello.jsx.coffee (note the file extension):

###* @jsx React.DOM ###

window.Hello = React.createClass
  render: ->
    `<div>{this.props.name}</div>`

app/views/layouts/application.html.erb

<html><div data-react-class="Hello" data-react-props=<%= { name: 'John' }.to_json %>/>
or:
  <%= react_component 'Hello', name: 'John' %>
....
</html>

Troubleshooting

  • Unexpected token ‘<’ Error

Did you forget to add this to the top of your React component file (.js.jsx)?

/** @jsx React.DOM */

or this to your CoffeeScript component (.jsx.coffee):

###* @jsx React.DOM ###

  • Target container is not a DOM element.

Does the DOM element exist?

Did you forget to call React.renderComponent in jQuery’s $(document).ready?

  • Adjacent XJS elements must be wrapped in an enclosing tag

Multiple nodes need a parent element:

<ul> // Doesn't work without <ul> wrapper
  <li></li>
  <li></li>
</ul>

  • Still doesn’t work?

react-rails is not stable; expect it to break.

Gotchas

Solutions:
React.js Google Group
React.js and jQuery sortable
React.js and select2
React.js and Bootstrap Modal
React.js and Kendo UI

  • You need to change the HTML “class” attribute to “className” in JSX

How to configure a no-www redirection on Amazon AWS (S3 and Cloudfront)

How do you configure a site hosted on Amazon AWS, e.g. a static website, to redirect from “www” to the “no-www” domain (aka “naked domain”)? The tool for the job is the AWS CLI.

For example, this creates a redirection from www.×.com to x.com for a bucket named www.×.com:

aws s3api put-bucket-website --bucket www.x.com --website-configuration '{
    "RedirectAllRequestsTo": {
        "HostName": "x.com"
    }
}'

Use this command to check that the configuration is set properly:

aws s3api get-bucket-website --bucket www.x.com

World's Smallest Screenshot Server?

require 'base64'
require 'selenium-webdriver'

class ScreenshotMiddleware
  def initialize(app)
    @app = app
  end

  def screenshot(url, width, height, file)
    begin
      driver = Selenium::WebDriver.for :firefox
      driver.manage.window.resize_to(width, height)
      driver.navigate.to url
      driver.save_screenshot("screenshots/#{file}")
      file
    ensure
      driver.quit
    end
  end

  def call(env)
    req = Rack::Request.new(env)
    width = req.params.fetch('width') { 1024 }
    height = req.params.fetch('height') { 768 }
    url = req.params.fetch('url')
    file = Base64.urlsafe_encode64(req.params.inspect) + ".png"
    screenshot(url, width, height, file)
    env['PATH_INFO'] = "/#{file}"
    @app.call(env)
  end
end

use ScreenshotMiddleware
run Rack::URLMap.new  "/" => Rack::Directory.new('screenshots')

Also see How to capture screenshots with Selenium, Ruby and Firefox.

Screenshot server API

How to use the AWS Command Line Interface

How to use the AWS Command Line Interface Documentation.

AWS CLI doesn’t support Cloudfront at the moment, see list of supported services for details.

Install the Amazon AWS command line client

$ pip install awscli
# or
$ sudo easy_install awscli

Now you need to configure the client:

$ aws configure

How to create an S3 bucket

aws s3 mb s3://xxx

How to list S3 buckets

$ aws s3 ls

How to configure a website

$ aws s3 website help

How to configure the index and error documents of a website

aws s3api put-bucket-website --bucket xxx.com --website-configuration '{
    "IndexDocument": {
        "Suffix": "index.html"
    },
    "ErrorDocument": {
        "Key": "404/index.html"
    }
}'

How to set the website policy

aws s3api put-bucket-policy --bucket www.xxx.com --policy '{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AddPerm",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::www.xxx.com/*"
    }
  ]
}'

How to get the website configuration

 $ aws s3api get-bucket-website --bucket x.com

How to configure redirection for your website

This creates a redirection from x.com to www.×.com:

aws s3api put-bucket-website --bucket x.com --website-configuration '{
    "RedirectAllRequestsTo": {
        "HostName": "www.x.com"
    }
}'

How to set the error document

$ aws s3 website s3://xxx --index-document index.html --error-document 404/index.html

How to upload a website to S3

$ aws s3 sync ~/xxx s3://xxx --delete --cache-control \"max-age=3600\" --acl public-read --exclude *.txt

Note that the command will:

  • delete files from S3 that don’t exist locally.
  • set max-age header to 3600
  • allow anyone to access the files
  • exclude *.txt files

How to use Rails Engines

Create a new engine named x:

$ rails plugin new x --mountable

Load the engine by modifying Gemfile:

gem 'x', path: 'engines/x'

Mount the engine as root in config/routes.rb:

mount X::Engine => ”/”, as: ’root’

Optionally, allow the engine to override views and assets by adding this to config/application.rb:

ApplicationController.prepend_view_path Rails.root.join('engines', 'x', 'app', 'views')

%w(stylesheets javascripts images).each do |dir|
  Rails.configuration.assets.paths.unshift Rails.root.join('engines', 'x', 'app', 'assets', dir)
end

Resources

https://github.com/shageman/the_next_big_thing
http://pivotallabs.com/migrating-from-a-single-rails-app-to-a-suite-of-rails-engines/
http://pivotallabs.com/unbuilt-rails-dependencies-how-to-design-for-loosely-coupled-highly-cohesive-components-within-a-rails-application/
http://pivotallabs.com/experience-report-engine-usage-that-didn-t-work/
http://assets.pivotallabs.com/1439/original/2012_06_12_wrangling_large_rails_codebases.pdf
http://microservices.io/patterns/monolithic.html

Web Microframework Benchmark

Results on an old MacBook Air:

Ruby 2.1.0 + Hobbit + Hat + puma (-t 8 -w 2) - ~100-150 req/s (Hat = Hobbit app template with i18n, asset pipeline, etc)
Ruby 2.1.0 + Hobbit + Hat custom + puma (-t 8 -w 2) - ~1500 req/s (Hat without asset pipeline)
Ruby 2.1.0 + Hobbit + puma (-t 8 -w 2) - ~1600 req/s
Ruby 2.1.0 + rack + puma (-t 8 -w 2) - ~1600 req/s
Golang 1.3.1 + net/http - ~2700 req/s
Elixir 1.0.0 + Phoenix 0.4.1 - ~1300 req/s
Clojure 1.6.0 + ring 1.3.1 - ~5000 req/s
Clojure 1.6.0 + ring 1.3.1 + slim - ~270 req/s

YMMV.

Cuba Hello World

rubycubachristianVersion 2

http://cuba.is/

app.rb:

require "cuba"
#require "rack/protection"

#Cuba.use Rack::Session::Cookie, :secret => "__a_very_long_string__"
#Cuba.use Rack::Protection

Cuba.define do
  # only GET requests
  on get do
    # /
    on root do
      res.write "Home"
    end
  end
end

config.ru:

require "./app"
require 'sprockets'

map '/assets' do
  env = Sprockets::Environment.new
  env.append_path 'assets/javascripts'
  env.append_path 'assets/stylesheets'
  env.append_path 'assets/images'
  run env
end

run Cuba

Gemfile:

source 'https://rubygems.org'
gem 'cuba'
gem 'sprockets'
gem 'puma'

Anonymous hello world benchmark:

Node.js - 27 000 req/s
Ruby cuba - 17 000 req/s
Ruby hobbit - 17 000 req/s
Ruby rack - 25 000 req/s

Yes, Ruby is very slow.

How to prepend a directory to the view path in Rails (3, 4, etc)

Put this in an initializer e.g. config/initializers/parameterization.rb to load different views for different apps that use the same codebase:

APP_NAME = ENV['APP']
ApplicationController.prepend_view_path Rails.root.join('app', 'views', APP_NAME)

prepend_view_path can also be called e.g. once per request before rendering the view to have different view templates for each subdomain, user, cat, etc.

How to Implement Client-Cert Authentication with Apache (Smart Cards, HST-kortti, FINEID)

The goal is to automatically sign in users who have an SSL client-certificate issued by a known certificate authority, e.g. Finnish Väestörekisteri (VRK).

First, we’re going to install and configure Apache 2.2 for client-cert authentication.

Install Apache 2.2

$ brew install -v httpd22.rb 2>&1

Download VRK Certificates

In this example we’re using VRK’s root test certificate. Make sure you pick the right certificate for your purposes, e.g. the one for healthcare professionals.

VRK certificates can be downloaded here:
http://fineid.fi/default.aspx?docid=2293&site=10&id=597

$ cd /usr/local/etc/apache2/2.2/
$ mkdir ssl
$ wget http://vrk.fineid.fi/certs/vrktestc.crt
$ mv vrktestc.crt ssl/
# Convert from DER to PEM format
$ openssl x509 -in ssl/vrktestc.crt -inform DER -outform PEM -out ssl/vrktestc.pem

Download VRK Revocation List

Revocation lists for VRK’s client certificates can be found here:
http://fineid.fi/default.aspx?docid=2330&site=10&id=588#whereistherevocationlist

$ cd /usr/local/etc/apache2/2.2/
$ wget http://proxy.fineid.fi/crl/vrkcqcc.crl
$ mv vrkcqcc.crl ssl/
# Convert CRL file from DER to PEM format
$ openssl crl -in ssl/vrkcqcc.crl -inform der >ssl/vrkcqcc.crl.pem

More about OpenSSL’s CRL command".

Generate Self-Signed Certificate for Apache

If you have a real certificate, skip this part.

$ cd /usr/local/etc/apache2/2.2/
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ssl/server.key -out ssl/server.crt

Configure Apache

<VirtualHost xxx:443>

ProxyPreserveHost On
ProxyPass / http://0.0.0.0:8888/

SSLCertificateFile "/usr/local/etc/apache2/2.2/ssl/server.crt"
SSLCertificateKeyFile "/usr/local/etc/apache2/2.2/ssl/server.key"
SSLCACertificateFile "/usr/local/etc/apache2/2.2/ssl/vrktestc.pem" 
SSLVerifyClient optional # none, optional, require and optional_no_ca
SSLVerifyDepth 2 # Root certificate requires depth >= 2

# Let Rails know we’re using HTTPS
RequestHeader set X_FORWARDED_PROTO 'https'

#
# Forward certificate information to application
# See http://httpd.apache.org/docs/2.2/mod/mod_ssl.html#envvars
#
# Subject's certificate
RequestHeader set SSL_CLIENT_S_DN "%{SSL_CLIENT_S_DN}s"
RequestHeader set SSL_SERVER_S_DN_OU "%{SSL_SERVER_S_DN_OU}s"
# Issuer's certificate
RequestHeader set SSL_CLIENT_I_DN "%{SSL_CLIENT_I_DN}s"
# Verification status: NONE, SUCCESS, GENEROUS or FAILED:reason
RequestHeader set SSL_CLIENT_VERIFY "%{SSL_CLIENT_VERIFY}s"


# Optional settings
#
# Require issuer of certificate to have a specific O and OU:
<Location />
    SSLRequire %{SSL_CLIENT_I_DN_O} eq "Vaestorekisterikeskus TEST" and \
           %{SSL_CLIENT_I_DN_OU} eq "Terveydenhuollon testiammattivarmenteet"
</Location>

# Export SSL and certificate variables
# SSLOptions +ExportCertData +StrictRequire +StdEnvVars

# Revocation list
SSLCARevocationFile "/usr/local/etc/apache2/2.2/ssl/vrkcqcc.crl.pem"

Remember to harden your SSL configuration.

Troubleshooting & FAQ

How can I verify that the SSL-certificate is set up properly?

$ openssl s_client -connect localhost:443 -showcerts

Acceptable client certificate CA names
/C=FI/ST=Finland/O=Vaestorekisterikeskus TEST/OU=Certification Authority Services/OU=Varmennepalvelut/CN=VRK TEST Root CA

Why am I getting SSL handshake failures?

Invalid self-signed certificate?

Removing the smart card or disconnecting the smart card reader will close the browser

This means you need to expire the session when the browser is closed. In a Rails application you need to remove expire_after from session_store.rb.

Browser issues

Browers might perform client-cert authentication in different ways. Many browsers have bugs related to client-cert authentication.

Inserting the smart card after starting the browser might mean the SSL client certificate’s information is not sent to the server.

If you’re having issues, the best solution is usually to restart the browser.

Apache is unable to read the revocation list (CRL)

VRK’s revocation certificate is not in the format required by Apache’s mod_ssl, i.e. the file doesn’t begin with:
-BEGIN X509 CRL——-

The file is binary, so the CRL list is probably in DER format. However, converting the file to PEM format doesn’t work:

$ openssl x509 -in ssl/vrkcqcc.crl -inform DER -outform PEM -out ssl/vrkcqcc.pem

Reading the CRL file seems to work fine:

$ openssl crl -in ssl/vrkcqcc.crl -text -noout -inform DER

The solution for getting revocation to work is to use an LDAP search for revocation. Apache doesn’t support this.

Implementing Client-Cert Authentication in Web Applications

An application that needs to support client-cert authentication should implement the following system and user stories:

  • Enable client-cert authentication (user)

As a user I want to enable client-cert authentication, so that I don’t need to sign in manually.

  • Disable client-cert authentication (user)
  • Sign in user with client certificate (system)

In Ruby on Rails you can access the client-certification information through the headers set by Apache:

request.headers['HTTP_SSL_CLIENT_S_DN'] == user's distinguished name (DN)
request.headers['HTTP_SSL_CLIENT_I_DN'] == issuer's DN
request.headers['HTTP_SSL_CLIENT_VERIFY'] == 'SUCCESS'