Locking insights: an alternative to redis set nx ex / memcache add

A lock that does not timeout can lead to a standstill and manual cleanup. Simple solution: redis ‘set ex nx’ and memcached ‘add’.

When indefinite locks happen, getting information on why they happen helps to debug the locking mechanism and see if the processes always fail to unlock.

A softer locking approach to receive feedback when locks expire:

Code

def lock
  timeout = 30
  key = 'lock'
  now = Time.now.to_i
  if redis.setnx(key, "#{now}-#{Process.pid}-#{Socket.gethostname}")
    yield
  elsif (old = redis.get(key)) && now > old.to_i + timeout
    logger.error("Releasing expired lock #{old}")
    redis.delete(key) # next process can get the lock 
  end
end

Not 100% safe since the delete could cause multiple processes to get a lock, but depending on your usecase this might be an ok tradeoff.

Using rails 2 views in rails 3.2

Usage
Just keep using your deprecated block helpers, they can be converted once the transition to rails 3 is complete.

<% form_for xxx do |f| %>
  still works
<% end %>

Code

  # make <% helper do %> work
  block_concat = [
    [ActionView::Helpers::FormHelper, :fields_for],
    [ActionView::Helpers::FormBuilder, :fields_for],
    [ActionView::Helpers::FormHelper, :form_for],
    [ActionView::Helpers::FormTagHelper, :form_tag],
    [ActionView::Helpers::TagHelper, :content_tag],
    [ActionView::Helpers::UrlHelper, :link_to],
    [ActionView::Helpers::JavaScriptHelper, :javascript_tag],
    [ActionView::Helpers::CacheHelper, :cache],
  ]

  ERB_EXTENSION = '.erb'.freeze

  block_concat.each do |klass, method|
    klass.class_eval do
      without = "#{method}_without_concat".to_sym
      alias_method without, method
      define_method(method) do |*args, &block|
        result = send(without, *args, &block)
        if block && block.source_location.first.include?(ERB_EXTENSION) # called from a erb template ?
          (@template || self).concat(result) # deprecated erb concat helper
          ""
        else
          result
        end
      end
    end
  end

Upgrading from rails 2 mailer to rails 3 without using new api

Converting all your mailers to the new api in a single commit is pretty daunting and makes them less readable / reusable since now everything has to fit into one big hash at the end of each method. This little patch keeps mailer methods that use the old api working while allowing to use the new api, a smooth transition path!

Usage

class UserMailer < ApplicationMailer::Base
  include DeprecatedMailerAPI

  def welcome
    to "me@example.com"
    from "you@example.com"
    subject "blah"
  end
end

UserMailer.deliver_welcome

Code

  module DeprecatedMailerAPI
    # call build_mail if mail was not used
    def process(*args)
      old = @_message
      super
      unless @mail_was_called
        @_message = old
        build_mail
      end
    end

    def from(xxx=:read)
      mail_cache(:from, xxx)
    end

    def recipients(xxx=:read)
      mail_cache(:to, xxx)
    end

    def cc(xxx=:read)
      mail_cache(:cc, xxx)
    end

    def bcc(xxx=:read)
      mail_cache(:bcc, xxx)
    end

    def reply_to(xxx=:read)
      mail_cache(:reply_to, xxx)
    end

    def content_type(xxx=:read)
      mail_cache(:content_type, xxx)
    end

    def body(xxx=:read)
      mail_cache(:body, xxx)
    end

    def part(options)
      key = case options[:content_type]
      when Mime::TEXT.to_s then :text
      when Mime::HTML.to_s then :html
      else
        raise "Unuspported content_type #{options[:content_type].inspect}"
      end
      @mail_cache ||= {}
      @mail_cache[:parts] ||= {}
      @mail_cache[:parts][key] = options[:body]
    end

    def subject(xxx=:read)
      mail_cache(:subject, xxx)
    end

    def build_mail
      if @mail_cache[:parts]
        mail(@mail_cache.except(:parts, :content_type)) do |format|
          @mail_cache[:parts].each do |f,b|
            format.send(f) { render :text => b }
          end
        end
      else
        mail(@mail_cache)
      end
    end

    def mail_cache(k,v)
      @mail_cache ||= {}
      if v == :read
        @mail_cache[k]
      else
        @mail_cache[k] = v
      end
    end

    def self.included(base)
      class << base
        alias_method :method_missing_without_deprecated, :method_missing

        # convert old create/deliver_foo to foo().deliver
        def method_missing(name, *args, &block)
          if match = /^(create|deliver)_([_a-z]\w*)/.match(name.to_s)
            mail = method_missing_without_deprecated(match[2], *args, &block)
            match[1] == "create" ? mail : mail.deliver
          else
            method_missing_without_deprecated(name, *args, &block)
          end
        end
      end
    end
  end