702 snippets – displaying 1–30

How to take a backup of a Postgres database

This is one way of doing it:

pg_dump -s yamaha_development > structure.sql
pg_dump --column-inserts --data-only yamaha_development  > data.sql

You can then restore it to a different database if needed:

psql -c 'drop database yamaha_test;'
psql -c 'create database yamaha_test;'

psql yamaha_test < structure.sql
psql yamaha_test < data.sql

All without foreign key constraint, etc, errors.

How to fix "command not found:  grep" on OSX

This issue will bite everyone who touches a keyboard. This fixes, e.g., the following issues:

  • command not found:  grep (Shell)
  • undefined local variable or method (Ruby)
  • <your key-binding related issue here>

First, do this:

mkdir -p ~/Library/KeyBindings/
vim ~/Library/KeyBindings/DefaultKeyBinding.dict

Then paste this:

{
  "~ " = ("insertText:", " ");
}

Save the file.

Quit and reopen applications to apply the new key bindings in DefaultKeyBinding.dict.

References

http://osxnotes.net/keybindings.html
http://linuxtoosx.blogspot.fi/2010/10/disabling-option-space-and-altgr-space.html

MacVim

For MacVim i have yet to find a solution for insert mode, so just use vim in the terminal…

Sanitizing XML with Go's UnmarshalXML

Use a custom type to avoid this error when unmarshalling XML with Go:

strconv.ParseInt: parsing "86148865.00": invalid syntax

Example:

import (
    "encoding/xml"
    "strconv"
)

type DogHouse struct {
    //Count int `xml:"dog_house>count"` << this code will fail with "invalid syntax"
    Count sanitizedInt `xml:"dog_house>count"`
}

type sanitizedInt int

func (si *sanitizedInt) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    var value string
    // Read tag content into value
    d.DecodeElement(&value, &start)
    // Remove "crap" and convert to int64
    i, err := strconv.ParseInt(strings.Replace(value, "crap", "", -1), 0, 64)
    if err != nil {
        return err
    }
    // Cast int64 to sanitizedInt
    *si = (sanitizedInt)(i)
    return nil
}

How to parse an XML document in Go

This example shows how to fetch and parse an XML feed with Go. It also shows you how to fix “xml: encoding \”windows-1252\" declared but Decoder.CharsetReader is nil" errors.

Save this in main_test.go:

package main

import (
    "bytes"
    "code.google.com/p/go-charset/charset"
    _ "code.google.com/p/go-charset/data"
    "encoding/xml"
    "io/ioutil"
    "log"
    "net/http"
    "testing"
)

type RssFeed struct {
    XMLName xml.Name  `xml:"rss"`
    Items   []RssItem `xml:"channel>item"`
}

type RssItem struct {
    XMLName     xml.Name `xml:"item"`
    Title       string   `xml:"title"`
    Link        string   `xml:"link"`
    Description string   `xml:"description"`
    //NestedTag    string      `xml:">nested>tags>"`
}

func fetchURL(url string) []byte {
    resp, err := http.Get(url)
    if err != nil {
        log.Fatalf("unable to GET '%s': %s", url, err)
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatalf("unable to read body '%s': %s", url, err)
    }
    return body
}

func parseXML(xmlDoc []byte, target interface{}) {
    reader := bytes.NewReader(xmlDoc)
    decoder := xml.NewDecoder(reader)
    // Fixes "xml: encoding \"windows-1252\" declared but Decoder.CharsetReader is nil"
    decoder.CharsetReader = charset.NewReader
    if err := decoder.Decode(target); err != nil {
        log.Fatalf("unable to parse XML '%s':\n%s", err, xmlDoc)
    }
}

func TestParseReport(t *testing.T) {
    var rssFeed = &RssFeed{}
    xmlDoc := fetchURL("https://news.ycombinator.com/rss")
    parseXML(xmlDoc, &rssFeed)
    for _, item := range rssFeed.Items {
        log.Printf("%s: %s", item.Title, item.Link)
    }
}

Run it with go test.

How to use Ansible Vault to store SSL certificates

Playbook

In playbook.yml, list the file where we’ll put the SSL certificates:

---
- hosts: servers
  roles:
...
  vars_files:
    - vault/certs/{{ domain }}.yml # Private SSL certificates

Ansible Vault

Next, create an encrypted Ansible vault (one per domain):

$ ansible-vault create vault/certs/xxx.com.yml

Put the following in the vault/certs/xxx.com.yml:

certificates:
  - name: "xxx.com.pem"
    content: |
      -----BEGIN CERTIFICATE-----
  - name: "login.xxx.com.pem"
    content: |
      -----BEGIN CERTIFICATE-----

The last thing we need to do is copy the certificates from the Ansible Vault to the server (roles/ssl_certs/tasks/main.yml):

- name: Copy private SSL certificates from Ansible Vault
  tags: ssl_certs
  copy:
    content: "{{ item.content }}"
    dest: "/etc/ssl/private/{{ item.name }}"
    owner: root
    group: root
    mode: "u=rw,g=r,o="
  sudo: yes
  with_items:
    # Certificates are stored encrypted in vault/certs/{{ domain }}.yml
    - "{{ certificates }}"

Usage

Use ask-vault-pass to specify the Ansible Vault’s password at deployment:

$ ansible-playbook -i inventory/hosts --limit server1 --tags "ssl_certs" playbook.yml -v --ask-vault-pass

Postgres substring search with trigrams

What we have is:

  • a table “companies” and a column “name”
  • Postgres and the pgtrgm extension
  • a company named “Berkshéiße”

Let’s enable the trigram extension:

-- $ psql app_schema -U superuser
CREATE EXTENSION pg_trgm;

Now we can search for trigrams, including “shèiß”:

-- $ psql app_schema -U user
SELECT * FROM companies WHERE name ~* 'shèiß';

The query returns nothing, so let’s do this instead:

  • install the unaccent extension
  • create an “immutable unaccent” function
  • apply “unaccent” and “lower” to the query
  • apply “unaccent” and “lower” to the index

-- $ psql app_schema -U superuser
CREATE EXTENSION unaccent;

-- $ psql app_schema -U user
--DROP INDEX companies_name_search_idx;
CREATE OR REPLACE FUNCTION f_unaccent(text)
  RETURNS text AS
$func$
SELECT unaccent('unaccent', $1)
$func$  LANGUAGE sql IMMUTABLE SET search_path = public, pg_temp;
CREATE INDEX companies_name_search_idx ON companies USING gin(f_unaccent(name) gin_trgm_ops);

Finally, the query returns what we’re looking for:

-- Plain SQL
SELECT * FROM companies WHERE lower(f_unaccent(name)) LIKE ('%' || lower(f_unaccent('shèiß')) || '%');
-- With pg_trgrm syntax
SELECT * FROM companies WHERE lower(f_unaccent(name) ~* lower(f_unaccent('shéiße'));
-- Look, even this works
SELECT * FROM companies WHERE lower(f_unaccent(name) ~* lower(f_unaccent('shEiSe'));

If Postgres still doesn’t use the index we created, it’s probably because it’s faster to scan the table than using the index.

Notes

  • The WHERE-condition must match the index definition:

-- yes
lower(unaccent_text(name) ~* lower(unaccent_text('shéiße'))
-- no
name ~* lower(unaccent_text('shéiße'))
-- no
name ~* unaccent_text('shéiße')

References

Diffs with Postgres' lead and window functions

Our requirement

Output the previous quarter’s value for each company:

ID | NAME              | QUARTER     |  VALUE    | PREVIOUS_VALUE
1  | CORP              | 2015-03-31  |  44317.0038755.00
1  | CORP              | 2014-12-31  |  38755.00 | 33617.00
1  | CORP              | 2014-09-30  |  33617.00 | 32406.00
1  | CORP              | 2014-06-30  |  32406.0029909.00
1  | CORP              | 2014-03-31  |  29909.000

The solution

To calculate the change we use the window and lead functions:

WITH diffs AS (
  select
    *,
    coalesce(lead(value) over (partition by company_id order by quarter desc), 0) as previous_value
  from
    history
)
select * from diffs;

Note that the window is partitioned by company_id, and ordered by quarter.

HTTP parameter named "action" is reserved in Rails

The “action” HTTP parameter is reserved in Rails, so you don’t see it in the logs or in the params hash.

So how do you read the “action” parameter?

For GET:

Rack::Utils.parse_nested_query(env['QUERY_STRING'])['action']

For POST:

post_body = request.body.read
request.body.rewind if request.body.respond_to?(:rewind)
post_params = CGI.parse(post_body)
post_params['action'].try(:first)

Calculating quarters in Ruby

rubyquarterschristianVersion 2

class Date
  def quarter
    case self.month
    when 1,2,3 then 1
    when 4,5,6 then 2
    when 7,8,9 then 3
    when 10,11,12 then 4
    end
  end

  def start_of_previous_quarter
    end_of_previous_quarter.start_of_quarter
  end

  def end_of_previous_quarter
    start_of_quarter.prev_day
  end

  def start_of_quarter
    first_month = [10, 7, 4, 1].detect { |m| m <= self.month }
    Date.new(self.year, first_month, 1)
  end

  def end_of_quarter
    last_month = [3, 6, 9, 12].detect { |m| m >= self.month }
    Date.new(self.year, last_month, 1).next_month.prev_day
  end
end

Example:

now = Date.parse('2015-06-01')
now.start_of_quarter => 2015-04-01
now.end_of_quarter => 2015-06-30
now.start_of_previous_quarter => 2015-01-01
now.end_of_previous_quarter => 2015-03-31

Selecting the "top n" or "n latest" rows by group in Postgres

Try this SQL query, to select only the latest transactions, or the top 10 transactions, per user, for a group of users:

  • partition the transactions by user_id
  • order the transactions in each partition by the column created_at
  • keep order of the rank, per partition (first row is 1, second is 2)
  • select only first row, i.e. the latest

WITH latest_customer_transactions AS (
  SELECT
    *, rank() OVER (PARTITION BY user_id ORDER BY created_at desc) AS rank
  FROM
    transactions
)
SELECT
  id, customer_id
FROM
  latest_customer_transactions
WHERE
  rank = 1;

Keep your fingers crossed that it works. For alternatives, see Shtack Overflow.

Recursive queries with connectby in Postgres

Create an organization hierarchy and display the organization from the top:

CREATE EXTENSION "tablefunc";
CREATE TABLE organizations(id text, parent_id text, pos int);

INSERT INTO organizations VALUES('row1',NULL, 0);
INSERT INTO organizations VALUES('row2','row1', 0);
INSERT INTO organizations VALUES('row3','row1', 0);
INSERT INTO organizations VALUES('row4','row2', 1);
INSERT INTO organizations VALUES('row5','row2', 0);
INSERT INTO organizations VALUES('row6','row4', 0);
INSERT INTO organizations VALUES('row7','row3', 0);
INSERT INTO organizations VALUES('row8','row6', 0);
INSERT INTO organizations VALUES('row9','row5', 0);

-- Fetch self and descendants for row1
SELECT * FROM connectby('organizations', 'id', 'parent_id', 'row1', 0, '~') AS t(id text, parent_id text, level int, branch text);

Output:

  id  | parent_id | level |          branch
------+-----------+-------+--------------------------
 row1 |           |     0 | row1
 row2 | row1      |     1 | row1~row2
 row4 | row2      |     2 | row1~row2~row4
 row6 | row4      |     3 | row1~row2~row4~row6
 row8 | row6      |     4 | row1~row2~row4~row6~row8
 row5 | row2      |     2 | row1~row2~row5
 row9 | row5      |     3 | row1~row2~row5~row9
 row3 | row1      |     1 | row1~row3
 row7 | row3      |     2 | row1~row3~row7

Usually it’s better to use recursive CTE queries:

WITH RECURSIVE organization_tree AS (
  SELECT 
  	id, parent_id 
  FROM
  	organizations
  WHERE
  	id = 'row1'
UNION ALL
  SELECT 
  	organizations.id, organizations.parent_id 
  FROM 
  	organizations, organization_tree
  WHERE
  	organizations.parent_id = organization_tree.id
) select * from organization_tree;

Dot notation for Ruby configuration hash

Convert a normal hash into a hash that allows e.g. this:

CONFIG['session.ttl'] => 60
CONFIG['session.tt'] => KeyError

Code:

def to_namespace_hash(object, prefix = nil)
  if object.is_a? Hash
    object.map do |key, value|
      if prefix
        to_namespace_hash value, "#{prefix}.#{key}"
      else
        to_namespace_hash value, "#{key}"
      end
    end.reduce(&:merge)
  else
    { prefix => object }
  end
end

CONFIG = ... # Load hash from YAML/JSON/database/etc
CONFIG = to_namespace_hash(CONFIG).freeze

def CONFIG.[](key)
  fetch(key)
rescue KeyError => e
  possible_keys =keys.map { |x| x if x.match /.*?#{key}.*?/i }.delete_if(&:blank?).join("\n")
  fail KeyError, "Key '#{key}' not found. Did you mean one of:\n#{possible_keys}"
end

Example of how to use Ruby's RestClient with POST parameters, basic authorization, HTTP headers, and JSON

Example of how to use Ruby’s RestClient with POST parameters, basic authorization, HTTP headers, and JSON:

    response = RestClient::Request.new({
      method: :post,
      url: 'https://xyz,
      user: 'someone',
      password: 'mybirthday',
      payload: { post_this: 'some value', post_that: 'other value' },
      headers: { :accept => :json, content_type: :json }
    }).execute do |response, request, result|
      case response.code
      when 400
        [ :error, parse_json(response.to_str) ]
      when 200
        [ :success, parse_json(response.to_str) ]
      else
        fail "Invalid response #{response.to_str} received."
      end
    end

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

This gulp.js template will compile JSX to JS and ES6 to ES5 using Babel.

Features:

  • browserify (dependency management)
  • sourcemaps
  • ES6 to ES5
  • React.js (with JSX to JS compilation)
  • Error notifications

var gulp       = require('gulp');
var browserify = require('browserify');
var babelify   = require('babelify');
var notify     = require("gulp-notify");
var source     = require('vinyl-source-stream');

var paths = {
  	js : { src : [ 'app/**/*.js', 'app/**/*.jsx' ], dest : 'public/js/' }
};

gulp.task('default', ['build']);

//
// Watch
//
gulp.task('watch', function () {
  	gulp.watch(paths.js.src, ['compile']);
});

//
// Compile
//
gulp.task('build', [ 'compile' ]);

gulp.task('compile', function () {
  browserify({
    entries: [ './app/index.js' ],
    extensions: [ '.js', '.jsx' ],
    debug: true // Add sourcemaps
  })
  .transform(babelify) // JSX and ES6 => JS
  .bundle() // Browserify bundles required files
    .on('error', console.error.bind(console))
    .on("error", notify.onError({
      message: 'Error: <%= error.message %>',
      sound: 'Sosumi'
    }))
  .pipe(source('app.js')) // Desired filename of bundled files
  .pipe(gulp.dest(paths.js.dest));
});

package.json:

{
  "name": "snippets",
  "version": "0.0.1",
  "author": "christian@aktagon.com",
  "main": "app/index.js",
  "dependencies": {},
  "devDependencies": {
    "babelify": "^6.1.2",
    "browserify": "^10.2.1",
    "gulp": "^3.6.2",
    "gulp-notify": "^2.2.0",
    "material-ui": "^0.8.0",
    "mocha": "*",
    "react": "^0.13.3",
    "react-tap-event-plugin": "^0.1.7",
    "vinyl-source-stream": "^1.1.0"
  },
  "scripts": {
    "main": "gulp",
    "test": "gulp build && mocha"
  }
}

First save the files to your project directory then install the dependencies:

$ brew install npm
$ npm install

Start coding (app/index.js):

React = require('react')
_ = require('lodash')

# LOL

ALERT: ActiveRecord#order and ActiveRecord#default_scope are dangerous

Consider this model:

class Transaction
  default_scope -> { order("created_at ASC") }

  def self.latest
    order('created_at DESC')
  end

   def self.approved_for_customer(customer)
    where('approved is not null and customer_id = ?', customer).order('approved_at DESC')
  end
end

And this controller:

class TransactionsWorker
   def perform(...)
      transaction = Transaction.for_customer.latest # WTF!
   end
end

The order will be “ORDER BY created_at ASC, approved_at DESC, created_at DESC”, not “created_at DESC”.

Solutions:

  • Don’t return an instead of ActiveRecord::Relation if you use order, return an array (.all, .first, etc)
  • Don’t use order and default_scope at all in models, ordering usually belongs in the business logic layer.
  • Use reorder as the last method call if chaining ActiveRecord::Relation in the business layer, e.g. Transaction.where(‘…’).reorder(:approved_at)

A safer model:

class Transaction
   def self.latest
    order('created_at DESC').all
  end

   def self.approved_for_customer(customer)
    where('approved is not null and customer_id = ?', customer).order('approved_at DESC').all
  end
end

Rails Form Pattern With Delegated Validations

Where do you put validations when you implement the Form pattern in Rails:

  • all in the model?
  • all in the form?
  • both in the model and form? This is a bad idea because you have to keep both model and form validations in sync.

This example defines all validations in the model, i.e. validation errors and form helpers work without additional code.

#
# Form object wrapper for all models created when creating a new event.
#
class CreateEventForm
  # Rails 4+
  include ActiveModel::Model
  # Rails 3
  #extend ActiveModel::Naming
  #include ActiveModel::Conversion
  #include ActiveModel::Validations

  attr_accessor :event, :calendar
  delegate :id, :persisted?, to: :event

  def initialize(attributes = {})
    @event = Event.new
    @calendar = Calendar.new
    attributes.each do |key, value|
      public_send("#{key}=", value)
    end
    setup_associations
  end

  #
  # Save the event and all related models.
  #
  def save
    if valid?
      ActiveRecord::Base.transaction do
        event.save!
        calendar.save!
      end
   else
     false
    end
  end

  def self.model_name
    Event.model_name
  end

  def event_attributes=(attributes)
    event.attributes = attributes
  end

  def calendar_attributes=(attributes)
    calendar.attributes = attributes
  end

  def valid?
    valid = super
    # errors.base(:validation_error) unless valid_children?
    valid && valid_children?
  end

  private

  def valid_children?
    [ event, calendar ].map(&:valid?).all? { |valid| valid == true }
  end

  def setup_associations
    calendar.event = event
  end
end

View example:

<%= simple_form_for @form do |f| # @form = CreateEventForm.new %>
...
f.simple_fields_for :calendar do |calendar|
end
f.simple_fields_for :event do |event|
end
...
<% end %>

Note, instead of form objects you can always use accepts_nested_attributes_for. However, why should the view define what the model should look like? accepts_nested_attributes_for is a similar hack as the deprecated attr_accessible.

Tax Optimizer For Suomi/Finland, not Sweden

finlandtaxchristianVersion 9

Usage:

$ ruby vero.rb --gross-income-step 1000 --gross-income 20000-35000 --capital-income-step 1000 --capital-income 0-20000
Usage: ruby vero.rb [options]
        --entrepreneur [ARG]         Y/N (FöPL/YEL is assumed to be equal to gross income)
        --gross-income [ARG]         Gross income range, e.g. 25000-30000
        --gross-income-step [ARG]    Gross income step, e.g. 1000
        --capital-income [ARG]       Capital income range, e.g. 0-20000
        --capital-income-step [ARG]  Capital income step, e.g. 1000
    -h, --help                       Display this help

Save this file to vero.rb (edit it if you’re not me):

require 'mechanize'
require 'pry'
require 'csv'
require 'optparse'

options = {}
OptionParser.new do |opts|
  opts.banner = "Usage: ruby vero.rb [options]"
  opts.on('--entrepreneur [ARG]', "Y/N (FöPL/YEL is assumed to be equal to gross income)") do |v|
    options[:entrepreneur] = v.strip.downcase == 'y'
  end
  opts.on('--gross-income [ARG]', "Gross income range, e.g. 25000-30000") do |v|
    parts = v.split('-')
    options[:gross_income_range] = (parts[0].to_f..parts[1].to_f)
  end
  opts.on('--gross-income-step [ARG]', "Gross income step, e.g. 1000") do |v|
    options[:gross_income_step] = v.to_f
  end
  opts.on('--capital-income [ARG]', "Capital income range, e.g. 25000-30000") do |v|
    parts = v.split('-')
    options[:capital_income_range] = (parts[0].to_f..parts[1].to_f)
  end
  opts.on('--capital-income-step [ARG]', "Capital income step, e.g. 1000") do |v|
    options[:capital_income_step] = v.to_f
  end
  opts.on('-h', '--help', 'Display this help') do
    puts opts
    exit
  end
  puts opts
end.parse!

puts options

def tax_for(options = {})
  gross_income = options.fetch(:gross_income)
  capital_income = options.fetch(:capital_income, 0)
  entrepreneur = options.fetch(:entrepreneur, false)
  mech = Mechanize.new
  page = mech.get('http://prosentti.vero.fi/VPL2015/Sivut/Aloitus.aspx')
  # Page 1
  page_1 = mech.click(page.link_with(text: /Veroprosenttilaskuri 2015/))
  form_1 = page_1.form_with(action: 'Henkilotiedot.aspx')
  form_1.field_with(name: "ddKotikunta").option_with(text: "Espoo").click
  form_1.field_with(name: "ddSeurakunta").option_with(text: "ei kirkollisverovelvollinen").click
  form_1.field_with(name: "ddSyntymavuosi").value = "1976"
  form_1.field_with(name: "ddPuoliso").value = "Kyllä"
  form_1.field_with(name: "tbYhteishuoltoLapset").value = "2"
  form_1.field_with(name: "tbLapset").value = "2"
  # Page 2
  page_2 = form_1.submit(form_1.button_with(name: "btnJatka"))
  fail page_2.body unless page_2.uri.to_s == 'http://prosentti.vero.fi/VPL2015/Sivut/TulotJaVahennykset.aspx'
  form_2 = page_2.form_with(action: 'TulotJaVahennykset.aspx') do |f|
    # Bruttoinkomster
    f.field_with(name: "tbPaatoimiArvioTulo").value = gross_income.to_s.gsub('.', ',')
    # Dividender
    # f.field_with(name: "tbOsingotJulkNotPotArvioTulo").value = capital_income.to_s.gsub('.', ',')
    # Övrig kapitalinkomster
    f.field_with(name: "tbVuokratulotJaTappiotVuoratulotOsakehuoneistoista").value = capital_income.to_s.gsub('.', ',')
    # Entrepreneurs need to include "FöPL- eller LFöPL-arbetsinkomst (YEL)":
    if entrepreneur
      f.field_with(name: "tbShJaPaivarahamaksunTiedotYelTyotulo").value = gross_income.to_s.gsub('.', ',')
      f.field_with(name: "tbShJaPaivarahamaksunTiedotTyotuloillaKorvattavaYpalkka").value = gross_income.to_s.gsub('.', ',')
    end
  end
  # Page 3
  page_3 = form_2.submit(form_2.button_with(name: "btnJatka"))
  base = Float(page_3.at('#riviPerusprosentti').at('#neljassoluPerusprosentti').text.gsub(',', '.'))
  additional = Float(page_3.at('#riviLisaprosentti').at('#neljassoluLisaprosentti').text.gsub(',', '.'))
  [ base, additional ]
#rescue => e
  #puts e
  #binding.pry
end

CSV.open("taxes.csv", "w") do |csv|
  cols = [ "bruttoinkomster", "kapitalinkomster", "grundprocent", "tilläggsprocent" ]
  csv << cols
  options.fetch(:gross_income_range).step(options.fetch(:gross_income_step)).each do |gross_income|
    options.fetch(:capital_income_range).step(options.fetch(:capital_income_step)).each do |capital_income|
      taxes = tax_for(gross_income: gross_income, capital_income: capital_income)
      row = [ gross_income, capital_income, taxes ].flatten
      puts Hash[cols.zip row]
      csv << row
    end
  end
end

Open taxes.csv and think about what you’re doing.

Finnish SSID/HETU generator in Ruby

module Ssid
  module Fake
    #
    # Generates fake Finnish SSIDs (HETU).
    #
    # Original Python version:
    # https://gist.github.com/puumuki/11172310
    #
    class Finnish
      CHECK_KEYS = "0123456789ABCDEFHJKLMNPRSTUVWXY"
      CENTURIES = { '18' => '+',
                    '19' => '-',
                    '20' => 'A' }
      def self.generate(start_year=1800, end_year=2014)
        year = start_year + rand(start_year - end_year)
        month = 1 + rand(12)
        day = 1 + rand(days_in_month(year, month))
        century_sep = CENTURIES[year.to_s[0..1]]
        order_num = 2 + rand(889)
        check_number = "%02d%02d%s%03d" % [ day, month, year.to_s[0..1], order_num ]
        check_number_index = check_number.to_i % 31
        key = CHECK_KEYS[check_number_index]
        "%02d%02d%s%s%03d%s" % [ day, month, year.to_s[0..1], century_sep, order_num, key ]
      end

      private

      def self.days_in_month(year, month)
        (Date.new(year, 12, 31) << (12-month)).day
      end
    end
  end
end

How to get notified when a Cron job fails

To get notified when a cron script fails you can tell cron to send any errors to you by email.

It already does this by default, but the emails are sent to the Unix account’s mailbox:

cat /var/spool/mail/<username> | less

To use a different email address use the MAILTO setting as described here:

MAILTO=email@example.com
0 */2 * * * /bin/backup.sh > /dev/null

Cron will now send an email whenever the script (/bin/backup.sh) prints something to stderr.

If the script is printing to stderr without it failing try Cronic:

0 */2 * * * cronic /bin/backup.sh

You can of course also try to fix the script by not printing to STDERR unless it’s really an error.

Check the mail log to see if it’s working:

sudo tail -f /var/log/mail.log

If you’re email server is rejecting emails sent by cron you might need to set the MAILFROM variable. However, only some cron version supports MAILFROM.

How to mark deprecated methods in Ruby

This code can be used to mark deprecated methods in Ruby:

module Kernel
  def mark_deprecated(new_method)
    warn "DEPRECATED: the #{__method__} called from #{caller.first} has been deprecated and replaced by #{new_method}!!!"
  end
end

Example:

class Horse
  def self.shit
    mark_deprecated("Bull.shit")
  end
end