Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

About this user

Remco van 't Veer http://blog.remvee.net

« Newer Snippets
Older Snippets »
Showing 1-10 of 16 total  RSS 

Object.memoize

The following is inspired by the article "Caching and Memoization" by James Edward Gray II.

# = Memoization for objects
# 
# This module will extend +Object+ with the +memoize+ method.  This method
# provides memoization for instance methods, which means return values will
# be cached and subsequent calls will return the cached value of the first
# call.
# 
# Caching is done based on instance, method and arguments to the method.  All
# data is kept in a single +Hash+ store which allows flushing all cached
# results at ones using the +flush_memos+ method.
# 
# == Example
#   class Person < Struct.new(:email)
#     def finger
#       `finger #{email}`
#     end
#     memoize :finger
#   end
#   
#   bob = Person.new('bob@test.net')
#   bob.finger                        # finger command executed
#   bob.finger                        # cached value returned
#   Memoizable.flush_memos
#   bob.finger                        # finger command executed
#
# == See also
# http://blog.grayproductions.net/articles/caching_and_memoization
#
# == Author
# R.W. van 't Veer, 2008-04-01, Amsterdam
module Memoizable
  # Store for cached values.
  CACHE = Hash.new{|h,k| h[k] = Hash.new{|h,k| h[k] = {}}} # 3 level hash; CACHE[:foo][:bar][:yelp]
  
  # Memoize the given method(s).
  def memoize(*names)
    names.each do |name|
      unmemoized = "__unmemoized_#{name}"
      
      class_eval %Q{
        alias   :#{unmemoized} :#{name}
        private :#{unmemoized}
        def #{name}(*args)
          cache = CACHE[self][#{name.inspect}]
          cache.has_key?(args) ? cache[args] : (cache[args] = send(:#{unmemoized}, *args))
        end
      }
    end
  end
  
  # Flush cached return values.
  def flush_memos
    CACHE.clear
  end
  module_function :flush_memos
end

class Object # :nodoc:
  extend Memoizable
end

if $0 == __FILE__
  require 'test/unit'
  
  class MemoizeTest < Test::Unit::TestCase # :nodoc:
    def setup
      @obj = TestObject.new
    end
    
    def teardown
      Memoizable.flush_memos
    end
    
    def test_memoize_value_should_stick_until_cache_flushed
      @obj.value = 'a'
      assert_equal 'a', @obj.value
      
      @obj.value = 'b'
      assert_equal 'a', @obj.value
      
      Memoizable.flush_memos
      assert_equal 'b', @obj.value
    end
    
    def test_flush_should_clear_all_cached_objects
      @obj.value = 'yelp'
      @obj.value
      
      assert_not_equal 0, Memoizable::CACHE.size
      Memoizable.flush_memos
      assert_equal 0, Memoizable::CACHE.size
    end
    
    def test_memoize_should_keep_separate_cache_per_instance
      other = TestObject.new
      @obj.value, other.value = 'a', 'b'
      
      assert_equal 'a', @obj.value
      assert_equal 'b', other.value
    end
    
    def test_memoize_should_keep_separate_cache_per_method
      @obj.value, @obj.other = 'a', 'b'
      
      assert_equal 'a', @obj.value
      assert_equal 'b', @obj.other
    end
    
    def test_memoize_should_include_arguments_in_cache_key
      @obj.with_arguments = 'a'
      assert_equal 'a', @obj.with_arguments(:this)
      
      @obj.with_arguments = 'b'
      assert_equal 'a', @obj.with_arguments(:this)
      assert_equal 'b', @obj.with_arguments(:that)
    end
    
    class TestObject # :nodoc:
      attr_accessor :value, :other
      memoize :value, :other
      
      attr_writer :with_arguments
      def with_arguments(*args); @with_arguments; end
      memoize :with_arguments
      
      attr_writer :question
      def question?; @question; end
      memoize :question?
      
      attr_writer :exclamation
      def exclamation!; @exclamation; end
      memoize :exclamation!
    end
  end
end

Simple cacher module for Rails

Illustration of a simple cacher module for Rails.

# lib/cacher.rb
module Cacher
  STORE = {}
  
  def cache(*args)
    STORE[args.inspect] ||= yield
  end

  def flush!
    STORE.clear
  end
  module_function :flush!
end

# app/models/person.rb
class Person < AR::Base
  include Cacher

  def finger
    cache(:finger, email) do
      `finger #{email}`
    end
  end
end

# app/controller/application.rb
class ApplicationController < AC::Base
  after_filter { Cacher.flush! }
end

Persistent Rails cookie session

Session cookies, the Rails-2 kind, are transient because that's safer. In some applications safety isn't important. The following makes the session cookies persist for a year.

class ApplicationController < ActionController::Base
  before_filter :update_session_expiration_date

private
  def update_session_expiration_date
    unless ActionController::Base.session_options[:session_expires]
      ActionController::Base.session_options[:session_expires] = 1.year.from_now
    end
  end
end

load all fixtures

In some test cases I need all my fixtures to be loaded. To make this easier, add the following to test/test_helper.rb:

class Test::Unit::TestCase
  def self.all_fixtures
    Dir[File.dirname(__FILE__) + "/fixtures/*.yml"].each do |f|
      fixtures File.basename(f, '.yml')
    end
  end

  ..
end


and in your tests use it as follows:

class FooTest < Test::Unit::TestCase
  all_fixtures

  ..
end


Happy testing!

response caching in camping

A basic implementation of response caching in camping.

Camping.goes :MyCampingApp

module MyCampingApp
  module Controller
    class View < R '/'
      def get
        cache('root') do
          'Expensive operation!'
        end
      end
    end
  end
  
  def flush(id)
    f = File.dirname(__FILE__) + "/cache/#{id}"
    File.delete(f) if File.exists?(f)
  end
  
  def cache(id, timeout = 1.hour)
    f = File.dirname(__FILE__) + "/cache/#{id}"
    
    if File.exists?(f) && (Time.now - File.stat(f).mtime) < timeout
      File.read(f)
    else
      r = yield
      open(f, 'w'){|wr| wr.write(r)}
      r
    end
  end

  def self.create
    cache_dir = File.dirname(__FILE__) + "/cache"
    Dir.mkdir(cache_dir) unless File.directory?(cache_dir)
  end
end

avoid web caching

// Set to expire far in the past.
response.setHeader("Expires", "Sat, 6 May 1995 12:00:00 GMT");
// Set standard HTTP/1.1 no-cache headers.
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
// Set IE extended HTTP/1.1 no-cache headers (use addHeader).
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
// Set standard HTTP/1.0 no-cache header.
response.setHeader("Pragma", "no-cache");

Disable protected attributes in ActiveRecordHelper forms

To make the form method in ActiveRecordHelper disable protected attribute, place the following in your application_help.rb:

def attr_protected?(record, column)
  o = instance_variable_get("@#{record}")
  o && o.class.protected_attributes &&
      o.class.protected_attributes.include?(column.name.to_sym)
end

def default_input_block
  Proc.new { |record, column|
    options = attr_protected?(record, column) ? {:disabled => true} : {}
    <<-"end_html" 
      <p>
        <label for="#{record}_#{column.name}">#{column.human_name}</label>
        <br />
        #{input(record, column.name, options)}
      </p>
    end_html
  }
end


And mark some attributes protected, for example:

class Item < ActiveRecord::Base
  attr_protected :created_at
end


The created_at attribute will be rendered but won't be editable. This will work for "dynamic scaffolds" too!

time based cache

A simple time based cache build around a map store.

import java.util.Map;
import java.util.WeakHashMap;

/**
 * Simple time-based cache.
 */
public class SimpleCache {
	private long maxAge;
	private Map store;

	/**
	 * Instanciate a cache with max age of 1 hour and a WeakHashMap as store.
	 * @see java.util.WeakHashMap
	 */
	public SimpleCache() {
		this.maxAge = 1000 * 60 * 60;
		this.store = new WeakHashMap();
	}
	
	/**
	 * @param maxAge maximum age of an entry in milliseconds
	 * @param store map to hold entries
	 */
	public SimpleCache(long maxAge, Map store) {
		this.maxAge = maxAge;
		this.store = store;
	}
	
	/**
	 * Cache an object.
	 * @param key unique identifier to retrieve object
	 * @param value object to cache
	 */
	public void put(Object key, Object value) {
		store.put(key, new Item(value));
	}
	
	/**
	 * Fetch an object.
	 * @param key unique identifier to retrieve object
	 * @return an object or null in case it isn't stored or it expired
	 */
	public Object get(Object key) {
		Item item = getItem(key);
		return item == null ? null : item.payload;
	}
	
	/**
	 * Fetch an object or store and return output of callback.
	 * @param key unique identifier to retrieve object
	 * @param block code executed when object not in cache
	 * @return an object
	 */
	public synchronized Object get(Object key, Callback block) {
		Item item = getItem(key);
		if (item == null) {
			Object value = block.execute();
			item = new Item(value);
			store.put(key, item);
		}
		return item.payload;
	}
	
	/**
	 * Remove an object from cache.
	 * @param key unique identifier to retrieve object
	 */
	public void remove(Object key) {
		store.remove(key);
	}
	
	/**
	 * Get an item, if it expired remove it from cache and return null.
	 * @param key unique identifier to retrieve object
	 * @return an item or null
	 */
	private Item getItem(Object key) {
		Item item = (Item) store.get(key);
		if (item == null) {
			return null;
		}
		if (System.currentTimeMillis() - item.birth > maxAge) {
			store.remove(key);
			return null;
		}
		return item;		
	}

	/**
	 * Value container.
	 */
	private static class Item {
		long birth;
		Object payload;
		Item(Object payload) {
			this.birth = System.currentTimeMillis();
			this.payload = payload;
		}
	}
	
	/**
	 * A visitor interface.
	 */
	public static interface Callback {
		Object execute();
	}
}


And a couple of junit tests:

import java.util.HashMap;

import junit.framework.TestCase;

public class SimpleCacheTest extends TestCase {
	public void testPutGet () {
		SimpleCache c = new SimpleCache(Long.MAX_VALUE, new HashMap());
		c.put("key1", "value1");
		assertEquals("value1", c.get("key1"));
		c.put("key1", "value1.0");
		assertEquals("value1.0", c.get("key1"));
		c.put("key2", "value2");
		assertEquals("value2", c.get("key2"));
		assertEquals("value1.0", c.get("key1"));
	}
	
	public void testMaxAge () throws InterruptedException {
		SimpleCache c = new SimpleCache(1000, new HashMap());
		c.put("key1", "value1");
		assertEquals("value1", c.get("key1"));
		Thread.sleep(1500);
		assertNull(c.get("key1"));
		
		c.put("key2", "value2");
		Thread.sleep(750);
		c.put("key3", "value3");
		Thread.sleep(750);
		assertNull(c.get("key2"));
		assertNotNull(c.get("key3"));
		Thread.sleep(750);
		assertNull(c.get("key3"));
	}
	
	public void testRemove () {
		SimpleCache c = new SimpleCache(Long.MAX_VALUE, new HashMap());
		c.remove("key");
		assertNull(c.get("key"));
		c.put("key", "value");
		assertNotNull(c.get("key"));
		c.remove("key");
		assertNull(c.get("key"));
	}
	
	public void testCallBack () {
		SimpleCache c = new SimpleCache(Long.MAX_VALUE, new HashMap());
		assertEquals("value1", c.get("key1", new SimpleCache.Callback() {
			public Object execute() {
				return "value1";
			}
		}));
		assertEquals("value1", c.get("key1"));
		
		// again with a new callback (value)
		c.get("key1", new SimpleCache.Callback() {
			public Object execute() {
				return "value2";
			}
		});
		assertEquals("value1", c.get("key1"));
	}
}

give focus to first input field or textarea on page

Place the following in your application.js file and make sure your layout includes "javascript_include_tag :defaults".
Event.observe(window, 'load', function() {
  var e = $A(document.getElementsByTagName('*')).find(function(e) {
    return (e.tagName.toUpperCase() == 'INPUT' && (e.type == 'text' || e.type == 'password'))
        || e.tagName.toUpperCase() == 'TEXTAREA' || e.tagName.toUpperCase() == 'SELECT';
  });
  if (e) e.focus();
});

non persistent ActiveRecord

# = ActiveForm - non persistent ActiveRecord
#
# Simple base class to make AR objects without a corresponding database
# table.  These objects can still use AR validations but can't be saved
# to the database.  Use the +valid?+ method to validate.
#
# == Example
#
#   class FeedbackForm < ActiveForm
#     column :email
#     column :message
#     validates_presence_of :email, :message
#   end
#
class ActiveForm < ActiveRecord::Base
  def self.columns() @columns ||= []; end # :nodoc:
  
  # Define an attribute, takes the same arguments as
  # ActiveRecord::ConnectionAdapters::Column.new only in a
  # slightly different order.
  def self.column(name, sql_type = nil, default = nil, null = true)
    columns << ActiveRecord::ConnectionAdapters::Column.new(
        name.to_s, default, sql_type.to_s, null)
  end
end
« Newer Snippets
Older Snippets »
Showing 1-10 of 16 total  RSS