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

Tim Morgan http://timmorgan.org

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

Automatic Expiration of Rails Action Caching

Would love to get some feedback on this...

No doubt, 5 minutes after posting this, someone will tell me of the built-in Rails way of doing this, but alas I could not find it.

Usage looks like this:

class PeopleController < ApplicationController
  caches_action :show, :for => 1.hour, :cache_path => Proc.new { |c| "people/#{c.params[:id]}_for_#{Person.logged_in.id}" }
end


The ":for => 1.hour" part is where the magic happens.

Basically, this bit of code adds a before_filter that checks the last modified time of the cache entry, and expires it if it is older than the specified time period.

module ActionController
  module Caching
    module Fragments
      # expire a cache key only if the block returns true or
      # if the age of the fragment is more than the specified age argument.
      def expire_fragment_by_mtime(key, age=nil, &block)
        block = Proc.new { |m| m < age.ago } unless block_given?
        if (m = cache_store.mtime(fragment_cache_key(key))) and block.call(m)
          expire_fragment(key)
        end
      end
    end
    module Actions
      module ClassMethods
        # adds an option :for
        # caches_action :show, :for => 2.hours, :cache_path => ...
        # :cache_path is required, unfortunately
        def caches_action_with_for(*actions)
          original_actions = actions.clone
          options = actions.extract_options!
          if for_time = options.delete(:for)
            cache_path = options[:cache_path]
            before_filter do |controller|
              cache_path = cache_path.call(controller) if cache_path.respond_to?(:call)
              controller.expire_fragment_by_mtime(cache_path, for_time)
            end
          end
          caches_action_without_for(*original_actions)
        end
        alias_method_chain :caches_action, :for
      end
    end
  end
end

# Add a method to grab the last modified time of the cache key.
# If you use a store other than the FileStore, you'll need to add
# a method like this to your store.
module ActiveSupport
  module Cache
    class FileStore
      def mtime(name)
        File.mtime(real_file_path(name)) rescue nil
      end
    end
  end
end

Rails Notice/Warning Flash Message

Somewhat lame, but handy nonetheless.

<% if flash[:warning] or flash[:notice] %>
  <div id="notice" <% if flash[:warning] %>class="warning"<% end %>>
    <%= flash[:warning] || flash[:notice] %>
  </div>
  <script type="text/javascript">
    setTimeout("new Effect.Fade('notice');", 15000)
  </script>
<% end %>

Rails MySQL/SQLite convenience methods

Usage:

  Person.find :all, :conditions => ["#{sql_year 'birthday'} >= ?", year]


SQLITE = true # or false

def sql_concat(*args)
  SQLITE ? args.join(' || ') : "CONCAT(#{args.join(', ')})"
end

def sql_lcase(expr)
  SQLITE ? "LOWER(#{expr})" : "LCASE(#{expr})"
end

def sql_year(expr)
  SQLITE ? "CAST(STRFTIME('%y', #{expr}) as 'INTEGER')" : "YEAR(#{expr})"
end

def sql_month(expr)
  SQLITE ? "CAST(STRFTIME('%m', #{expr}) as 'INTEGER')" : "MONTH(#{expr})"
end

def sql_day(expr)
  SQLITE ? "CAST(STRFTIME('%d', #{expr}) as 'INTEGER')" : "DAY(#{expr})"
end

def sql_now
  SQLITE ? "CURRENT_TIMESTAMP" : "NOW()"
end

Rails Array#add_condition Method

Lets you add a condition to a set of ActiveRecord conditions easily like this:

  conditions = ['active = ? and type = ?', true, 2]
  conditions.add_condition ['person_id = ?', 345]


class Array
  def add_condition(condition, conjunction='and')
    if condition.is_a? Array
      if self.empty?
        (self << condition).flatten!
      else
        self[0] += " #{conjunction} " + condition.shift
        (self << condition).flatten!
      end
    elsif condition.is_a? String
      self[0] += " #{conjunction} " + condition
    else
      raise "don't know how to handle this condition type"
    end
    self
  end
end

Ultimate Radiant CMS Script

This is a script that builds a Radiant CMS site with several third-party extensions I use a lot. This just saves me time from having to look up the setup commands each time. YMMV

I call this power_radiant and stick it in /usr/local/bin.

Update: this has been fixed to work with latest Radiant code (since svn path as changed and freeze:edge task no longer works). You will need Git to be installed on your machine for this to work.

#!/bin/sh
radiant --database sqlite3 $1
cd $1
echo "
production:
  adapter: sqlite3
  database: db/production.sqlite3
test:
  adapter: sqlite3
  database: db/test.sqlite3
development:
  production
" > config/database.yml
svn export http://svn.radiantcms.org/radiant/trunk/extensions/shards/ vendor/extensions/shards
svn export svn://zuurstof.openminds.be/home/kaizer/svn/rails_stuff/radiant_extensions/wym_editor_filter vendor/extensions/wym_editor_filter
svn export http://svn.seancribbs.com/svn/rails/plugins/extensions/page_attachments vendor/extensions/page_attachments
#rake radiant:freeze:edge # stopped working in latest gem
git clone git://github.com/seancribbs/radiant.git /tmp/radiant
cp -r /tmp/radiant/radiant vendor/radiant
rake production db:bootstrap
rake production db:migrate:extensions
rake production radiant:extensions:wym_editor_filter:install
rake production radiant:extensions:page_attachments:update


To run:
power_radiant my_site

Using POP3 to Retrieve Email for Rails App

Put this in a file called "inbox" in the scripts/ directory of your Rails app. change the host, username, and password to match.

Set up a cron job to run this script every so often (frequency depends on your needs).

#!/usr/bin/env ruby

require 'net/pop'
require File.dirname(__FILE__) + '/../config/environment'

logger = RAILS_DEFAULT_LOGGER

logger.info "Running Mail Importer..." 
Net::POP3.start("localhost", nil, "username", "password") do |pop|
  if pop.mails.empty?
    logger.info "NO MAIL" 
  else
    pop.mails.each do |email|
      begin
        logger.info "receiving mail..." 
        Notifier.receive(email.pop)
        email.delete
      rescue Exception => e
        logger.error "Error receiving email at " + Time.now.to_s + "::: " + e.message
      end
    end
  end
end
logger.info "Finished Mail Importer." 

image_tag with popup for alt attribute

Firefox doesn't display a popup for the alt attribute for images. While this is according to spec, it's slightly annoying to put the same text in both the alt and the title. Put the following in your ApplicationHelper to copy the alt to the title for every image. Please don't tell me how wrong this is. I don't care.

module ApplicationHelper
  def image_tag(location, options)
    options[:title] ||= options[:alt]
    super(location, options)
  end
end

Fix for ActiveRecord SQL Server adapter dates

The SQL Server adapter for ActiveRecord uses Time objects to cast dates from the db. This fails for dates before 1970, thus some birthdates come back as nil. This is some kludge to use a DateTime in that case so we still get the value.

A better approach may be to convert this code to use DateTime objects exclusively, but I'm not sure of the speed implications of doing so. The code below first tries to cast the value to a Time object; if that fails, it tries a DateTime object; if that fails, it returns nil.

Stick this in a plugin to use with Rails.

module ActiveRecord
  module ConnectionAdapters
    class ColumnWithIdentity
      def cast_to_time(value)
        return value if value.is_a?(Time) or value.is_a?(DateTime)
        time_array = ParseDate.parsedate(value)
        time_array[0] ||= 2000
        time_array[1] ||= 1
        time_array[2] ||= 1
        Time.send(Base.default_timezone, *time_array) rescue DateTime.new(*time_array[0..5]) rescue nil
      end
      def cast_to_datetime(value)
        if value.is_a?(Time) or value.is_a?(DateTime)
          if value.year != 0 and value.month != 0 and value.day != 0
            return value
          else
            return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
          end
        end
        return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
        value
      end
    end
  end
end

Force output to download as a file in Rails

response.headers['Content-Type'] = 'text/csv' # I've also seen this for CSV files: 'text/csv; charset=iso-8859-1; header=present'
response.headers['Content-Disposition'] = 'attachment; filename=thefile.csv'

Rails - Easily work with uppercase column names

I had to work with a legacy database with hundreds of tables with uppercase column names. Here is how I made my life a whole lot easier...

module ActiveRecord
  class MyCustomARClass < ActiveRecord::Base
  
    def self.reloadable?
      false
    end
    
    # all columns are uppercase
    set_primary_key 'ID'
    
    # convert to uppercase attribute if in existence
    # record.name => record.NAME
    # record.id => record.ID
    def method_missing(method_id, *args, &block)
      method_id = method_id.to_s.upcase if @attributes.include? method_id.to_s.upcase.gsub(/=/, '')
      super(method_id, *args, &block)
    end

    # strip leading and trailing spaces from attribute string values
    def read_attribute(attr_name)
      value = super(attr_name)
      value.is_a?(String) ? value.strip : value
    end

    class << self # Class methods
      
      private
        # allow for find_by and such to work with uppercase attributes
        # find_by_name => find_by_NAME
        # find_by_dob_and_height => find_by_DOB_and_HEIGHT
        def extract_attribute_names_from_match(match)
          match.captures.last.split('_and_').map { |name| name.upcase }
        end
    end
  end
end


This was defined in a plugin. Then, in each of my models I used the subclass instead of ActiveRecord::Base, like so...

class MyModel < ActiveRecord::MyCustomARClass
  # ...
end
« Newer Snippets
Older Snippets »
Showing 1-10 of 13 total  RSS