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-9 of 9 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

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

url and xml encode to fool naive web spiders

Encode all characters to XML entities:

  def xml_encode(text)
    text.unpack('c*').map{|c|"&\##{c};"}.join
  end


Encode all characters to %00%00.. url ecoding:

  def url_encode(text)
    text.split('').map{|c|"%#{c.unpack('H2')}"}.join
  end


The following:

  <a href="<%= xml_encode("mailto:" + url_encode("somebody@somewhere.net")) %>">mail somebody</a>


yields:

  <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#37;&#55;&#51;&#37;&#54;&#102;&#37;&#54;&#100;&#37;&#54;&#53;&#37;&#54;&#50;&#37;&#54;&#102;&#37;&#54;&#52;&#37;&#55;&#57;&#37;&#52;&#48;&#37;&#55;&#51;&#37;&#54;&#102;&#37;&#54;&#100;&#37;&#54;&#53;&#37;&#55;&#55;&#37;&#54;&#56;&#37;&#54;&#53;&#37;&#55;&#50;&#37;&#54;&#53;&#37;&#50;&#101;&#37;&#54;&#101;&#37;&#54;&#53;&#37;&#55;&#52;">mail somebody</a>


which works fine in a browser.

determine image size

Attributed to Sam Stephenson:
IO.read('image.png')[0x10..0x18].unpack('NN')
=> [713, 54]

Homegrown,

GIF:
IO.read('image.gif')[6..10].unpack('SS')
=> [130, 50]

BMP:
d = IO.read('image.bmp')[14..28]
d[0] == 40 ? d[4..-1].unpack('LL') : d[4..8].unpack('SS')


JPEG (slightly more complex):
class JPEG
  attr_reader :width, :height, :bits

  def initialize(file)
    if file.kind_of? IO
      examine(file)
    else
      File.open(file, 'rb') { |io| examine(io) }
    end
  end

private
  def examine(io)
    raise 'malformed JPEG' unless io.getc == 0xFF && io.getc == 0xD8 # SOI

    class << io
      def readint; (readchar << 8) + readchar; end
      def readframe; read(readint - 2); end
      def readsof; [readint, readchar, readint, readint, readchar]; end
      def next
        c = readchar while c != 0xFF
        c = readchar while c == 0xFF
        c
      end
    end

    while marker = io.next
      case marker
        when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF # SOF markers
          length, @bits, @height, @width, components = io.readsof
          raise 'malformed JPEG' unless length == 8 + components * 3
        when 0xD9, 0xDA:  break # EOI, SOS
        when 0xFE:        @comment = io.readframe # COM
        when 0xE1:        io.readframe # APP1, contains EXIF tag
        else              io.readframe # ignore frame
      end
    end
  end
end

From my blog entry about reading JPEG.

read and parse RSS feed

require 'open-uri'

require 'rss/0.9'
require 'rss/1.0'
require 'rss/2.0'
require 'rss/parser'

url = 'http://some.host/rss.xml'
rss = RSS::Parser.parse(open(url){|fd|fd.read})
puts rss.items.collect{|item|item.title}.join("\n")


See also simple-rss at rubyforge.
« Newer Snippets
Older Snippets »
Showing 1-9 of 9 total  RSS