<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
  <title>Rails, Ruby &amp; Prototype ramblings - Home</title>
  <id>tag:blog.methodmissing.com,2008:mephisto/</id>
  <generator version="0.7.0" uri="http://mephistoblog.com">Mephisto Noh-Varr</generator>
  <link href="http://blog.methodmissing.com/feed/atom.xml" rel="self" type="application/atom+xml"/>
  <link href="http://blog.methodmissing.com/" rel="alternate" type="text/html"/>
  <updated>2008-02-04T02:28:59Z</updated>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2008-02-04:64</id>
    <published>2008-02-04T02:20:00Z</published>
    <updated>2008-02-04T02:28:59Z</updated>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2008/2/4/exception-notification-and-edge" rel="alternate" type="text/html"/>
    <title>Exception Notification and Edge</title>
<content type="html">
            &lt;p&gt;Changeset &lt;a href='http://dev.rubyonrails.org/changeset/8669'&gt;8669&lt;/a&gt; breaks the &lt;a href='http://dev.rubyonrails.org/browser/plugins/exception_notification'&gt;exception_notification&lt;/a&gt; plugin ... raising Unprocessed view path found in &amp;lt;somewhere&gt;.&lt;/p&gt;

&lt;p&gt;Drop the following in config/initializers/exception_notification.rb ... &lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;ExceptionNotifier.exception_recipients = %w(you@yourdomain.com)
ExceptionNotifier.class_eval do 
  remove_method :template_root 
  ExceptionNotifier.template_root = &quot;#{RAILS_ROOT}/vendor/plugins/exception_notification/lib/../views&quot; 
end&lt;/code&gt;&lt;/pre&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2008-01-19:62</id>
    <published>2008-01-19T12:06:00Z</published>
    <updated>2008-01-21T16:03:44Z</updated>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2008/1/19/edge-callback-refactorings-attachment_fu" rel="alternate" type="text/html"/>
    <title>Edge callback refactorings &amp;&amp; attachment_fu</title>
<content type="html">
            &lt;p&gt;Edge changeset &lt;a href='http://dev.rubyonrails.org/changeset/8664'&gt;8664&lt;/a&gt; introduces ActiveSupport::Callbacks.&lt;/p&gt;

&lt;p&gt;This currently breaks attachment_fu's callback internals and may affect other plugins as well ...&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;module Technoweenie
  module AttachmentFu

    module InstanceMethods

      def self.included( base )
        base.define_callbacks *[:after_resize, :after_attachment_saved, :before_thumbnail_saved]
      end  

      def callback_with_args(method, arg = self)
         notify(method)

          result = run_callbacks(method, { :object =&gt; arg }) { |result, object| result == false }

          if result != false &amp;&amp; respond_to_without_attributes?(method)
            result = send(method)
          end

          return result
      end      

      def run_callbacks(kind, options = {}, &amp;block)
        options.reverse_merge!( :object =&gt; self )
        ::ActiveSupport::Callbacks::Callback.run(self.class.send(&quot;#{kind}_callback_chain&quot;), options[:object], options, &amp;block)
      end      
    end
  end
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Hope the above monkey patch relieves a bout of intense swearing.&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2007-06-04:31</id>
    <published>2007-06-04T02:26:00Z</published>
    <updated>2007-06-04T03:17:12Z</updated>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2007/6/4/get-your-caffeine-on" rel="alternate" type="text/html"/>
    <title>Get your Caffeine on</title>
<content type="html">
            &lt;p&gt;I recently stumbled upon &lt;a href='http://rubyforge.org/projects/adocca-plugins/'&gt;Adocca plugins&lt;/a&gt;, which is a suite of plugins / extensions extracted from the development of &lt;a href='http://www.trig.com'&gt;trig.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This includes a nifty Ruby extension, Caffeine, built on &lt;a href='http://people.freebsd.org/~seanc/libmemcache/'&gt;libmemcache&lt;/a&gt;, the C API for &lt;a href='http://www.danga.com/memcached'&gt;Memcached&lt;/a&gt;.The API is almost 100% compatible with memcache-client, with the following exceptions :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No get_multi ( may or may not be a big deal for you )&lt;/li&gt;
&lt;li&gt;Multithreaded by default, only accepts a single :namespace as options Hash&lt;/li&gt;
&lt;li&gt;MemCache#servers is write only and you can't overwrite previously defined servers&lt;/li&gt;
&lt;li&gt;MemCache#get accepts either a String or Array, the latter which forces a namespace lookup (more on this later)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll continue with a quick rundown ...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Example installation ( OS X )&lt;/li&gt;
&lt;li&gt;Requires a properly configured Memcached &lt;a href='http://wiki.rubyonrails.com/rails/pages/MemCached'&gt;environment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Libmemcache installation&lt;/h2&gt;

&lt;p&gt;Grab it &lt;a href='http://people.freebsd.org/~seanc/libmemcache/libmemcache-1.4.0.rc2.tar.bz2'&gt;here&lt;/a&gt;, extract and ....&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
libmemcache-1.4.0.rc2 lourens$ ./configure &amp;&amp; make &amp;&amp; make install
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Libuuid installation&lt;/h2&gt;

&lt;p&gt;Download the &lt;a href='http://e2fsprogs.sourceforge.net'&gt;e2fsprogs package&lt;/a&gt;, extract and ...&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
e2fsprogs-1.39 lourens$ ./configure
e2fsprogs-1.39 lourens$  cd lib/uuid
e2fsprogs-1.39 lourens$ make &amp;&amp; sudo make install
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We don't need the whole package, just libuuid.&lt;/p&gt;

&lt;h2&gt;Caffeine installation&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;
lourens$ sudo gem install caffeine
Building native extensions.  This could take a while...
caffeine.c: In function 'key_from_segments':
caffeine.c:712: warning: incompatible implicit declaration of built-in function 'mempcpy'
caffeine.c: In function 'md5_digest':
caffeine.c:872: warning: pointer targets in passing argument 1 of 'MD5' differ in signedness
/usr/bin/ld: warning multiple definitions of symbol _setregid
/opt/local/lib/libruby.dylib(process.o) definition of _setregid
/usr/lib/gcc/i686-apple-darwin8/4.0.1/../../../libpthread.dylib(setregid.So) definition of _setregid
/usr/bin/ld: warning multiple definitions of symbol _setreuid
/opt/local/lib/libruby.dylib(process.o) definition of _setreuid
/usr/lib/gcc/i686-apple-darwin8/4.0.1/../../../libpthread.dylib(setreuid.So) definition of _setreuid
ruby extconf.rb install caffeine
checking for mc_new() in -lmemcache... yes
checking for uuid_generate() in -luuid... yes
checking for main() in -lssl... yes
creating Makefile

make
gcc -I. -I. -I/opt/local/lib/ruby/1.8/i686-darwin8.9.1 -I. -O -pipe -I/opt/local/include -fno-common -ggdb -O0 -Df  -c caffeine.c
cc -dynamic -bundle -undefined suppress -flat_namespace -L/opt/local/lib   -L&quot;/opt/local/lib&quot; -o caffeine.bundle caffeine.o  -lruby -lssl -luuid -lmemcache  -lpthread -ldl -lobjc -lspread 

make install
/usr/bin/install -c -m 0755 caffeine.bundle /opt/local/lib/ruby/gems/1.8/gems/caffeine-0.0.2/lib

make clean
Successfully installed caffeine-0.0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Namespaces&lt;/h2&gt;

&lt;p&gt;The interesting aspect about this client lib is the elegant namespace support, not to be confused with your connection namespace.One could have keys like the following (pseudo code) :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bookings-development:Account:1:Addresses:2&lt;/li&gt;
&lt;li&gt;bookings-development:Account:20:Addresses:12&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Very simple to sweep a whole set of related keys eg. to expire Account information, we just unlink the Account namespace.Actual keys resemble this ...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bookings-development:7f722ed3a969554d010e62510217cc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;... courtesy of libuuid to avoid namespace / key trashing.&lt;/p&gt;

&lt;h2&gt;Performance?&lt;/h2&gt;

&lt;p&gt;I didn't gain all that much by using Caffeine in my dev / staging environment, but mileage varies, I like to keep good karma and thus won't open a 'scaling / performance' can of worms.&lt;/p&gt;

&lt;h2&gt;Integration&lt;/h2&gt;

&lt;p&gt;The following svn diff applied to &lt;a href='http://require.errtheblog.com/plugins/browser/cache_fu'&gt;cache_fu&lt;/a&gt; revision 240 should have you up and running without hickups.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
lourens$ svn diff vendor/plugins/cache_fu
Index: vendor/plugins/cache_fu/lib/acts_as_cached/config.rb
===================================================================
--- vendor/plugins/cache_fu/lib/acts_as_cached/config.rb        (revision 2337)
+++ vendor/plugins/cache_fu/lib/acts_as_cached/config.rb        (working copy)
@@ -42,9 +42,16 @@

     def setup_memcache(config)
       config[:namespace] &amp;lt;&amp;lt; &quot;-#{RAILS_ENV}&quot;
-
+      
+      begin
+        require 'caffeine'
+        Caffeine::MemCache.class_eval{ attr_reader :namespace  }
+      rescue          
+      end
+                 
       silence_warnings do
-        Object.const_set :CACHE, MemCache.new(config)
+        Object.const_set :CACHE, Object.const_defined?( :Caffeine ) ? Caffeine::MemCache.new( { :namespace =&gt; config[:namespace] } ) : MemCache.new(config) 
+                                 
       end

       CACHE.servers = Array(config.delete(:servers))
Index: vendor/plugins/cache_fu/lib/acts_as_cached/cache_methods.rb
===================================================================
--- vendor/plugins/cache_fu/lib/acts_as_cached/cache_methods.rb (revision 2337)
+++ vendor/plugins/cache_fu/lib/acts_as_cached/cache_methods.rb (working copy)
@@ -26,7 +26,7 @@
         id = args.first
       end

-      if (item = fetch_cache(id)).nil?
+      if (item = fetch_cache(id)).nil? || item == :MemCache_no_such_entry
         set_cache(id, block_given? ? yield : fetch_cachable_data(id), options[:ttl])
       else
         item
@@ -239,7 +239,7 @@
       set_cache
     end

-    # Lourens Naud?
+    # Lourens Naudé
     def expire_cache_with_associations(*associations_to_sweep)
       ((cache_options[:include] || []) + associations_to_sweep).flatten.uniq.compact.each do |assoc|
         Array(send(assoc)).compact.each { |item| item.expire_cache if item.respond_to?(:expire_cache) }

&lt;/code&gt;&lt;/pre&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2007-05-19:27</id>
    <published>2007-05-19T02:08:00Z</published>
    <updated>2007-05-19T02:49:21Z</updated>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2007/5/19/warmup-mongrels-on-deploy" rel="alternate" type="text/html"/>
    <title>Warmup Mongrels on deploy</title>
<content type="html">
            &lt;h1&gt;... so your users don't have to&lt;/h1&gt;

&lt;p&gt;After each deployment with &lt;a href='http://www.capify.org/'&gt;Capistrano&lt;/a&gt;, there's a slight delay on the first request to each of your Mongrel processes. &lt;/p&gt;

&lt;p&gt;Any Libraries, GEMS, Models, Helpers, Controllers etc. not referenced by plugins or during config.inititalize ( and the callback variants ) in your environment is registered and loaded by the Rails Dependency mechanism on the first request.&lt;/p&gt;

&lt;p&gt;This delay is relative to your Application size and the number of third party dependencies (2 seconds for me). &lt;/p&gt;

&lt;h2&gt;A Warmup task for your deployment recipe&lt;/h2&gt;

&lt;p&gt;CAUTION: The namespaces is Capistrano 2 specific, just drop them if you're using 1.x revisions.&lt;/p&gt;

&lt;p&gt;This snippet would only execute IF &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a configuration file ( mongrel&lt;em&gt;cluster.yml ) exists in RAILS&lt;/em&gt;ROOT/config&lt;/li&gt;
&lt;li&gt;your production system has the &lt;a href='http://www.gnu.org/software/wget/'&gt;Wget&lt;/a&gt; package installed ( standard on most Linux distros ).Modify this for your OS specific CLI HTTP client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single request is sent to each of your mongrel ports as defined in mongrel_cluster.yml.
This may 404 depending on your application configuration ( subdomain based? ), in other words any status code is fine, 'Connection refused' not. &lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;namespace :deploy do
  task :warmup, :roles =&gt; :app do
    require 'yaml'
     c = YAML.load_file(File.join('./', 'config', 'mongrel_cluster.yml')) rescue {}
     ((c[&quot;port&quot;].to_i)...(c[&quot;port&quot;].to_i + c[&quot;servers&quot;].to_i)).each do |port|
       run 'wget localhost:%04d &gt; /dev/null' % port  rescue 'No wget binary found'
       sleep(0.2)
     end unless c.empty?
  end
end&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;The call chain&lt;/h2&gt;

&lt;p&gt;I use the pattern below as my default App server deploy recipe.&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;namespace :deploy do
  task :default, :roles =&gt; :app do
    update
    compress_and_package_assets
    web:disable
    restart
    sleep(20)
    warmup
    sleep(2)
    web:enable
  end
end&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Update code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gzip and merge all JS and CSS assets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Disable App tier via Proxy&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fire up Mongrels and allow 20 seconds for them to do so&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Warm them up&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sleep another 2  seconds for everything to settle down&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Allow traffic via Proxy&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2007-05-10:25</id>
    <published>2007-05-10T22:38:00Z</published>
    <updated>2007-05-10T22:57:25Z</updated>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2007/5/10/rails-memory-usage-case-study" rel="alternate" type="text/html"/>
    <title>Rails memory usage case study</title>
<content type="html">
            &lt;p&gt;Stumbled across this interesting &lt;a href='http://rubyforge.org/pipermail/mongrel-users/2007-March/003098.html'&gt;thread&lt;/a&gt; earlier today.&lt;/p&gt;

&lt;p&gt;The remainder of this post assumes the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production environment &lt;/li&gt;
&lt;li&gt;Mongrel does not leak memory (Zed would have told you so if it did) &lt;/li&gt;
&lt;li&gt;Latest stable Mongrel and FastThread&lt;/li&gt;
&lt;li&gt;You're not using the usual culprits, RMagick and Ferret&lt;/li&gt;
&lt;li&gt;You're not running any patched GC&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;The system&lt;/h1&gt;

&lt;p&gt;My staging system is CentOS 4 (64 bit) with 2GB RAM and runs 5 x PHP FCGI's, Nginx, Memcached, MySQL 5 and 8 x Mongrels.
Staging Mongrel traffic only, medium traffic PHP forum.&lt;/p&gt;

&lt;h1&gt;Linux memory management&lt;/h1&gt;

&lt;p&gt;Here's a snapshot after just firing up mongrel_cluster:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
[bookings@somehost ~]$  free -m
             total       used       free     shared    buffers     cached
Mem:          2003       1390        612          0        107        152
-/+ buffers/cache:       1131        871
Swap:         1992         81       1910
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The -/+ buffers/cache is how much memory I have available, swap space is pretty much stock defaults for CentOS.&lt;/p&gt;

&lt;p&gt;Here's a process snapshot of the Mongrels ( all idle, no traffic, no warm up, &lt;em&gt;yet&lt;/em&gt; ):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
[bookings@somehost ~]$ ps aux | grep mongrel
USER       PID %CPU %MEM   VSZ  RSS TTY      STAT START   TIME COMMAND
mongrel   7659  2.7  4.8 139080 99840 ?      S    11:35   0:15 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8000 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8000.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7662  2.6  4.8 138648 99408 ?      S    11:35   0:15 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8001 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8001.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7665  2.7  4.6 134836 95560 ?      S    11:35   0:15 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8002 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8002.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7668  2.7  4.6 133684 94408 ?      S    11:35   0:15 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8003 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8003.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7671  2.7  4.7 135744 96540 ?      S    11:35   0:15 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8004 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8004.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7674  2.7  4.7 135768 96540 ?      S    11:35   0:15 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8005 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8005.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7677  2.7  4.6 133644 94404 ?      S    11:35   0:15 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8006 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8006.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7680  2.7  4.8 139284 100044 ?     S    11:36   0:15 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8007 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8007.pid -c /u/apps/bookings/current --user mongrel --group mongrel
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Virtual Size&lt;/h2&gt;

&lt;p&gt;The Virtual Size (VIRT in 'top' output) for the process with PID 7659 is 139MB.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total memory used by the process&lt;/li&gt;
&lt;li&gt;INCLUDING any shared libraries (FreeImage, MySQL, Zlib etc.)&lt;/li&gt;
&lt;li&gt;INCLUDING memory shared with other processes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A more verbose example of exactly which shared libs we require ( Rails AND Mongrel ):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
[root@somehost bookings]# pmap -d 7659
7659:   /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8000 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8000.pid -c /u/apps/bookings/current --user mongrel --group mongrel
Address           Kbytes Mode  Offset           Device    Mapping
0000000000400000     780 r-x-- 0000000000000000 008:00003 ruby
00000000005c3000       8 rw--- 00000000000c3000 008:00003 ruby
00000000005c5000   43380 rwx-- 00000000005c5000 000:00000   [ anon ]
0000002a95556000      16 rw--- 0000002a95556000 000:00000   [ anon ]
0000002a95563000     404 rw--- 0000002a95563000 000:00000   [ anon ]
0000002a955c9000     704 rw--- 0000002a955c9000 000:00000   [ anon ]
0000002a95679000      12 r-x-- 0000000000000000 008:00003 digest.so
0000002a9567c000    1020 ----- 0000000000003000 008:00003 digest.so
0000002a9577b000       4 rw--- 0000000000002000 008:00003 digest.so
0000002a9577c000     232 r-x-- 0000000000000000 008:00003 openssl.so
0000002a957b6000    1020 ----- 000000000003a000 008:00003 openssl.so
0000002a958b5000      12 rw--- 0000000000039000 008:00003 openssl.so
0000002a958b8000       4 r-x-- 0000000000000000 008:00003 fcntl.so
0000002a958b9000    1020 ----- 0000000000001000 008:00003 fcntl.so
0000002a959b8000       4 rw--- 0000000000000000 008:00003 fcntl.so
0000002a959b9000    3548 rw--- 0000002a959b9000 000:00000   [ anon ]
0000002a95d30000      20 r-x-- 0000000000000000 008:00003 stringio.so
0000002a95d35000    1020 ----- 0000000000005000 008:00003 stringio.so
0000002a95e34000       4 rw--- 0000000000004000 008:00003 stringio.so
0000002a95e35000     112 r-x-- 0000000000000000 008:00003 syck.so
0000002a95e51000    1020 ----- 000000000001c000 008:00003 syck.so
0000002a95f50000       8 rw--- 000000000001b000 008:00003 syck.so
0000002a95f52000      40 r-x-- 0000000000000000 008:00003 socket.so
0000002a95f5c000    1024 ----- 000000000000a000 008:00003 socket.so
0000002a9605c000       4 rw--- 000000000000a000 008:00003 socket.so
0000002a9605d000      24 r-x-- 0000000000000000 008:00003 http11.so
0000002a96063000    1024 ----- 0000000000006000 008:00003 http11.so
0000002a96163000       4 rw--- 0000000000006000 008:00003 http11.so
0000002a96164000      16 r-x-- 0000000000000000 008:00003 fastthread.so
0000002a96168000    1020 ----- 0000000000004000 008:00003 fastthread.so
0000002a96267000       4 rw--- 0000000000003000 008:00003 fastthread.so
0000002a96268000      16 r-x-- 0000000000000000 008:00003 thread.so
0000002a9626c000    1020 ----- 0000000000004000 008:00003 thread.so
0000002a9636b000       4 rw--- 0000000000003000 008:00003 thread.so
0000002a9636c000      36 r-x-- 0000000000000000 008:00003 zlib.so
0000002a96375000    1020 ----- 0000000000009000 008:00003 zlib.so
0000002a96474000       4 rw--- 0000000000008000 008:00003 zlib.so
0000002a96475000       8 r-x-- 0000000000000000 008:00003 etc.so
0000002a96477000    1024 ----- 0000000000002000 008:00003 etc.so
0000002a96577000       4 rw--- 0000000000002000 008:00003 etc.so
0000002a96579000    4104 rw--- 0000002a96579000 000:00000   [ anon ]
0000002a96987000      40 r-x-- 0000000000000000 008:00003 libnss_files-2.3.4.so
0000002a96991000    1024 ----- 000000000000a000 008:00003 libnss_files-2.3.4.so
0000002a96a91000       8 rw--- 000000000000a000 008:00003 libnss_files-2.3.4.so
0000002a96a93000      16 r-x-- 0000000000000000 008:00003 strscan.so
0000002a96a97000    1024 ----- 0000000000004000 008:00003 strscan.so
0000002a96b97000       4 rw--- 0000000000004000 008:00003 strscan.so
0000002a96b98000     240 r-x-- 0000000000000000 008:00003 nkf.so
0000002a96bd4000    1024 ----- 000000000003c000 008:00003 nkf.so
0000002a96cd4000      16 rw--- 000000000003c000 008:00003 nkf.so
0000002a96cd8000       4 rw--- 0000002a96cd8000 000:00000   [ anon ]
0000002a96cd9000      40 r-x-- 0000000000000000 008:00003 bigdecimal.so
0000002a96ce3000    1024 ----- 000000000000a000 008:00003 bigdecimal.so
0000002a96de3000       4 rw--- 000000000000a000 008:00003 bigdecimal.so
0000002a96de5000    7384 rw--- 0000002a96de5000 000:00000   [ anon ]
0000002a9751b000       4 r-x-- 0000000000000000 008:00003 md5.so
0000002a9751c000    1020 ----- 0000000000001000 008:00003 md5.so
0000002a9761b000       4 rw--- 0000000000000000 008:00003 md5.so
0000002a9761c000      16 r-x-- 0000000000000000 008:00003 cparse.so
0000002a97620000    1020 ----- 0000000000004000 008:00003 cparse.so
0000002a9771f000       4 rw--- 0000000000003000 008:00003 cparse.so
0000002a97720000      16 r-x-- 0000000000000000 008:00003 iconv.so
0000002a97724000    1020 ----- 0000000000004000 008:00003 iconv.so
0000002a97823000       4 rw--- 0000000000003000 008:00003 iconv.so
0000002a97824000   13292 rw--- 0000002a97824000 000:00000   [ anon ]
0000002a9851f000       4 r-x-- 0000000000000000 008:00003 sha1.so
0000002a98520000    1020 ----- 0000000000001000 008:00003 sha1.so
0000002a9861f000       4 rw--- 0000000000000000 008:00003 sha1.so
0000002a98620000   23924 rw--- 0000002a98620000 000:00000   [ anon ]
0000002a99d7d000     916 r-x-- 0000000000000000 008:00003 mysql.so
0000002a99e62000    1020 ----- 00000000000e5000 008:00003 mysql.so
0000002a99f61000    1036 rw--- 00000000000e4000 008:00003 mysql.so
0000002a9a064000       8 rw--- 0000002a9a064000 000:00000   [ anon ]
000000320ac00000      84 r-x-- 0000000000000000 008:00003 ld-2.3.4.so
000000320ad14000       8 rw--- 0000000000014000 008:00003 ld-2.3.4.so
000000320ae00000    1196 r-x-- 0000000000000000 008:00003 libc-2.3.4.so
000000320af2b000    1024 ----- 000000000012b000 008:00003 libc-2.3.4.so
000000320b02b000       8 r---- 000000000012b000 008:00003 libc-2.3.4.so
000000320b02d000      12 rw--- 000000000012d000 008:00003 libc-2.3.4.so
000000320b030000      16 rw--- 000000320b030000 000:00000   [ anon ]
000000320b100000       8 r-x-- 0000000000000000 008:00003 libdl-2.3.4.so
000000320b102000    1020 ----- 0000000000002000 008:00003 libdl-2.3.4.so
000000320b201000       8 rw--- 0000000000001000 008:00003 libdl-2.3.4.so
000000320b300000     532 r-x-- 0000000000000000 008:00003 libm-2.3.4.so
000000320b385000    1020 ----- 0000000000085000 008:00003 libm-2.3.4.so
000000320b484000       8 rw--- 0000000000084000 008:00003 libm-2.3.4.so
000000320b500000      76 r-x-- 0000000000000000 008:00003 libz.so.1.2.1.2
000000320b513000    1020 ----- 0000000000013000 008:00003 libz.so.1.2.1.2
000000320b612000       4 rw--- 0000000000012000 008:00003 libz.so.1.2.1.2
000000320b700000      68 r-x-- 0000000000000000 008:00003 libresolv-2.3.4.so
000000320b711000    1024 ----- 0000000000011000 008:00003 libresolv-2.3.4.so
000000320b811000       8 rw--- 0000000000011000 008:00003 libresolv-2.3.4.so
000000320b813000       8 rw--- 000000320b813000 000:00000   [ anon ]
000000320b900000      20 r-x-- 0000000000000000 008:00003 libcrypt-2.3.4.so
000000320b905000    1020 ----- 0000000000005000 008:00003 libcrypt-2.3.4.so
000000320ba04000       8 rw--- 0000000000004000 008:00003 libcrypt-2.3.4.so
000000320ba06000     184 rw--- 000000320ba06000 000:00000   [ anon ]
000000320bd00000      80 r-x-- 0000000000000000 008:00003 libnsl-2.3.4.so
000000320bd14000    1020 ----- 0000000000014000 008:00003 libnsl-2.3.4.so
000000320be13000       8 rw--- 0000000000013000 008:00003 libnsl-2.3.4.so
000000320be15000       8 rw--- 000000320be15000 000:00000   [ anon ]
000000320bf00000       8 r-x-- 0000000000000000 008:00003 libcom_err.so.2.1
000000320bf02000    1020 ----- 0000000000002000 008:00003 libcom_err.so.2.1
000000320c001000       4 rw--- 0000000000001000 008:00003 libcom_err.so.2.1
000000320c300000     436 r-x-- 0000000000000000 008:00003 libkrb5.so.3.2
000000320c36d000    1024 ----- 000000000006d000 008:00003 libkrb5.so.3.2
000000320c46d000      16 rw--- 000000000006d000 008:00003 libkrb5.so.3.2
000000320c500000    1080 r-x-- 0000000000000000 008:00003 libcrypto.so.0.9.7a
000000320c60e000    1024 ----- 000000000010e000 008:00003 libcrypto.so.0.9.7a
000000320c70e000     120 rw--- 000000000010e000 008:00003 libcrypto.so.0.9.7a
000000320c72c000      16 rw--- 000000320c72c000 000:00000   [ anon ]
000000320c800000     136 r-x-- 0000000000000000 008:00003 libk5crypto.so.3.0
000000320c822000    1020 ----- 0000000000022000 008:00003 libk5crypto.so.3.0
000000320c921000       8 rw--- 0000000000021000 008:00003 libk5crypto.so.3.0
000000320ca00000      84 r-x-- 0000000000000000 008:00003 libgssapi_krb5.so.2.2
000000320ca15000    1024 ----- 0000000000015000 008:00003 libgssapi_krb5.so.2.2
000000320cb15000       4 rw--- 0000000000015000 008:00003 libgssapi_krb5.so.2.2
000000320ce00000     216 r-x-- 0000000000000000 008:00003 libssl.so.0.9.7a
000000320ce36000    1024 ----- 0000000000036000 008:00003 libssl.so.0.9.7a
000000320cf36000      20 rw--- 0000000000036000 008:00003 libssl.so.0.9.7a
000000320cf3b000       4 rw--- 000000320cf3b000 000:00000   [ anon ]
0000007fbffa7000     356 rw--- 0000007fbffa7000 000:00000   [ stack ]
ffffffffff600000    8192 ----- 0000000000000000 000:00000   [ anon ]
mapped: 147272K    writeable/private: 98744K    shared: 0K
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Resident Size&lt;/h2&gt;

&lt;p&gt;The Resident Size (RES in 'top' output) for the process with PID 7659 is 99MB.
This is the net amount amount of memory consumed by the process.&lt;/p&gt;

&lt;h1&gt;The app&lt;/h1&gt;

&lt;p&gt;Whoa, that's a lot of memory! rake:stats ...&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
(in /Users/lourens/projects/bookings)
+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          |  5622 |  4351 |      99 |     604 |   6 |     5 |
| Helpers              |  2493 |  1909 |       0 |     467 |   0 |     2 |
| Models               |  5654 |  4067 |     209 |     685 |   3 |     3 |
| Libraries            |  2258 |  1476 |      42 |     210 |   5 |     5 |
| APIs                 |     0 |     0 |       0 |       0 |   0 |     0 |
| Components           |     0 |     0 |       0 |       0 |   0 |     0 |
| Model specs          | 14977 | 11275 |       0 |     131 |   0 |    84 |
| View specs           |     0 |     0 |       0 |       0 |   0 |     0 |
| Controller specs     | 24538 | 18954 |       0 |       0 |   0 |     0 |
| Helper specs         |     0 |     0 |       0 |       0 |   0 |     0 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                | 55542 | 42032 |     350 |    2097 |   5 |    18 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 11803     Test LOC: 30229     Code to Test Ratio: 1:2.6
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Memcached is used extensively.&lt;/p&gt;

&lt;h3&gt;My memory hogs&lt;/h3&gt;

&lt;p&gt;In addition to application code and stock Rails:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDF Writer and TZinfo gems&lt;/li&gt;
&lt;li&gt;Globalize ( 8 languages, 1400 translations per language, preloaded in config.after_initialize ) and another 20 odd plugins&lt;/li&gt;
&lt;li&gt;Per request &lt;a href='http://require.errtheblog.com/plugins/browser/cache_fu/lib/acts_as_cached/local_cache.rb'&gt;local cache&lt;/a&gt; for &lt;a href='http://require.errtheblog.com/plugins/browser/cache_fu'&gt;cache_fu&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Memory usage for remainder of the stack&lt;/h2&gt;

&lt;p&gt;Nginx, neglible:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
[root@somehost bookings]# ps aux | grep nginx
root     17280  0.0  0.0 18192  548 ?        Ss   Apr22   0:00 nginx: master process /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
nginx    17281  0.0  0.1 18948 2400 ?        S    Apr22  13:12 nginx: worker process                                          
nginx    17282  0.0  0.1 18828 2196 ?        S    Apr22  12:58 nginx: worker process                                          
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Mysql, pretty standard:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
[root@somehost bookings]# ps aux | grep mysql
root     10187  0.0  0.0 53840 1316 ?        S    May03   0:00 /bin/sh /usr/bin/mysqld_safe --datadir=/mysql/data --pid-file=/mysql/data/somehost.com.pid
mysql    10226  0.1  3.5 733792 73524 ?      Sl   May03  18:24 /usr/sbin/mysqld --basedir=/ --datadir=/mysql/data --user=mysql --pid-file=/mysql/data/somehost.com.pid --skip-external-locking --port=3306 --socket=/var/lib/mysql/mysql.sock
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;PHP:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
[root@somehost bookings]# ps aux | grep php
505       8352  0.0  0.0 58880 1172 ?        Ss   Mar28   0:00 /usr/local/bin/php
505       7597  0.8  0.6 71172 14304 ?       S    11:29   0:23 /usr/local/bin/php
505       7750  0.9  0.6 71160 14276 ?       S    11:54   0:13 /usr/local/bin/php
505       7754  0.9  0.6 71232 14272 ?       S    11:55   0:11 /usr/local/bin/php
505       7820  1.1  0.4 65284 8436 ?        S    12:06   0:07 /usr/local/bin/php
505       7823  1.0  0.6 71232 14288 ?       S    12:08   0:04 /usr/local/bin/php
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Memcached:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
[root@somehost bookings]# ps aux | grep memcached
502       5354  0.0  1.3 45784 28320 ?       Ss   May04   0:13 memcached -d -m 128 -p 11211 -u memcached
&lt;/code&gt;&lt;/pre&gt;

&lt;h1&gt;After a decent warmup&lt;/h1&gt;

&lt;p&gt;Snapshot of the Mongrels - 4000 requests (concurrency 8, 250 calls, rate of 2) across the Nginx/MySQL/Mongrel stack :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
[root@somehost bookings]# ps aux | grep mongrel
mongrel   7659  0.7  5.8 164704 120156 ?     S    11:35   1:20 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8000 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8000.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7662  0.7  5.8 164668 120128 ?     S    11:35   1:20 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8001 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8001.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7665  0.7  5.8 164728 120180 ?     S    11:35   1:20 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8002 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8002.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7668  0.8  5.8 165300 120668 ?     S    11:35   1:21 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8003 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8003.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7671  0.8  5.8 164636 120084 ?     S    11:35   1:20 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8004 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8004.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7674  0.8  5.8 164988 120400 ?     S    11:35   1:20 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8005 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8005.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7677  0.7  5.8 164772 120224 ?     S    11:35   1:20 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8006 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8006.pid -c /u/apps/bookings/current --user mongrel --group mongrel
mongrel   7680  0.8  5.8 164840 120188 ?     S    11:36   1:20 /usr/local/bin/ruby /usr/local/bin/mongrel_rails start -d -e production -p 8007 -a 127.0.0.1 -P /u/apps/bookings/current/log/mongrel.8007.pid -c /u/apps/bookings/current --user mongrel --group mongrel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Resident Size increased by 20MB to 24MB per process.&lt;/p&gt;

&lt;h3&gt;Why memory increases after warm up&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Any gems or libs referenced by your plugins, environment files and config.after_initialize blocks will be required on process startup.&lt;/li&gt;
&lt;li&gt;The Dependencies mechanism in Rails &gt;= 1.2 kicks in after the first request, during which most of your application code and gems/libs not required during initialization is loaded.&lt;/li&gt;
&lt;li&gt;String count in ObjectSpace will increase dramaticly: ERB templates/partials, routes, lib/file paths, generated SQL and Marshalled data if you're using a cache backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your process size after proper warmup should remain more or less constant (give or take for GC).&lt;/p&gt;

&lt;h1&gt;What to do if you suspect memory leaks&lt;/h1&gt;

&lt;h2&gt;Bleak House&lt;/h2&gt;

&lt;p&gt;An excellent &lt;a href='http://blog.evanweaver.com/articles/2007/04/28/bleak_house'&gt;tool&lt;/a&gt; for polling your production environment at set intervals.A pure C (custom binary) and ObjectSpace version is available.&lt;/p&gt;

&lt;h2&gt;Mongrel &quot;Dash-Bee&quot; logging&lt;/h2&gt;

&lt;p&gt;Start Mongrel with the '-B' switch.
This yields a few log files in RAILS&lt;em&gt;ROOT/log/mongrel&lt;/em&gt;debug, of which you should inspect the following :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;objects.log - A log of all the Objects currently in ObjectSpace.Diff this per request count and watch for a growing Delta.&lt;/li&gt;
&lt;li&gt;files.log - Lists open file descriptors.Useful if your application is IO bound.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Finding open file descriptors&lt;/h2&gt;

&lt;p&gt;'lsof' is a neat utility for listing open ports, sockets and files on a per process basis.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
[root@somehost bookings]# /usr/sbin/lsof -p 7659
COMMAND    PID    USER   FD   TYPE             DEVICE     SIZE     NODE NAME
mongrel_r 7659 mongrel  cwd    DIR                8,3     4096  1639306 /u/apps/bookings/releases/20070510100305
mongrel_r 7659 mongrel  rtd    DIR                8,3     4096        2 /
mongrel_r 7659 mongrel  txt    REG                8,3  2493404  1696180 /usr/local/bin/ruby
mongrel_r 7659 mongrel  mem    REG                8,3    32267  1926292 /usr/local/lib/ruby/1.8/x86_64-linux/digest.so
mongrel_r 7659 mongrel  mem    REG                8,3   832033  1926293 /usr/local/lib/ruby/1.8/x86_64-linux/openssl.so
mongrel_r 7659 mongrel  mem    REG                8,3    10544  1926290 /usr/local/lib/ruby/1.8/x86_64-linux/fcntl.so
mongrel_r 7659 mongrel  mem    REG                8,3    51414  1926305 /usr/local/lib/ruby/1.8/x86_64-linux/stringio.so
mongrel_r 7659 mongrel  mem    REG                8,3   341425  1926303 /usr/local/lib/ruby/1.8/x86_64-linux/syck.so
mongrel_r 7659 mongrel  mem    REG                8,3   119273  1926301 /usr/local/lib/ruby/1.8/x86_64-linux/socket.so
mongrel_r 7659 mongrel  mem    REG                8,3    74815  2330154 /usr/local/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/http11.so
mongrel_r 7659 mongrel  mem    REG                8,3    46134  1920694 /usr/local/lib/ruby/gems/1.8/gems/fastthread-1.0/lib/fastthread.so
mongrel_r 7659 mongrel  mem    REG                8,3    45517  1926304 /usr/local/lib/ruby/1.8/x86_64-linux/thread.so
mongrel_r 7659 mongrel  mem    REG                8,3   108943  1926307 /usr/local/lib/ruby/1.8/x86_64-linux/zlib.so
mongrel_r 7659 mongrel  mem    REG                8,3    24565  1926302 /usr/local/lib/ruby/1.8/x86_64-linux/etc.so
mongrel_r 7659 mongrel  mem    REG                8,3    56902  2588702 /lib64/libnss_files-2.3.4.so
mongrel_r 7659 mongrel  mem    REG                8,3    47492  1926298 /usr/local/lib/ruby/1.8/x86_64-linux/strscan.so
mongrel_r 7659 mongrel  mem    REG                8,3   424522  1926299 /usr/local/lib/ruby/1.8/x86_64-linux/nkf.so
mongrel_r 7659 mongrel  mem    REG                8,3   131340  1926308 /usr/local/lib/ruby/1.8/x86_64-linux/bigdecimal.so
mongrel_r 7659 mongrel  mem    REG                8,3    12623  1926296 /usr/local/lib/ruby/1.8/x86_64-linux/digest/md5.so
mongrel_r 7659 mongrel  mem    REG                8,3    37234  1926306 /usr/local/lib/ruby/1.8/x86_64-linux/racc/cparse.so
mongrel_r 7659 mongrel  mem    REG                8,3    40134  1926291 /usr/local/lib/ruby/1.8/x86_64-linux/iconv.so
mongrel_r 7659 mongrel  mem    REG                8,3    13615  1926295 /usr/local/lib/ruby/1.8/x86_64-linux/digest/sha1.so
mongrel_r 7659 mongrel  mem    REG                8,3  4756583  1920155 /usr/local/lib/ruby/site_ruby/1.8/x86_64-linux/mysql.so
mongrel_r 7659 mongrel  mem    REG                8,3    21546  1721087 /usr/lib64/gconv/gconv-modules.cache
mongrel_r 7659 mongrel  mem    REG                8,3    24492   359548 /home/mongrel/.ruby_inline/Inline_ImageScience_aa58.so
mongrel_r 7659 mongrel  mem    REG                8,3  1547144  1697450 /usr/lib/libfreeimage-3.9.3.so
mongrel_r 7659 mongrel  mem    REG                8,3    47496  2588909 /lib64/libgcc_s-3.4.6-20060404.so.1
mongrel_r 7659 mongrel  mem    REG                8,3   105080  2588770 /lib64/ld-2.3.4.so
mongrel_r 7659 mongrel  mem    REG                8,3  1493409  2588771 /lib64/tls/libc-2.3.4.so
mongrel_r 7659 mongrel  mem    REG                8,3    17943  2588904 /lib64/libdl-2.3.4.so
mongrel_r 7659 mongrel  mem    REG                8,3   613297  2588908 /lib64/tls/libm-2.3.4.so
mongrel_r 7659 mongrel  mem    REG                8,3    79336  1701661 /usr/lib64/libz.so.1.2.1.2
mongrel_r 7659 mongrel  mem    REG                8,3    91412  2588903 /lib64/libresolv-2.3.4.so
mongrel_r 7659 mongrel  mem    REG                8,3    30070  2588910 /lib64/libcrypt-2.3.4.so
mongrel_r 7659 mongrel  mem    REG                8,3   107187  2588726 /lib64/libnsl-2.3.4.so
mongrel_r 7659 mongrel  mem    REG                8,3    10384  2588902 /lib64/libcom_err.so.2.1
mongrel_r 7659 mongrel  mem    REG                8,3   464040  1701659 /usr/lib64/libkrb5.so.3.2
mongrel_r 7659 mongrel  mem    REG                8,3  1230232  2588905 /lib64/libcrypto.so.0.9.7a
mongrel_r 7659 mongrel  mem    REG                8,3   145456  1698080 /usr/lib64/libk5crypto.so.3.0
mongrel_r 7659 mongrel  mem    REG                8,3    93832  1701660 /usr/lib64/libgssapi_krb5.so.2.2
mongrel_r 7659 mongrel  mem    REG                8,3   910744  1697214 /usr/lib64/libstdc++.so.6.0.3
mongrel_r 7659 mongrel  mem    REG                8,3   244320  2588906 /lib64/libssl.so.0.9.7a
mongrel_r 7659 mongrel    0r   CHR                1,3              1982 /dev/null
mongrel_r 7659 mongrel    1w   REG                8,3   572768  1579794 /u/apps/bookings/shared/log/mongrel.log
mongrel_r 7659 mongrel    2w   REG                8,3   572768  1579794 /u/apps/bookings/shared/log/mongrel.log
mongrel_r 7659 mongrel    3u  IPv4           14667600               TCP localhost.localdomain:8000 (LISTEN)
mongrel_r 7659 mongrel    4w   REG                8,3 68750821  1579790 /u/apps/bookings/shared/log/production.log
mongrel_r 7659 mongrel    5u  IPv4           14667733               TCP localhost.localdomain:39863-&gt;localhost.localdomain:11211 (ESTABLISHED)
mongrel_r 7659 mongrel    6u  unix 0x0000010072f89380          14690356 socket
mongrel_r 7659 mongrel    7w   REG                8,3       57  1556510 /u/apps/bookings/shared/log/memory.log
mongrel_r 7659 mongrel    9r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   10r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   11r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   12r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   13r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   14r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   15r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   16r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   17r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   18r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   19r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   20r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   21r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   22r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   23r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   24r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   25r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   26r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   27r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   28r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   29r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   30r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   31r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   32r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   33r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   34r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   35r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   36r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   37r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   38r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   39r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   40r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   41r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   42r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   43r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   44r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   45r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   46r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   47r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   48r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   49r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   50r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   51r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   52r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   53r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   54r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   55r   DIR                8,3     4096  1823783 /u/apps/bookings/releases/20070510100305/public/stylesheets
mongrel_r 7659 mongrel   56r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   57r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   58r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   59r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   60r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   61r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   62r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   63r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   64r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   65r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   66r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   67r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   68r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   69r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
mongrel_r 7659 mongrel   70r   DIR                8,3     4096  2528355 /u/apps/bookings/releases/20070510100305/public/javascripts
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;MemoryProfiler&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://scottstuff.net/blog'&gt;Scott Laird&lt;/a&gt; and &lt;a href='http://www.zenspider.com/'&gt;Ryan Davis&lt;/a&gt; hacked up a memory leak &lt;a href='http://scottstuff.net/blog/articles/2006/08/17/memory-leak-profiling-with-rails'&gt;spotter&lt;/a&gt; with a very useful strings logger.
Rails plugin available &lt;a href='http://rails-memoryprofiler-plugin.tigris.org'&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;WeakRef&lt;/h1&gt;

&lt;p&gt;The &lt;a href='http://www.ruby-doc.org/stdlib/libdoc/weakref/rdoc/index.html'&gt;WeakRef&lt;/a&gt; Standard Library class can be used to clean up in tough spots where the GC doesn't clean up properly.In process caches etc. 
See &lt;a href='http://eigenclass.org/hiki/weakhash+and+weakref'&gt;WeakHash&lt;/a&gt; for in depth usage.&lt;/p&gt;

&lt;h1&gt;Custom logging&lt;/h1&gt;

&lt;p&gt;I use the following pattern ( credits to BleakHouse ) for logging without polluting the application with ActionController filters:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;class Dispatcher
  class &amp;lt;&amp;lt; self
    def reset_after_dispatch_with_globalize
      translator = Globalize::DbViewTranslator.instance     
      ActiveRecord::Base.logger.info &quot;Cache size: #{translator.cache_size}, Cache total hits: #{translator.cache_total_hits}, Cache total queries: #{translator.cache_total_queries}, Cache keys total: #{translator.instance_variable_get(:@cache).keys.size}&quot;
      reset_after_dispatch() 
    end
    alias_method_chain :reset_after_dispatch, :globalize

  end
 end&lt;/code&gt;&lt;/pre&gt;

&lt;h1&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;Rails is greedy on Memory, however :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If you spent the time developing and testing an application, why cut back on resources if a Dual Core box with 2GB RAM can be rented for $130 to $200 p/m at many respectable hosts?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Many opt for VPS solutions, which pretty much require at least some sysadmin skills ... is the time and monitoring spent on avoiding swap space for anything more than a blog really worth it?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I like the Container/Slice approach offered by &lt;a href='http://www.joyent.com/accelerator'&gt;Joyent&lt;/a&gt; and &lt;a href='http://www.engineyard.com/whatyouget'&gt;Engine Yard&lt;/a&gt; a lot from a administration and management perspective, but can't help feeling restricted by the memory offered ( Memcached? ).Adding an additional 1GB to a dedicated box will cost you $15 to $30 at most providers ... but in Container/Slice terms that's an additional container, worst case scenario.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I assume many large Rails applications have a Resident Size of 100MB to 130MB per process average, anyone care to chime in and share some production memory usage for others to have a baseline before loosing sleep over memory consumption?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2007-05-08:24</id>
    <published>2007-05-08T22:26:00Z</published>
    <updated>2007-05-08T23:31:04Z</updated>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2007/5/8/volatile-memoization-for-marshalled-ar-objects" rel="alternate" type="text/html"/>
    <title>Volatile memoization for Marshalled AR objects </title>
<content type="html">
            &lt;p&gt;The Ruby &lt;a href='http://rubyforge.org/projects/seattlerb/'&gt;memcache-client gem&lt;/a&gt; uses the &lt;a href='http://corelib.rubyonrails.org/classes/Marshal.html'&gt;Marshal&lt;/a&gt; library to serialize ruby objects for storage in &lt;a href='http://danga.com/memcached/'&gt;Memcached&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By default, all instance variables is marshalled as well, which pretty much defeats instance variable based memoization eg.&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;  def to_param; @to_param ||= &quot;#{self.id}-#{self.name.to_url}&quot; end &lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Usage scenario&lt;/h2&gt;

&lt;p&gt;An account based application where almost all Models belongs to an account:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;  class Account &amp;lt; ActiveRecord::Base
     acts_as_cached :version =&gt; 1, :include =&gt; [:subscription, :setting, :logo, :domain, :active_theme], :ttl =&gt; 1.day

     belongs_to :domain
     has_many :bookings, :class_name =&gt; 'PropertyBooking', :include =&gt; [:date, :customer], :foreign_key =&gt; :account_id, :dependent =&gt; :delete_all, :order =&gt; 'bookings.created_at DESC', :extend =&gt; BookingsExtension
    #define other relationships
  end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We set the account instance in a before filter for every request:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;  class AccountController &amp;lt; ApplicationController
     attr_accessor :account 
     before_filter :account_from_subdomain

     protected

      def account_from_subdomain   
         @account = Account.get_cache([account_subdomain(),request.domain].join('.'))
         if @account.nil?
           account_not_found  
         else
          @account.disabled? ? account_disabled() : account_found()
        end    
     end         

  end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;From time to time we need to reference the account belongs_to association to load/query other data scoped to the particular account: &lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;  class Booking &amp;lt; ActiveRecord::Base
    #associations etc.

    #verify subscription limit before saving another booking
    def before_create; self.account.subscription.bookings_this_cycle? end 

  end&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Headaches&lt;/h2&gt;

&lt;p&gt;Simply caching an account alongside a child Model forces us to implement excessive sweepers to preserve data integrity and also duplicates data, which isn't desireable:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;class Booking &amp;lt; ActiveRecord::Base
  #cache account alongside this model  
   acts_as_cached :version =&gt; 1, :include =&gt; [:account, :date, :progress, :payment], :ttl =&gt; 2.hours

end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We can override the reader/accessor method, but this results in excessive Memcache lookups if the related model is referenced multiple times per request:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt; class Booking &amp;lt; ActiveRecord::Base
  #do not cache the account alongside this model
  acts_as_cached :version =&gt; 1, :include =&gt; [:date, :progress, :payment], :ttl =&gt; 2.hours

  #potential multiple lookups
  def account; Account.get_cache(account_id) unless account_id.nil? end
end &lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;The solution&lt;/h2&gt;

&lt;p&gt;We can use the after_initialize callback to clear an instance variable used for memoization:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt; class Booking &amp;lt; ActiveRecord::Base
  #do not cache the account alongside this model
  acts_as_cached :version =&gt; 1, :include =&gt; [:date, :progress, :payment], :ttl =&gt; 2.hours

  #memoization, only called once per lifecycle/request
  def account; @cached_account ||= Account.get_cache(account_id) unless account_id.nil? end

  #AR callback
  def after_initialize; @cached_account = nil end

end &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In theory either after&lt;em&gt;initialize OR after&lt;/em&gt;find can be used to accomplish this.&lt;/p&gt;

&lt;p&gt;You need to ensure your cache backend reference these callbacks to mimick and replicate the standard ActiveRecord behaviour.&lt;/p&gt;

&lt;p&gt;I use the following snippet for &lt;a href='http://require.errtheblog.com/plugins/browser/cache_fu'&gt;cache_fu&lt;/a&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;module ActsAsCached
   module ClassMethods

       def fetch_cache(id)
         return if ActsAsCached.config[:skip_gets]

           autoload_missing_constants do 
              data = cache_store(:get, cache_key(id))
              [:after_initialize, :after_find].each{|c| data.send(c) if data.respond_to? c }
              data
          end
      end
   end
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;memcache-client can be monkey patched if you rolled your own store using the CACHE.get API method: &lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;class MemCache
  def get(key, raw = false)
    server, cache_key = request_setup key

    value = if @multithread then
              threadsafe_cache_get server, cache_key
            else
              cache_get server, cache_key
            end

    return nil if value.nil?

    unless raw
       value = Marshal.load value     
       [:after_initialize, :after_find].each{|c| value.send(c) if value.respond_to? c } 
    end

    return value
  rescue TypeError, SocketError, SystemCallError, IOError =&gt; err
    handle_error server, err
  end
end&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Alternatives&lt;/h2&gt;

&lt;p&gt;The Marshal library define callbacks for more complex marshalling:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;  class SomeModel &amp;lt; ActiveRecord::Base

     #prepare for serialization
     def marshal_dump
     end

     #do something when deserialized
     def marshal_load( serialized_string )
     end

  end&lt;/code&gt;&lt;/pre&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2007-04-24:22</id>
    <published>2007-04-24T00:57:00Z</published>
    <updated>2007-04-24T01:58:38Z</updated>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2007/4/24/partially-bypass-activerecord-instantiation-when-using-memcached" rel="alternate" type="text/html"/>
    <title>Partially bypass ActiveRecord instantiation when using Memcached</title>
<content type="html">
            &lt;p&gt;With the recent &lt;a href='http://www.loudthinking.com/arc/000608.html'&gt;scaling&lt;/a&gt; &lt;a href='http://www.radicalbehavior.com/5-question-interview-with-twitter-developer-alex-payne/'&gt;woes&lt;/a&gt; and Blaine Cook's &lt;a href='http://www.slideshare.net/Blaine/scaling-twitter/'&gt;Scaling Twitter&lt;/a&gt; talk, I think slide 23 is a welcome, and bold, statement.&lt;/p&gt;

&lt;p&gt;Memcahed is generally used as an intermediate cache to lessen database load, and especially more crucial when using ORM ( eg. ActiveRecord ) as avoiding/bypassing object instantiation will give you additional mileage in the App tier as well.&lt;/p&gt;

&lt;h2&gt;The context:&lt;/h2&gt;

&lt;p&gt;Three example models: Account, Customer and Booking ( some omitted for simplicity ).&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;class Account &amp;lt; ActiveRecord::Base
  acts_as_cached :version =&gt; 1, :include =&gt; [:omitted], :ttl =&gt; 2.hours

  has_many :customers
  has_many :bookings
  has_many :contacts
  has_many :addresses
end

class Customer &amp;lt; ActiveRecord::Base
  acts_as_cached :version =&gt; 1, :include =&gt; [:address, :contact, :bookings, :account], :ttl =&gt; 2.hours 

  belongs_to :account
  has_one :address, :as =&gt; :addressable, :dependent =&gt; :destroy  
  has_one :contact, :as =&gt; :contactable, :dependent =&gt; :destroy
end

class Booking &amp;lt; ActiveRecord::Base
  acts_as_cached :version =&gt; 1, :include =&gt; [:omitted], :ttl =&gt; 2.hours[:date, :progress, :user, :customer, :events, :booking_extras, :booking_products, :notes, :payment, :account, :coupon], :ttl =&gt; 2.hours

  belongs_to :customer
end&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;The problem&lt;/h2&gt;

&lt;p&gt;We are caching the Customer model, with its direct and most often used associations (Contact, Address and Bookings) and would like to maintain this integrity, without instantiating additional AR objects and related associations when referencing all Customers or Bookings for a given Account:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;  account.customers.to_a #or account.customers(true)
  account.bookings.to_a #or account.bookings(true)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;However, Rails do have an association proxy reader method:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;   account.customer_ids # =&gt; [1]
  account.booking_ids # =&gt; [6, 1, 5, 2, 3]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;... which isn't very helpful if we'd like a specifc subset of customer or booking identifiers.&lt;/p&gt;

&lt;h2&gt;The solution&lt;/h2&gt;

&lt;p&gt;Extend ActiveRecord::Base with a find_ids singleton method, with the exact same usage as AR::Base#find, but never instantiates any objects.We only fetch the ID's from the raw connection:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;module ActiveRecord 
  class Base  

   class &amp;lt;&amp;lt; self
    def find_ids(*args)
      options = extract_options_from_args!(args)
      logger.debug(&quot;Find by ID:&quot; + options.inspect)
      validate_find_options(options)      

      case args.first
        when :first then find_initial_id(options)
        when :all   then find_every_id(options)
      end
    end  

    def find_by_sql_ids(sql)
      connection.select_all(sanitize_sql(sql), &quot;#{name} Load&quot;).collect! { |record| record['id'] }
    end    

    private

    def find_initial_id(options)
      options.update(:limit =&gt; 1) unless options[:include]
      find_every_id(options).first
    end

    def find_every_id(options)
      records = scoped?(:find, :include) || options[:include] ?
        find_with_associations_ids(options) : 
        find_by_sql_ids(construct_finder_sql(options))
      records
    end
    end        
  end  
end

module ActiveRecord
  module Associations
    module ClassMethods
      def find_with_associations_ids(options = {})
        catch :invalid_query do
          join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
          logger.debug(&quot;All rows: &quot; + select_all_rows(options, join_dependency).inspect)
          return select_all_rows(options, join_dependency).collect { |row| row[join_dependency.joins.first.aliased_primary_key] }
        end
        []
      end
    end  
  end
end&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Usage examples&lt;/h2&gt;

&lt;p&gt;Following is an association extension that illustrates compatibility with AR::Base#find :&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;module BookingsExtension
  def upcoming( page = 1) self.find_ids(:all, default_find_options(page)) end

  def recent( page = 1 ) self.find_ids(:all, default_find_options(page).merge!({:order =&gt; 'booking_dates.date_from DESC'})) end

  def by_status(status = 'pending', page = 1)
    self.find_ids(:all, default_find_options(page).merge!(:conditions =&gt; ['bookings.status = ?', status]))     
  end

    def since(date = Time.now.utc, page = 1)
    self.find_ids(:all, default_find_options(page).merge!(:conditions =&gt; [&quot;bookings.status != ? AND booking_dates.date_from &gt;= ?&quot;, 'in_progress', date.to_s(:db)]))      
  end  

  def until(date = Time.now.utc, page = 1)
    self.find_ids(:all, default_find_options(page).merge!(:conditions =&gt; [&quot;bookings.status != ? AND booking_dates.date_from &amp;lt;= ?&quot;, 'in_progress', date.to_s(:db)]))        
  end

  def by_user(user, page = 1)
    self.find_ids(:all, default_find_options(page).merge!(:conditions =&gt; [&quot;bookings.status != ? AND bookings.user_id = ?&quot;,'in_progress',user.id]))
  end

  def by_reference( reference, page = 1 )
    self.find_ids(:all, default_find_options(page).merge!(:conditions =&gt; [&quot;bookings.status != ? AND bookings.reference = ?&quot;,'in_progress',reference]))
  end  

  def default_find_options(page)
    { :include =&gt; [:customer, :date], :conditions =&gt; ['bookings.status != ?', 'in_progress'], :order =&gt; 'bookings.created_at DESC', :page =&gt; { :size =&gt; 10, :current =&gt; page, :first =&gt; 1 } }
  end
end&amp;lt;/code&gt;&amp;lt;/pre&gt;

&amp;lt;h2&gt;Standalone examples:&amp;lt;/h2&gt;

&amp;lt;pre&gt;&amp;lt;code class=&quot;ruby&quot;&gt;  account.bookings.by_status(:pending).to_a  # =&gt; [&quot;2&quot;, &quot;1&quot;]

  account.bookings.by_user( User.get_cache(1) ).to_a # =&gt; [&quot;2&quot;, &quot;1&quot;, &quot;3&quot;]&amp;lt;/code&gt;&amp;lt;/pre&gt;

&amp;lt;h2&gt;Memcached friendly examples&amp;lt;/h2&gt;

&amp;lt;p&gt;I use &amp;lt;a href=&quot;http://require.errtheblog.com/plugins/browser/cache_fu&quot;&gt;cache&amp;lt;em&gt;fu&amp;lt;/a&gt; to interface with memcache-client.The &amp;lt;a href=&quot;http://require.errtheblog.com/plugins/browser/cache_fu/defaults/extensions.rb.default&quot;&gt;multi&amp;lt;/em&gt;get_cache&amp;lt;/a&gt; extension method is particularly useful here:&amp;lt;/p&gt;

&amp;lt;pre&gt;&amp;lt;code class=&quot;ruby&quot;&gt;  Booking.multi_get_cache( [&quot;2&quot;, &quot;1&quot;, &quot;3&quot;] ) # =&gt; lots of output&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the above example we are attempting to fetch Bookings with ID 1..3 from Memcached.Should the objects already be cached, we only had the DB overhead of 1 relatively cheap query while maintaining cache integrity without duplicating any processing or data.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The above is a slight anti-pattern, which true the 90/10 principle, would only ever be useful to those users of the framework with Memcached in their production stack.&lt;/p&gt;

&lt;p&gt;It's OK to break free from constraints, religious DRY development that may shoot you in the foot later and even denormalize, as per Blaine's slide number 23, if and when the framework doesn't natively solve your problem ( performance? design constraints? production environment? ) at hand.&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2007-01-11:16</id>
    <published>2007-01-11T02:39:00Z</published>
    <updated>2007-01-11T03:17:12Z</updated>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2007/1/11/dry-association-sweeping-with-acts_as_cached" rel="alternate" type="text/html"/>
    <title>DRY association sweeping with acts_as_cached</title>
<content type="html">
            &lt;p&gt;Cherry picking exactly which models and associations to cache with &lt;a href='http://errtheblog.com/post/27'&gt;Acts as Cached&lt;/a&gt; is dead simple.However, cached object associations quickly result in repetitive sweeping snippets.&lt;/p&gt;

&lt;h2&gt;How to cache associations&lt;/h2&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;class Address &amp;lt; ActiveRecord::Base

  acts_as_cached :version =&gt; 1, :include =&gt; [:country] 
  belongs_to :addressable, :polymorphic =&gt; true #we can't do find :include =&gt; X with polymorhic associations
  belongs_to :country, :class_name =&gt; 'Globalize::Country', :foreign_key =&gt; 'country_id'

end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The above snippet will execute the following behind the scenes:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;Address.get_cache(1) #Address.find(1, :include =&gt; [ :country ])&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;How to sweep associations&lt;/h2&gt;

&lt;h3&gt;Overriding accessors&lt;/h3&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;  class Address &amp;lt; ActiveRecord::Base

    def country
       Globalize::Country.get_cache( country_id ) unless country_id.nil?
    end

  end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Just works, always fresh but has the overhead of a ridiculous amount of CACHE.get lookups&lt;/p&gt;

&lt;h3&gt;Manually sweeping&lt;/h3&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;  class Address &amp;lt; ActiveRecord::Base

    after_save :cache_sweeper
    after_destroy :cache_sweeper

    def cache_sweeper
       expire_cache
       country.expire_cache
    end

  end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;  class Globalize::Country &amp;lt; ActiveRecord::Base

    has_many :addresses, :as =&gt; :addressable

    after_save :cache_sweeper
    after_destroy :cache_sweeper

    def cache_sweeper
       expire_cache
       addresses.each{|a| a.expire_cache }
    end

  end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Very bloated for complex relationships.&lt;/p&gt;

&lt;h2&gt;But there's a better way&lt;/h2&gt;

&lt;p&gt;Add the following to acts&lt;em&gt;as_cached's InstanceMethods or Monkey Patch/module&lt;/em&gt;eval in your environment.&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;         def expire_cache_with_associations( *associations_to_sweep )
          ((self.class.cache_options[:include] || []) + associations_to_sweep).flatten.uniq.compact.each do |assoc|
            macro = self.class.reflect_on_association(assoc.to_sym).macro
            macro == :has_many ? self.send(assoc.to_sym).each{|r| r.expire_cache unless r.nil? } : self.send(assoc.to_sym).expire_cache unless self.send(assoc.to_sym).nil?
          end 
          expire_cache
        end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This snippet will sweep associations passed in the :includes config hash option, but also allows for additional associations to be merged in later on.&lt;/p&gt;

&lt;h2&gt;A DRY and refactored example&lt;/h2&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;  class Address &amp;lt; ActiveRecord::Base
    acts_as_cached :version =&gt; 2, :include =&gt; [:country]

    #sweeps country    
    after_save :expire_cache_with_associations
    after_destroy :expire_cache_with_associations

  end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;  class Globalize::Country &amp;lt; ActiveRecord::Base
    acts_as_cached :version =&gt; 1
    has_many :addresses, :as =&gt; :addressable

    after_save :cache_sweeper
    after_destroy :cache_sweeper

    #We don't cache all addresses with the country as one country can grow to a potentially
    #large number of adresses ... we just sweep to update the cached country for each address
    def cache_sweeper;  expire_cache_with_associations(:addresses) end

  end&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Sweeping polymorphic associations&lt;/h2&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;class Address &amp;lt; ActiveRecord::Base
  acts_as_cached :version =&gt; 1, :include =&gt; [:country] 
  belongs_to :addressable, :polymorphic =&gt; true 
  belongs_to :country, :class_name =&gt; 'Globalize::Country', :foreign_key =&gt; 'country_id'

  after_save     :cache_sweeper
  before_destroy :cache_sweeper

  protected  

  #sweep the related addressable entitiy
  def cache_sweeper; expire_cache_with_associations(:addressable) end

end

class Customer &amp;lt; ActiveRecord::Base
  acts_as_cached :version =&gt; 1, :include =&gt; [:address, :contact] 

  has_one :address, :as =&gt; :addressable, :dependent =&gt; :destroy  
  has_one :contact, :as =&gt; :contactable, :dependent =&gt; :destroy

  #nothing fancy here
  after_save     :expire_cache_with_associations
  before_destroy :expire_cache_with_associations
end  

class Contact &amp;lt; ActiveRecord::Base
  acts_as_cached :version =&gt; 1 
  belongs_to :contactable, :polymorphic =&gt; true

  after_save     :cache_sweeper
  before_destroy :cache_sweeper

  protected

  #sweep related contactable entitiy
  def cache_sweeper; expire_cache_with_associations(:contactable) end

end   &lt;/code&gt;&lt;/pre&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2006-12-27:15</id>
    <published>2006-12-27T05:00:00Z</published>
    <updated>2006-12-27T05:10:15Z</updated>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2006/12/27/unobtrusive-around-filter-profile-snippet" rel="alternate" type="text/html"/>
    <title>Unobtrusive around filter profile snippet</title>
<content type="html">
            &lt;p&gt;Drop into environment.rb or other required library.&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;require 'profiler'

class ProfilingFilter

  def before( controller )
    Profiler__::start_profile 
  end

  def after( controller )
    Profiler__::stop_profile
    Profiler__::print_profile(File::new(&quot;#{RAILS_ROOT}/log/profiler.log&quot;, 'w'))  
  end

end&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Usage&lt;/h2&gt;

&lt;p&gt;Something hogs?&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;class SomeController &amp;lt; ApplicationController
  around_filter ProfilingFilter.new, :only =&gt; :slow_action

  def slow_action; end

  def speedy; end

  def zippy; end

end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Output dumped to log/profiler.log&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2006-12-27:14</id>
    <published>2006-12-27T04:43:00Z</published>
    <updated>2006-12-27T04:54:43Z</updated>
    <category term="Globalize"/>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2006/12/27/preloading-globalize-view-translations-in-production" rel="alternate" type="text/html"/>
    <title>Preloading Globalize view translations in production</title>
<content type="html">
            &lt;p&gt;When you've properly extracted and translated view translations from your source code, here's a snippet to preload them all before the first request served in Production.&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;Dispatcher.to_prepare :globalize_view_translations do
  if RAILS_ENV == 'production' 
    translator = Globalize::DbViewTranslator.instance
      #Or whichever other means used to keep track of app specific supported languages 
      SupportedLanguage.find(:all).each do |lang|
        ViewTranslation.find(:all, :conditions =&gt; ['globalize_translations.language_id = ?', lang.language_id]).each do |tr|
          translator.send(:cache_add, tr.tr_key, tr.language, tr.pluralization_index, tr.text)
        end  
    end  
  end  
end&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Dispatcher.to_prepare&lt;/h2&gt;

&lt;p&gt;An addition to 1.2 and Edge for quite some time.This callback is referenced&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;before the first request in Production&lt;/li&gt;
&lt;li&gt;before each request in Development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most notably initializes Observer instances&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2006-12-22:13</id>
    <published>2006-12-22T18:07:00Z</published>
    <updated>2006-12-22T18:20:51Z</updated>
    <category term="Globalize"/>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2006/12/22/globalize-dbtranslate-with-include-and-no-base-language-patch" rel="alternate" type="text/html"/>
    <title>Globalize::DbTranslate with :include and no base language patch</title>
<content type="html">
            &lt;p&gt;&lt;a href='http://trac.globalize-rails.org/trac/globalize/wiki'&gt;Globalize&lt;/a&gt; DB translations currently doesn't support a :select find option, which breaks has&lt;em&gt;many :trough =&gt; a&lt;/em&gt;translateable_model associations.&lt;/p&gt;

&lt;p&gt;I've refactored the Globalize::DbTranslate module to an AR::Associations pattern, override Model.find to always preload translations whilst removing all of the custom finder SQL.&lt;/p&gt;

&lt;h2&gt;Benefits&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Supports all of the ActiveRecord::Base#find functionality&lt;/li&gt;
&lt;li&gt;Plugs in with existing globalize table structures (no migrations required)&lt;/li&gt;
&lt;li&gt;There is no Base Locale.Edit or create in any language, any other language always retrieves a valid facet, albeit not always in the active Locale.&lt;/li&gt;
&lt;li&gt;Retrieves all facet translations for a given model at once, thus Locale switching doesn't incur a database roundtrip overhead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Caveats&lt;/h2&gt;

&lt;p&gt;I removed a tiny bit of functionality to allow me to focus on a initial prototype.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bidi strings temp. removed&lt;/li&gt;
&lt;li&gt;Removed piggy backing find option &lt;/li&gt;
&lt;li&gt;Works best with only one Locale per request, which is fine in most situations.&lt;/li&gt;
&lt;li&gt;The no Base Locale setup might not be ideal for all use cases.&lt;/li&gt;
&lt;li&gt;Only tested with Edge (production, test and development envs)&lt;/li&gt;
&lt;li&gt;Globalize unit test helper chokes with the Dependencies mechanism and I couldn't yet get the unit tests to run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;The rundown&lt;/h2&gt;

&lt;h3&gt;AR association and Translateable.find&lt;/h3&gt;

&lt;p&gt;Setup an association in the translates() hook:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;          has_many :translations, :class_name =&gt; 'Globalize::ModelTranslation', :conditions =&gt; ['globalize_translations.table_name = ?', table_name], :foreign_key =&gt; 'item_id'  &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Override Translateable.find, scoped to always prefetch translations:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;   alias_method :old_find, :find
   def find(*args)
      options = args.last.is_a?(Hash) ? args.last : {}
      if (options.has_key?(:untranslated) &amp;&amp; options[:untranslated] == true)
          old_find(*args)
      else
         with_scope( :find =&gt; { :include =&gt; :translations }) do
            old_find(*args)
         end
       end  
    end    &lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Cherry pick facet content&lt;/h3&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;     def find_translation_for_facet( facet )
         translations ||= []
         #highest priority, find for current language
         translations &amp;lt;&amp;lt; self.translations.detect{|tr| tr.facet == facet.to_s &amp;&amp; tr.language_id == Locale.language.id }
         #do we have a non-blank attribute in the model table?
         translations &amp;lt;&amp;lt; read_attribute(facet)
         #find the first translation, for any language
         translations &amp;lt;&amp;lt; self.translations.detect{|tr| tr.facet == facet.to_s &amp;&amp; !tr.text.nil? }

         #we don't want nil or empty string
         translations.reject!(&amp;:blank?)
         translations.empty? ? nil : (translations.first.respond_to?(:text) ? translations.first.text : translations.first)                          
      end  &lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;The patch&lt;/h2&gt;

&lt;p&gt;Grab this &lt;a href='http://blog.methodmissing.com/assets/2006/12/22/globalize_with_include_no_base_language.diff'&gt;patch&lt;/a&gt; and give it a whirl.&lt;/p&gt;

&lt;p&gt;Feedback on this? Performance implications? Backwards compatibility?&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2006-11-23:9</id>
    <published>2006-11-23T23:16:00Z</published>
    <updated>2006-11-23T23:46:07Z</updated>
    <category term="Rails"/>
    <category term="rspec"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2006/11/23/using-simply_helpful-with-rspec-controller-tests" rel="alternate" type="text/html"/>
    <title>Using simply_helpful with rspec controller tests</title>
<content type="html">
            &lt;p&gt;&lt;a href='http://rspec.rubyforge.org'&gt;Rspec&lt;/a&gt; currently doesn't play well on Edge Rails with the &lt;a href='http://dev.rubyonrails.org/browser/plugins/simply_helpful'&gt;simply_helpful&lt;/a&gt; plugin.&lt;/p&gt;

&lt;h2&gt;The cause&lt;/h2&gt;

&lt;p&gt;simply_helpful plugin hook (init.rb):&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;require 'simply_helpful'
ActionController::Base.send :include, SimplyHelpful::RecordIdentificationHelper
ActionController::Base.helper SimplyHelpful::RecordIdentificationHelper, SimplyHelpful::RecordTagHelper&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Rspec's ActionController::Base definition (lib/extensions/action_controller/base.rb):&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;module ActionController
  class Base
  end
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ActionController::Base is stripped from all modules included with the &lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;ActionController::Base.send :include, SomeModule&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;syntax.&lt;/p&gt;

&lt;h2&gt;Monkey patch to fix&lt;/h2&gt;

&lt;p&gt;Include in spec/spec_helper.rb, before your Test::Rails::TestCase definition:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;module Spec
  module Rails
    class ControllerContext &amp;lt; Rails::Context
        module ContextEvalInstanceMethods
          def setup_extra
            (class &amp;lt;&amp;lt; @controller; self; end).class_eval do
              include ControllerInstanceMethods
            end
            @controller.integrate_views! if @integrate_views
            @controller.session = session

            #extend the controller instance with simply_helpful modules
            @controller.extend SimplyHelpful::RecordIdentificationHelper
            @controller.class.helper SimplyHelpful::RecordIdentificationHelper, SimplyHelpful::RecordTagHelper
          end          
        end        
    end
  end
end    &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Running controller/view specifications using simply&lt;em&gt;helpful helper methods, such as dom&lt;/em&gt;id(), should not raise NoMethodErrors anymore.&lt;/p&gt;

&lt;h2&gt;Inline RJS in respond_to blocks&lt;/h2&gt;

&lt;p&gt;This snippet chokes (see comments) when referenced from a controller specification in &lt;em&gt;isolation&lt;/em&gt; mode:&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt; #@language is set in a before_filter
  def create
    @account.supported_languages &amp;lt;&amp;lt; @language

    #@language != nil
    respond_to do |format|
      format.js { #@language != nil 
                  render :update do |page|
                    #@language is nil
                    page.replace( dom_id( @language ), :partial =&gt; 'shared/admin/language', :locals =&gt; { :language =&gt; @language  } )
                    page.visual_effect :highlight, dom_id( @language )
                    page.call('Marmalade.Messaging.flash', 'success', &quot;Added language %s&quot; / @language.to_s)
                  end }
      format.xml { @account.languages.to_xml }
    end
  end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Explicitly integrating views with &lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt; integrate_views&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;properly instantiates a JavaScriptGenerator object, thus preserving controller instance variables within the render :update block.&lt;/p&gt;

&lt;p&gt;Any thoughts on this behaviour, or is inline RJS in respond_to blocks generally frowned upon?&lt;/p&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2006-11-22:8</id>
    <published>2006-11-22T03:19:00Z</published>
    <updated>2006-11-22T03:41:20Z</updated>
    <category term="Rails"/>
    <category term="rspec"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2006/11/22/testing-different-content-types-with-rspec" rel="alternate" type="text/html"/>
    <title>Testing different content types with rspec</title>
<content type="html">
            &lt;p&gt;&lt;a href='http://rspec.rubyforge.org'&gt;Rspec&lt;/a&gt; ( rather it's dependency &lt;a href='http://www.zenspider.com/ZSS/Products/ZenTest/'&gt;ZenTest&lt;/a&gt; ) does not currently support content type headers for Controller testing using the Rspec on Rails plugin.&lt;/p&gt;

&lt;h2&gt;Monkey patch Test::Rails::ControllerTestCase&lt;/h2&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;class Test::Rails::ControllerTestCase

  %w(get put post head delete).each{|m| define_method(m.to_sym) do |*args|
      @request.env['REQUEST_METHOD'] = m.upcase
      @request.env['CONTENT_TYPE'] = Mime::EXTENSION_LOOKUP[(args[2].nil? ? 'html' : args[2].to_s )]
      process( args[0], args[1] || {} )  
    end
  }   

end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Add the above to your Spec helper (spec/spec_helper.rb) after&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;require 'rspec_on_rails'&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and before your Test::Rails::Testcase definition.&lt;/p&gt;

&lt;h2&gt;Examples&lt;/h2&gt;

&lt;p&gt;For the sake of saving keystrokes and taking the ability to add your own custom MIME Type extensions in consideration, pass the MIME Type extension (xml, html, js, rss, atom, yaml etc.) as the third argument.Defaults to html if not given.&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;  get :index, {:locale =&gt; 'en'}           #requests HTML content
  get :index, {:locale =&gt; 'en'}, :html #requests HTML content
  get :index, {:locale =&gt; 'en'}, :xml  #requests XML content
  get :index, {:locale =&gt; 'en'}, :js     #requests Javascript content&lt;/code&gt;&lt;/pre&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2006-11-21:7</id>
    <published>2006-11-21T05:27:00Z</published>
    <updated>2006-11-21T06:03:38Z</updated>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2006/11/21/using-the-color-tools-gem-for-theme-support" rel="alternate" type="text/html"/>
    <title>Using the color-tools gem for theme support</title>
<content type="html">
            &lt;p&gt;When offering branding options to your customers/users, neither a cusomizable CSS stylesheet nor &lt;a href='http://home.leetsoft.com/liquid/'&gt;Liquid&lt;/a&gt; is unobtrusive from an integration or usability perspective.&lt;/p&gt;

&lt;h2&gt;Fall back to your design roots&lt;/h2&gt;

&lt;p&gt;A standard 3 color palette composed of a&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dominant color &lt;/li&gt;
&lt;li&gt;interactive color ( complement to your dominant color scheme )&lt;/li&gt;
&lt;li&gt;tonic color ( highlights and accents )  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;requires exactly 3 inputs from your customer/user from which a gradient based scheme can be generated server side for each of the above colors.&lt;/p&gt;

&lt;h2&gt;Requirements&lt;/h2&gt;

&lt;p&gt;Austin Ziegler's color-tools gem ( bundled as a dependency when installing pdf-writer ) already implements most of the required functionality.&lt;/p&gt;

&lt;p&gt;Install with&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;sudo gem install color-tools&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Parsing hexadecimal (HTML) color values&lt;/h2&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;module Format
  EMAIL = /^[_a-z0-9\+\.\-]+\@[_a-z0-9\-]+\.[_a-z0-9\.\-]+$/i
  HTTP_URI = /^https?:\/\/\S+$/
  HEX_COLOR = /(#)([a-fA-F0-9][a-fA-F0-9])([a-fA-F0-9][a-fA-F0-9])([a-fA-F0-9][a-fA-F0-9])/
end&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Format::HEX_COLOR should match the 6 and 7 digit hex color variations eg. #CCCCCC and CCCCCC&lt;/p&gt;

&lt;h2&gt;The Theme model&lt;/h2&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;class Theme &amp;lt; ActiveRecord::Base
  set_table_name 'themes'

  belongs_to :account
  validates_presence_of :title, :dominant_color, :interactive_color, :tonic_color
  validates_format_of :dominant_color, :with =&gt; Format::HEX_COLOR, :message =&gt; ' is not a valid HEX color.'.t
  validates_format_of :interactive_color, :with =&gt; Format::HEX_COLOR , :message =&gt; ' is not a valid HEX color.'.t
  validates_format_of :tonic_color, :with =&gt; Format::HEX_COLOR , :message =&gt; ' is not a valid HEX color.'.t
  validates_uniqueness_of :title, :scope =&gt; 'account_id'
  validates_presence_of :active, :in =&gt; 0..1

  serialize :dominant, Array
  serialize :interactive, Array
  serialize :tonic, Array

  delegate :to_s, :to =&gt; :title

  class &amp;lt;&amp;lt; self 

    def suggested_colors
      Color::RGB::constants.reject{|c| %w(PDF_FORMAT_STR Metallic).include? c }.collect!{|c| eval(&quot;Color::RGB::#{c}&quot;).html}   
    end

    def color_to_palette( color )
      Color::Palette::MonoContrast.new( Color::RGB.from_html( color ) ).background.to_a.collect!{|c| [c.first, c.last.html] }
    end

  end

  def to_param; &quot;#{self.id}-#{self.title.to_url}&quot; end

  def generate_color_scheme!
    %w(dominant interactive tonic).each do |c|
      self.send(&quot;#{c}=&quot;.to_sym,  Theme.color_to_palette( self.send(&quot;#{c}_color&quot;) ) )
    end
  end

  def active?() self.active == 1 end

  def activate!
    return if self.active?
    Theme.transaction do
      self.account.themes.update_all(&quot;themes.active = '0'&quot;)
      self.update_attribute(:active, '1')
    end
  end

  def before_destroy; !self.active? end 

end&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Theme observer&lt;/h2&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;class ThemeObserver &amp;lt; ActiveRecord::Observer

  def before_validation( theme )
    theme.active = 0 unless theme.attribute_present?(:active)
  end

  def before_save( theme ) theme.generate_color_scheme! end

end&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Console examples&lt;/h2&gt;

&lt;p&gt;Shamelessly rip these common colors from color-tools.&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;&gt;&gt; Theme.suggested_colors
=&gt; [&quot;#cd853f&quot;, &quot;#f8f8ff&quot;, &quot;#333333&quot;, &quot;#ffffe0&quot;, &quot;#dc143c&quot;, &quot;#a0522d&quot;, &quot;#f0e68c&quot;, &quot;#e6e6e6&quot;, &quot;#48d1cc&quot;, &quot;#ff8c00&quot;, &quot;#696969&quot;, &quot;#ee82ee&quot;, &quot;#ffa07a&quot;, &quot;#a52a2a&quot;, &quot;#da70d6&quot;, &quot;#008000&quot;, &quot;#808080&quot;, &quot;#800000&quot;, &quot;#b8860b&quot;, &quot;#ff0000&quot;, &quot;#708090&quot;, &quot;#add8e6&quot;, &quot;#f0f8ff&quot;, &quot;#ffe4b5&quot;, &quot;#8fbc8f&quot;, &quot;#9acd32&quot;, &quot;#778899&quot;, &quot;#d2691e&quot;, &quot;#db7093&quot;, &quot;#fffaf0&quot;, &quot;#ff69b4&quot;, &quot;#b3b3b3&quot;, &quot;#9370db&quot;, &quot;#8b008b&quot;, &quot;#f4a460&quot;, &quot;#d3d3d3&quot;, &quot;#f5f5dc&quot;, &quot;#6b8e23&quot;, &quot;#9400d3&quot;, &quot;#008080&quot;, &quot;#4d4d4d&quot;, &quot;#00ff00&quot;, &quot;#00ffff&quot;, &quot;#ffc0cb&quot;, &quot;#ffd700&quot;, &quot;#c71585&quot;, &quot;#9932cc&quot;, &quot;#c0c0c0&quot;, &quot;#0000ff&quot;, &quot;#e6e6fa&quot;, &quot;#ffa07a&quot;, &quot;#deb887&quot;, &quot;#eee8aa&quot;, &quot;#1e90ff&quot;, &quot;#d02090&quot;, &quot;#000000&quot;, &quot;#66cdaa&quot;, &quot;#a9a9a9&quot;, &quot;#bc8f8f&quot;, &quot;#adff2f&quot;, &quot;#808080&quot;, &quot;#ffdead&quot;, &quot;#483d8b&quot;, &quot;#fffafa&quot;, &quot;#f08080&quot;, &quot;#faebd7&quot;, &quot;#ff7f50&quot;, &quot;#ffefd5&quot;, &quot;#228b22&quot;, &quot;#1a1a1a&quot;, &quot;#778899&quot;, &quot;#3cb371&quot;, &quot;#556b2f&quot;, &quot;#2e8b57&quot;, &quot;#cd5c5c&quot;, &quot;#cccccc&quot;, &quot;#6b8e23&quot;, &quot;#ff1493&quot;, &quot;#d8bfd8&quot;, &quot;#90ee90&quot;, &quot;#ffe4c4&quot;, &quot;#dda0dd&quot;, &quot;#daa520&quot;, &quot;#4d4d4d&quot;, &quot;#32cd32&quot;, &quot;#00008b&quot;, &quot;#8b0000&quot;, &quot;#87ceeb&quot;, &quot;#fff0f5&quot;, &quot;#191970&quot;, &quot;#eee8aa&quot;, &quot;#d19275&quot;, &quot;#f5deb3&quot;, &quot;#20b2aa&quot;, &quot;#deb887&quot;, &quot;#4169e1&quot;, &quot;#808080&quot;, &quot;#999999&quot;, &quot;#66cdaa&quot;, &quot;#006400&quot;, &quot;#00ff7f&quot;, &quot;#e0ffff&quot;, &quot;#00ffff&quot;, &quot;#000080&quot;, &quot;#2f4f4f&quot;, &quot;#ff00ff&quot;, &quot;#1a1a1a&quot;, &quot;#b0c4de&quot;, &quot;#6495ed&quot;, &quot;#ffdab9&quot;, &quot;#556b2f&quot;, &quot;#7b68ee&quot;, &quot;#fff5ee&quot;, &quot;#4b0082&quot;, &quot;#cccccc&quot;, &quot;#00bfbf&quot;, &quot;#ffebcd&quot;, &quot;#d3d3d3&quot;, &quot;#ffa500&quot;, &quot;#ff6347&quot;, &quot;#daa520&quot;, &quot;#008b8b&quot;, &quot;#faf0e6&quot;, &quot;#b0e0e6&quot;, &quot;#666666&quot;, &quot;#e9967a&quot;, &quot;#7cfc00&quot;, &quot;#f5fffa&quot;, &quot;#6a5acd&quot;, &quot;#b22222&quot;, &quot;#5f9ea0&quot;, &quot;#87cefa&quot;, &quot;#98fb98&quot;, &quot;#f5f5f5&quot;, &quot;#f0fff0&quot;, &quot;#a9a9a9&quot;, &quot;#0000cd&quot;, &quot;#8b4513&quot;, &quot;#999999&quot;, &quot;#fafad2&quot;, &quot;#2f4f4f&quot;, &quot;#7fffd4&quot;, &quot;#fdf5e6&quot;, &quot;#4682b4&quot;, &quot;#dcdcdc&quot;, &quot;#fff8dc&quot;, &quot;#b0c4de&quot;, &quot;#ffdab9&quot;, &quot;#333333&quot;, &quot;#fffff0&quot;, &quot;#ff8c00&quot;, &quot;#00fa9a&quot;, &quot;#fff5ee&quot;, &quot;#e6e6e6&quot;, &quot;#ffb6c1&quot;, &quot;#696969&quot;, &quot;#8a2be2&quot;, &quot;#ff4500&quot;, &quot;#40e0d0&quot;, &quot;#ff00ff&quot;, &quot;#808080&quot;, &quot;#b8860b&quot;, &quot;#800080&quot;, &quot;#ffffff&quot;, &quot;#666666&quot;, &quot;#fffacd&quot;, &quot;#e9967a&quot;, &quot;#ffe4e1&quot;, &quot;#708090&quot;, &quot;#7fff00&quot;, &quot;#8470ff&quot;, &quot;#b22222&quot;, &quot;#afeeee&quot;, &quot;#ffff00&quot;, &quot;#ba55d3&quot;, &quot;#f0fff0&quot;, &quot;#bdb76b&quot;, &quot;#fa8072&quot;, &quot;#b3b3b3&quot;, &quot;#f0ffff&quot;, &quot;#fafad2&quot;, &quot;#00ced1&quot;, &quot;#808000&quot;, &quot;#d2b48c&quot;]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Visualizing generated palettes from a saved model.&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;&gt;&gt; t = Theme.find(1)
&gt;&gt; t.dominant_color
=&gt; &quot;#BBCC44&quot;
&gt;&gt; t.dominant      
=&gt; [[5, &quot;#ecf7f8&quot;], [0, &quot;#44aabb&quot;], [-2, &quot;#33808c&quot;], [1, &quot;#60b7c5&quot;], [-1, &quot;#3a919f&quot;], [2, &quot;#73bfcc&quot;], [-5, &quot;#071113&quot;], [3, &quot;#a2d4dd&quot;], [-4, &quot;#112b2f&quot;], [4, &quot;#d0eaee&quot;], [-3, &quot;#22555e&quot;]]
&gt;&gt; t.interactive_color
=&gt; &quot;#44AABB&quot;
&gt;&gt; t.interactive
=&gt; [[5, &quot;#ecf7f8&quot;], [0, &quot;#44aabb&quot;], [-2, &quot;#33808c&quot;], [1, &quot;#60b7c5&quot;], [-1, &quot;#3a919f&quot;], [2, &quot;#73bfcc&quot;], [-5, &quot;#071113&quot;], [3, &quot;#a2d4dd&quot;], [-4, &quot;#112b2f&quot;], [4, &quot;#d0eaee&quot;], [-3, &quot;#22555e&quot;]]
&gt;&gt; t.tonic_color
=&gt; &quot;#CC6644&quot;
&gt;&gt; t.tonic      
=&gt; [[5, &quot;#faf0ec&quot;], [0, &quot;#cc6644&quot;], [-2, &quot;#994d33&quot;], [1, &quot;#d47d60&quot;], [-1, &quot;#ad573a&quot;], [2, &quot;#d98c73&quot;], [-5, &quot;#140a07&quot;], [3, &quot;#e6b3a2&quot;], [-4, &quot;#331a11&quot;], [4, &quot;#f2d9d0&quot;], [-3, &quot;#663322&quot;]]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each color palette is an Array, with 11 items.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;indexes -1 to -5 : 5 darker shades of the original color&lt;/li&gt;
&lt;li&gt;index 0 : the original color&lt;/li&gt;
&lt;li&gt;index 1 to 5 : lighter shades of the orginal color&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Injecting to the view&lt;/h2&gt;

&lt;p&gt;There's two easy ways to reflect this in your view ...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inline style declaration, overriding color values defined in external stylesheets&lt;/li&gt;
&lt;li&gt;Use a seperate CSS controller, set content type to text/css and reference in your document head like any other external stylesheets (and the purists are happy too)&lt;/li&gt;
&lt;/ul&gt;
          </content>  </entry>
  <entry xml:base="/">
    <author>
      <name>lourens</name>
    </author>
    <id>tag:blog.methodmissing.com,2006-11-20:6</id>
    <published>2006-11-20T19:41:00Z</published>
    <updated>2006-11-20T20:08:07Z</updated>
    <category term="Rails"/>
    <category term="Ruby"/>
    <link href="http://blog.methodmissing.com/2006/11/20/quick-what-s-new-here-hack" rel="alternate" type="text/html"/>
    <title>Quick what's new here hack</title>
<content type="html">
            &lt;p&gt;Sometimes you may need to flag a model as being new or recently updated, within in certain time constraint.&lt;/p&gt;

&lt;h2&gt;Monkey patch/plugin&lt;/h2&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;module Marmalade
  module ActiveRecord
    module Acts
      module Recent

        def self.included( klass )
          super
          klass.extend(ClassMethods)
        end     

        module ClassMethods

          def acts_as_recent( threshold = 5.days )
            class_inheritable_accessor :recent_threshold
            self.recent_threshold = threshold.is_a?( Fixnum ) ? threshold : 5.days
            send :include, Marmalade::ActiveRecord::Acts::Recent::InstanceMethods 
          end
        end

        module InstanceMethods

          def fresh?
            created_at &amp;&amp; created_at &gt; Time.now.utc.ago(recent_threshold)
          end

          def recently_updated?
            updated_at &amp;&amp; updated_at &gt; Time.now.utc.ago(recent_threshold)  
          end        

        end

      end
    end
  end
end

ActiveRecord::Base.class_eval { include Marmalade::ActiveRecord::Acts::Recent }&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Usage&lt;/h2&gt;

&lt;p&gt;A Fixnum argument is expected.That's on purpose.The ActiveSupport Time extensions makes for clean DSL like reading.&lt;/p&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;class Booking &amp;lt; ActiveRecord::Base
  acts_as_recent 5.days #3.hours, 1.week etc.

end&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Examples&lt;/h2&gt;

&lt;pre&gt;&lt;code class='ruby'&gt;&gt;&gt; b.created_at
=&gt; Mon Nov 20 10:21:23 UTC 2006
&gt;&gt; b.fresh?
=&gt; true
&gt;&gt; b.recently_updated?
=&gt; true
&gt;&gt; b.created_at = Time.now.utc.ago(10.days)
=&gt; Fri Nov 10 19:44:59 UTC 2006
&gt;&gt; b.fresh?
=&gt; false
&gt;&gt; new_bookings, old_bookings = Account.find(2).bookings.by_status(:awaiting_payment).partition{|b| b.fresh?}&lt;/code&gt;&lt;/pre&gt;
          </content>  </entry>
</feed>
