acts_as_redis_counter plugin

I’ve just released acts_as_redis_counter plugin for Rails.

Description:

The acts_as_redis_counter plugin implements high performance counters using write-back strategy with Redis key-value database.

Enjoy it!
http://github.com/vitalie/acts_as_redis_counter

Easy Rails virtual hosts with mod_macro

Keeping a separate file for each virtual host in /etc/httpd/vhosts.d it’s clean and cool, but when you have many virtual hosts with same settings it’s a pain to keep them updated. We need a template system for Apache configurations and mod_macro module it’s a handy tool for this job.

Download module version suited for your Apache from:
http://www.cri.ensmp.fr/~coelho/mod_macro/

Ensure that you have httpd-devel package installed then untar archive and compile mod_macro module with apxs:

[root@silver tmp]# wget http://www.cri.ensmp.fr/~coelho/mod_macro/mod_macro-1.1.10.tar.bz2
 
[...]
 
[root@silver tmp]# tar xvfjp mod_macro-1.1.10.tar.bz2 
 
[...]
 
[root@silver tmp]# cd mod_macro-1.1.10
[root@silver mod_macro-1.1.10]# apxs -cia mod_macro.c 
/usr/lib64/apr-1/build/libtool --silent --mode=compile gcc -prefer-pic -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -fno-strict-aliasing  -DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -pthread -I/usr/include/httpd  -I/usr/include/apr-1   -I/usr/include/apr-1   -c -o mod_macro.lo mod_macro.c && touch mod_macro.slo
mod_macro.c: In function 'looks_like_an_argument':
mod_macro.c:316: warning: cast from pointer to integer of different size
mod_macro.c: In function 'say_it':
mod_macro.c:929: warning: cast from pointer to integer of different size
/usr/lib64/apr-1/build/libtool --silent --mode=link gcc -o mod_macro.la  -rpath /usr/lib64/httpd/modules -module -avoid-version    mod_macro.lo
/usr/lib64/httpd/build/instdso.sh SH_LIBTOOL='/usr/lib64/apr-1/build/libtool' mod_macro.la /usr/lib64/httpd/modules
/usr/lib64/apr-1/build/libtool --mode=install cp mod_macro.la /usr/lib64/httpd/modules/
cp .libs/mod_macro.so /usr/lib64/httpd/modules/mod_macro.so
cp .libs/mod_macro.lai /usr/lib64/httpd/modules/mod_macro.la
cp .libs/mod_macro.a /usr/lib64/httpd/modules/mod_macro.a
chmod 644 /usr/lib64/httpd/modules/mod_macro.a
ranlib /usr/lib64/httpd/modules/mod_macro.a
PATH="$PATH:/sbin" ldconfig -n /usr/lib64/httpd/modules
----------------------------------------------------------------------
Libraries have been installed in:
   /usr/lib64/httpd/modules
 
If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the `-LLIBDIR'
flag during linking and do at least one of the following:
   - add LIBDIR to the `LD_LIBRARY_PATH' environment variable
     during execution
   - add LIBDIR to the `LD_RUN_PATH' environment variable
     during linking
   - use the `-Wl,--rpath -Wl,LIBDIR' linker flag
   - have your system administrator add LIBDIR to `/etc/ld.so.conf'
 
See any operating system documentation about shared libraries for
more information, such as the ld(1) and ld.so(8) manual pages.
----------------------------------------------------------------------
chmod 755 /usr/lib64/httpd/modules/mod_macro.so
[activating module `macro' in /etc/httpd/conf/httpd.conf]

Create a file that will keep your macros in Apache’s conf.d directory (example: _mod_macro.conf), conf.d files are executed after modules are loaded:

# /etc/httpd/conf.d/_mod_macro.conf
 
# RailsHost macro
<Macro RailsHost $host $dir>
  <VirtualHost *:80>
    ServerName www.$host
    ServerAlias $host
    DocumentRoot $dir/current/public
 
    CustomLog "logs/$host_access.log" combined
    ErrorLog "logs/$host_error.log"
 
    # Passenger settings
    RailsBaseURI /
    RailsMaxPoolSize 1
    RailsPoolIdleTime 3600
    RailsEnv production
 
    # Redirect example.com -> www.example.com
    RewriteEngine On
    RewriteCond %{HTTP_HOST} !^www.$host
    RewriteRule ^/(.*) http://www.$host/$1 [R=301,L]
 
    # Check for maintenance file and redirect all requests
    RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
    RewriteCond %{SCRIPT_FILENAME} !maintenance.html
    RewriteCond %{SCRIPT_FILENAME} !^/images
    RewriteCond %{SCRIPT_FILENAME} !^/stylesheets
    RewriteCond %{SCRIPT_FILENAME} !^/javascripts
    RewriteRule ^.*$ /system/maintenance.html [L]
 
    ErrorDocument 404 $dir/current/public/404.html
    ErrorDocument 422 $dir/current/public/422.html
    ErrorDocument 500 $dir/current/public/500.html
 
    Use ModDeflate
    Use ModExpires
 
    <Directory $dir/current/public>
      Options FollowSymLinks
      AllowOverride None
      Order allow,deny
      Allow from all
    </Directory>
  </VirtualHost>
</Macro>
 
# ModDeflate macro
<Macro ModDeflate>
  <IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/plain
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/xml
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE application/xml
    AddOutputFilterByType DEFLATE application/xhtml+xml
    AddOutputFilterByType DEFLATE application/rss+xml
    AddOutputFilterByType DEFLATE application/javascript
    AddOutputFilterByType DEFLATE application/x-javascript
 
    BrowserMatch ^Mozilla/4 gzip-only-text/html
    BrowserMatch ^Mozilla/4\.0[678] no-gzip
    BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
  </IfModule>
</Macro>
 
# ModExpires macro
<Macro ModExpires>
  <IfModule mod_expires.c>
    ExpiresActive On
    <LocationMatch "^/(images|javascripts|stylesheets)">
      ExpiresDefault "access plus 1 year"
    </LocationMatch>
  </IfModule>
</Macro>

Append RailsHost macro calls to your httpd.conf to define your virtual hosts :

# /etc/httpd/conf/httpd.conf
 
[...]
 
Use RailsHost vhost1.com /data/virtualhosts/vhost1.com
Use RailsHost vhost2.com /data/virtualhosts/vhost2.com

Now, your Apache configuration looks much better and when it comes make changes to your configurations you’ll need to change just one macro.

PageActions Plugin released

I’ve just released PageActions plugin on GitHub. It’s a really simple Rails plugin, but it helps you to easy define and render actions links in your views. You can view installation and usage instructions on GitHub:


http://github.com/vitalie/page_actions

Iteration over large data sets in Rails

We often need to iterate over the database rows in our migrations. When dealing with millions of records, basic iteration techniques doesn’t work well because each loaded object is consuming system memory and it issues at least one database query per object to load.

# >> Book.count :all
# => 4000216

Solution 1

class BooksUpdateTitles < ActiveRecord::Migration
  def self.up
    Books.all.each do |book|
      # ...
    end
  end
 
  def self.down
  end
end

The problem with this version is that it will load all 4000216 objects into memory, all memory will be consumed and it will start to use disk swap and it will take hours to complete.

We can optimize it a little bit by specifying select parameter in our query:

Solution 2

Books.find(:all, :select => 'id').each do |t|
  book = Book.find t.id
  # ...
end

Version 2 still loads all objects in memory but selects only id field.

We need to avoid loading all objects in the memory, we’ll iterate over collection and we’ll load only current object.

Solution 3

last_id = 0
while book = Books.find(:first, :conditions => ['id > ?', last_id])
  # ...
  last_id = book.id
end

Version 3 it’s OK, but it can be speed up by loading objects in batch not just one by one.

Solution 4

last_id = 0
while books = Book.find(:all, :conditions => ['id > ?', last_id], :limit => 100)
  # ...
  last_id = books.last.id
end

Examining the log:

...
Domain LOAD (0.000176)  SELECT * FROM `books` WHERE (id > 0) LIMIT 100
...

We have loaded 100 objects with one query. Solution 4 seems to be the best solution to iterate over large data sets as it uses less memory with fewer SQL requests.

Update:

Mitchell proposed a better solution to use ActiveRecord’s find_in_batches method. DHH commited this feature on February 23, 2009 that permits iterating over large data sets in batches:

Read more:
WebOnRails
GitHub

Solution 5

Book.find_in_batches(:batch_size => 100) do |results|
# Do something with results
end

Missing host to link to! Please provide :host parameter or set default_url_options[:host]

Problem:
Missing host to link to! Please provide :host parameter or set default_url_options[:host] when sending emails.

Solution:
You can pass host parameter to url functions, but it’s cleaner to configure it with a before_filter globally in your application_controller.rb:

  # application_controller.rb
  before_filter :mailer_set_url_options
 
  ...
 
  def mailer_set_url_options
    ActionMailer::Base.default_url_options[:host] = request.host_with_port
  end

Simple script to convert ERB files to Haml

A simple script to convert .erb files from current directory to .haml :

#!/usr/bin/ruby
 
Dir.glob("*.html.erb").each do |erbname|
  hamlname = erbname.gsub(".html.erb", ".html.haml")
  system "/usr/bin/html2haml #{erbname} #{hamlname}"
end

File uploads in import scripts

Simulating file uploads in your scripts or from console can be done really simple using Rail’s ActionController::TestUploadedFile from action_pack.

Example code:

require 'action_controller/test_process'
 
class ImportExternalData
 ...
  def import_data
    ...
    page.attachments << PageAttachment.new(
      :uploaded_data => fake_file_upload(filename, mime_type),
      :title => title,
      :description => description)
    ...
  end
 
 
protected
  def fake_file_upload(path, mime_type = nil, binary = false)
    ActionController::TestUploadedFile.new(
      path,
      mime_type,
      binary
    )
  end
end

Excerpt from ActionController::TestUploadedFile’s comments:

Essentially generates a modified Tempfile object similar to the object
you’d get from the standard library CGI module in a multipart
request. This means you can use an ActionController::TestUploadedFile
object in the params of a test request in order to simulate
a file upload.