Cached .all(:include=>[:xxx]) on associations

When fetching all associations with includes they are not cached, but could be, since they are still the same records(unlike with :select/:conditions etc)

user = User.first
user.comments.length # hits db
user.comments.length # cached

user = User.first
user.commens.all(:include=>:comenter).length  # hits db
user.commens.all(:include=>:comenter).length  # hits db
user.comments.length # hits db

Cached find all with includes
This can save requests when performing repetitive calls to the same record.

user = User.first
user.comments.load_target_with_includes([:commenter, :tags]).length # hits db
user.comments.load_target_with_includes([:commenter, :tags]).length # cached
user.comments.length # cached

Code

# do not load an association twice, when all we need are includes
# all(:include=>xxx) would always reload the target
class ActiveRecord::Associations::AssociationCollection
  def load_target_with_includes(includes)
    raise if @owner.new_record?

    if loaded?
      @target
    else
      @loaded = true
      @target = all(:include => includes)
    end
  end
end

Big updates block database, use slow_update_all

Sometimes big updates that affect millions of rows kill our database (all queries hang/are blocked).
Therefore we built a simple solution:

class ActiveRecord::Base
  def self.slow_update_all(set, where, options={})
    ids_to_update = find_values(:select => :id, :conditions => where)
    ids_to_update.each_slice(10_000) do |slice|
      update_all(set, :id => slice)
      sleep options[:sleep] if options[:sleep]
    end
    ids_to_update.size
  end
end


This needs ActiveRecord find_values extension

Resque tasks, worker, web, GenericJob setup for Rails

Usage

resque-web
QUEUE='*' COUNT=3 rake resque:workers &

GenericJob.publish(Product, :reindex_all, :args=> [{:limit=>1000}]

Setup

#append to Rakefile
require 'resque/tasks'
namespace :resque do
  task :setup => :environment
end

class GenericJob
  @queue = :generic

  def self.perform(klass, method, options={})
    options = options.with_indifferent_access
    if options.has_key?(:args)
      klass.constantize.send(method, *options[:args])
    else
      klass.constantize.send(method)
    end
  end

  def self.publish(klass, method, options={})
    Resque.enqueue(self, klass.to_s, method.to_s, options)
  end
end


If you plan to process instances of AR with this setup, be sure to add serialize_ar / deserialize_ar from solving background processing with a single generic background-job

Kill process in capistrano without pkill

Pkill has issues with capistrano, because the pkill command is always inside a capistrano command, thereby matching and killing itself.

pkill free solution

task :foo do
  kill_processes_matching "MaintenanceDaemon"
end

def kill_processes_matching(name)
  run "ps -ef | grep #{name} | grep -v grep | awk '{print $2}' | xargs kill || echo 'no process with name #{name} found'"
end

Prevent paths in mails (ActionMailer)

It happens once in a while, a path gets into a mail, but with this patch it should happen nevermore.

Looks for paths in non-production and raises if it finds one.


unless Rails.env.production?
  class ActionMailer::Base
    def render_message_with_path_protection(method_name, body)
      body = render_message_without_path_protection(method_name, body)
      if body =~ %r{((href|src)=['"]/.*)}
        raise "Use absolute path in mail urls! #{$1}"
      end
      body
    end

    alias_method_chain :render_message, :path_protection
  end
end