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

« Newer Snippets
Older Snippets »
Showing 1-6 of 6 total  RSS 

Marshalize (Cache) ActiveRecord Query Results

A quick way to cache results in a file and read from the file on subsequent requests instead of the database. Makes the initial query a bit slower, but later queries *much* faster.

class MyCachedModel < ActiveRecord::Base
  class << self
    alias_method :rails_original_find_by_sql, :find_by_sql
    def find_by_sql(sql)
      cache_filename = Base64.encode64(sql)
      if File.exists? cache_filename
        Marshal.load(File.open(cache_filename))
      else
        Marshal.dump(records = rails_original_find_by_sql(sql), File.open(cache_filename, 'w'))
        return records
      end
    end
  end
end

Attribute Cache

Sometimes, you don't want to calculate the value of an attribute every time--like the sum of an array. However, you don't want it cached on the outside of the object since that makes for messy code. But you seem to cache things over and over again inside objects. Here's a module AttributeCache that does a little meta programming to make attribute caching a little bit easier. more details at http://webjazz.blogspot.com/2007/05/ruby-snippet-caching-object-attributes.html

module AttributeCache

  def metaclass; class << self; self; end; end
  
  def cache(attr_name, options)
    instance_variable_set "@#{attr_name}", options[:initial]
    instance_variable_set "@#{:sum}_outdated", true

    metaclass.instance_eval do 
      define_method("outdate_#{attr_name}") do
        puts "in outdated"
        instance_variable_set "@#{attr_name}_outdated", true
      end
    end
    
    metaclass.class_eval %Q{
      def cached_#{attr_name}(&block)
        if @#{attr_name}_outdated
          puts "in cached to update!"
          @#{attr_name}_outdated = false
          @#{attr_name} = block.call
        else
          puts "in cached to give cache!"
        end
        return @#{attr_name}
      end
    }

  end
  
end

class Collection
  include AttributeCache

  def initialize
    @array = []
    cache :sum, :initial => 0
  end

  def add(x)
    outdate_sum
    @array << x
  end

  def sum
    cached_sum { @array.inject {|t, e| t += e} }
  end
  
end

c = Collection.new
puts c.public_methods(false).inspect
c.add(2)
c.add(3)
puts c.sum
puts c.sum
c.add(4)
puts c.sum
puts c.sum

JS cache control

The following JSP snippet shows a simple caching policy for JS files, i.e.
the browser fetches js files when a new build is available.

qBuildNum is a build number or a repository revision suffix, e.g.?102 etc.
ServerInfoService.getBuildNumber() is a function which returns current build number. This function has to be coded manually, alternatively a servlet context attribute can be used...
<%
    String qBuildNum = '?' + ServerInfoService.getBuildNumber(); //Suffix for JS to avoid caching
%>
<script type="text/javascript" src="dwr/engine.js?113"></script>
<script type="text/javascript" src="dwr/util.js?113"></script>
<script type="text/javascript" src="rico/prototype.js?14"></script>
<script type="text/javascript" src="rico/rico.js?112"></script>
<script type="text/javascript" src="script/controller.js<%=qBuildNum%>"></script>

Rails - ‘poor mans’ SQL cache.

Rails memcached is not very easy to introduce to a large rails installation. Memcached also chews up a lot of memory on the box and overall cached model does not work the way I needed it to. Basically, I have just a “few� queries that I needed to cache because pagination sucks just that bad in rails.
So, I built my own cache, similar to how I build them in PHP except I am not using disk cache, I am using MySQL itself to cache it’s own results

First, we need a table to hold all this info (note the ‘blob’ field)
CREATE TABLE `cacheditems` (
`id` int(11) NOT NULL auto_increment,
`cachekey` varchar(255) default NULL,
`created` datetime default NULL,
`expires` datetime default NULL,
`content` longblob,
`cachehit` int(11) NOT NULL,
PRIMARY KEY  (`id`),
KEY `cacheditems_cachekey_index` (`cachekey`),
KEY `cacheditems_created_index` (`created`),
KEY `cacheditems_expires_index` (`expires`)
)

Then we create a model called “cacheditem� which has the following functions
require 'digest/sha1'
class Cacheditem < ActiveRecord::Base

def self.checkfor(sql)
key = Digest::MD5.hexdigest(Marshal.dump(sql))
logger.info "%%% checking for key #{key}"
#logger.info "%%% checking by sql #{sql[0]}"
Cacheditem.find( :first, :conditions => [ “cachekey = ? AND expires > NOW()�, key] )
end

def self.getcached(sql)
key = Digest::MD5.hexdigest(Marshal.dump(sql))
logger.info “%%% getting by key #{key}�
#logger.info “%%% getting by sql #{sql[0]}�
getc = Cacheditem.find( :first, :conditions => [ “cachekey = ?�, key] )
hitcount = getc.cachehit + 1
Cacheditem.update(getc.id, {:cachehit => hitcount})
Cacheditem.delete_all “expires < NOW()"  # cleaner
return Marshal.load( getc.content )
end

def self.storeresult(sql, result)
key = Digest::MD5.hexdigest(Marshal.dump(sql))
logger.info "%%% storing by key #{key}"
content = Marshal.dump(result)
logger.level = (4) # this stops display in logs of the marshal data
ci = new()
ci.cachekey    = key
ci.created       = Time.now()
ci.expires        = 30.minutes.from_now() # change as needed
ci.content        = content
ci.cachehit       = 0
ci.save
return  result
end

end

Then, in application.rb I added the following function
def find_by_sql_cache(sql)
iscached = Cacheditem.checkfor(sql)
if iscached
Cacheditem.getcached(sql)
else
result = connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
Cacheditem.storeresult(sql, result)
end
end

just throw “_cache� after any “find_by_sql� statement you have a need to cache and there you are.

This works very fast, very well, and doesn’t hog your memory. It cleans up after itself in the database, and perhaps it does that too much.. It would be easy to add in a standard garbage collection function which runs on a random but I felt this gave me much better stats of the actual thirty-minute cache…
If you use zabbix for monitoring your network, you can have fun graphs of cache statistics by adding the following to your zabbix_agentd.conf
UserParameter=mysql.totalcache,mysql –batch –skip-column-names -D {YOUR_DATABASE} -u{YOUR_USERNAME} -p{YOUR_PASSWORD} -e “SELECT count( * ) AS total, SUM( cachehit ) AS amount from cacheditems;� | cut -f1
UserParameter=mysql.cachehits,mysql –batch –skip-column-names -D {YOUR_DATABASE} -u{YOUR_USERNAME} -p{YOUR_PASSWORD} -e “SELECT count( * ) AS total, SUM( cachehit ) AS amount from cacheditems;� | cut -f2

Time based expiration of cache fragments in Ruby on Rails (MemCacheStore and FileStore)

This allows to expire cached fragments by ttl. Example usage (in views):

<% cache("name", :ttl => 7.days) do %>
  ... some database-heavy stuff ...
<% end %>


Put the following in environment.rb or in lib/.
class ActionController::Caching::Fragments::MemCacheStore
  def write(name, value, options=nil)
    if options.is_a?(Hash) && options.has_key?(:ttl)
      ttl = options[:ttl]
    else
      ttl = 0
    end
    @data.set(name, value, ttl)
  end
end

module ActionView::Helpers::CacheHelper
  def cache(name = {}, options = nil, &block)
    @controller.cache_erb_fragment(block, name, options)
  end
end

class ActionController::Caching::Fragments::UnthreadedFileStore
  def read(name, options = nil) 
    if options.is_a?(Hash) && options.has_key?(:ttl)
      ttl = options[:ttl]
    else
      ttl = 0
    end

    fn = real_file_path(name)

    # if cache expired act as if file doesn't exist
    return if ttl > 0 && File.exists?(fn) && (File.mtime(fn) < (Time.now - ttl))

    File.open(fn, 'rb') { |f| f.read } rescue nil
  end
end
« Newer Snippets
Older Snippets »
Showing 1-6 of 6 total  RSS