Archive for 'Rails'

Removing Rails formatted routes too soon

Here’s something to watch out for when serving different types of content using the Rails respond_to and :format parameters. Browsers don’t always know exactly what to do when serving non-html content. In my app, we serve many pages as csv as well as html.
Since the formatted_ routes are going to be removed from Rails, I stopped defining them in favor of simply using the :format parameter.

This means the url in the browser location bar will NOT end with “.csv”. Don’t do this on Rails 2.2.2 and earlier!

Rails 2.2.2 properly sets the Content-Type to text/csv. But that’s not enough for our browser to know what to do with it. Poking around this morning reveals that Numbers on my MacBook Pro will not open the csv file without an extension. Same issue reported by QA on Windows with IE and Firefox and Microsoft Excel. Same results whether you let the browser open the file in an app directly, or Save As and then open it.

Along with the content-type, the filename and extension needs to be correct.

I fixed this on the server side, by forcing a disposition header the same way send_data does.

respond_to do |format|
  format.html
  format.csv { headers["Content-Disposition"] ||= "attachment; filename=\"#{params[:action] + '.csv'}\"" }
end

Now we get a disposition header, which explicitly tells the browser what type of file it is.

Content-Disposition: attachment; filename="index.csv"

This is ugly. Don’t do it. It won’t be needed when this patch, which removes the formatted_ routes, it released. It seamlessly keeps support for incoming requests such as “/posts.csv” working as they should.

So the moral of the story is: don’t be overzealous and stop using formatted routes yet! When you upgrade your app, just remove the formatted routes from routes.rb and everything will continue to work as it should.

rexml-expansion-fix included in rails 2.2.2

The rexml-expansion-fix as noted here and is fixed by a patch or a gem, is included in activesupport-2.2.2. Use this spec to make sure you are patched.

Rails 2.2 broke my rspec rescue_from

When I upgraded to Rails 2.2 and a bunch of my controller tests started failing, I quickly realized that the rescue_from blocks were being skipped. I quick google turned up a solution that I quickly got working.

controller.use_rails_error_handling!

But since none of us wants to have to add this to each of our controller specs, add this to your configure block in spec_helper:

Spec::Runner.configure do |config|
  config.before(:each, :type=>:controller) {controller.use_rails_error_handling!}
end

Best ActiveRecord error message ever

ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) :blocked_client_system or :blocked_client_systems in model GroupsBlkClientSystem.  Try 'has_many :blocked_client_systems, :through => :groups_blk_client_systems, :source => <name>'.  Is it one of :group or :client_system?

I was mising :source => :group from my association. Perfect message. The code knew what was missing, and it told me in a way that could be literally copied and pasted. Do write code like this when you can.

Lots of Rails books coming soon

The people who market technical books pay attention to the “trends” in technology. The fall of C and C++. The rise of Ruby / Rails. The rise (and fall) of Java. If us technology people “watch the watchers”, we can get a nice warm-and-fuzzy about our current technology choices. For example, look at all the Ruby and Rails books being published in the coming months!

Testing SNMP traps with snmptrapd

If you’ve never worked with SNMP, then you’re in for a treat. Get ready to party like it’s 1988, with nested authentication models, over-designed protocols, and lots of meaningless numbers. Luckily, if all you need to do is write some ruby code to send SNMP traps, you have the snmp gem to help make things pretty palatable.

So there I was, with my ruby model code happily sending out SNMP traps….or was it? For development (and test and qa) I set up the yaml file to send SNMP traps to localhost (port 162 is the default). I just needed a way to recieve them.

Enter snmptrapd.

Yes! A command line tool to catch the traps and log them the stdout! This turned out to be pretty easy on my MacBookPro - net-snmp is already installed.

The hardest part is setting up the authorization model. Do this: Create a file called snmptrapd.conf in the current directory. Add the line “disableAuthorization yes” to it. Heheh. No need for security - we just want to see them.

Now run the snmptrapd in the foreground, logging everything to stdout:

sudo snmptrapd -f -Lo -c snmptrapd.conf

Done. Now your messages will show up in that console window. This was not as nearly as painful as I expected!

So in the yml file, I will configure the snmp_trap_destination to be localhost for all environments except production. If there is no daemon running, they will quietly be ignored (UDP rocks). If there is one running (debugging qa, development) then they can be logged. Life is good, even when working with a 20 year old protocol.

(and thanks to atlantageek for one of the only blog posts out there about snmp and ruby!)

Beware rendering multiple partials with layouts

Want to render more than one partial with the same layout in your page? Beware:


<%= render :partial => ‘p1′, :layout => ‘layouts/p_layout’ %>
<%= render :partial => ‘p2′, :layout => ‘layouts/p_layout’ %>

layouts/p_layout.html.erb


<%= yield :top %>
<%= yield %>
<%= yield :bottom %>
<hr/>

_p1.html.erb


<% content_for :top, 'HeaderFor1' %>
<% content_for :bottom, 'FooterFor1' %>
Content!

_p2.html.erb


<% content_for :top, 'HeaderFor2' %>
<% content_for :bottom, 'FooterFor2' %>
Content!

Did you expect this?

HeaderFor1 Content! FooterFor1


HeaderFor2 Content! FooterFor2


Nope. You get this:

HeaderFor1 Content! FooterFor1


HeaderFor1HeaderFor2 Content! FooterFor1FooterFor2


The instance variables in the layout object retain their values, even between invocations of render. My workaround (since I’m not sure if some would call this a “feature”)…clear the instance variables at the end of your layout.


<%= yield :top %>
<%= yield %>
<%= yield :bottom %>
<hr/>
<% @content_for_top, @content_for_bottom = nil %>

Now it works as I expected.

Rails LOC vs Java LOC

At my last client, we wrote a webservice in Java, it had perhaps 30 service calls and 3 endpoints. We worked on it for about 6 months.

$ find services -name *.java -exec wc {} \; | awk '{a+=$1;print a}' |tail -1
64793

At my current client, we are writing a Rails web application. It’s been a living project for almost 2 years.

$ find app -name *.rb -exec wc {} \; | awk '{a+=$1;print a}' |tail -1
5133
$ find lib -name *.rb -exec wc {} \; | awk '{a+=$1;print a}' |tail -1
1141
$ find app -name *.erb -exec wc {} \; | awk '{a+=$1;print a}' |tail -1
3104

Generate mod_rails httpd.conf using cap

With all the discussions around the new mod_rails, I thought I’d post the Apache conf that I came up with. Here’s the strategy: generate httpd.conf using cap, upload it to the server, and graceful restart Apache. This way, you don’t have to get your hands dirty logging into the box to reconfigure anything.

Here’s the erb template for httpd.conf:

#
# Apache httpd.conf
# generated by cap. run by <%= user %> at <%= Time.now %>
#

ServerRoot "<%= current_release %>"
PidFile "<%= current_release %>/tmp/pids/httpd.pid"
LockFile "<%= current_release %>/log/accept.lock"
ServerName localhost
ServerAdmin "me"
Listen <%= web_port %>

# prefork MPM
# StartServers: number of server processes to start
# MinSpareServers: minimum number of server processes which are kept spare
# MaxSpareServers: maximum number of server processes which are kept spare
# MaxClients: maximum number of server processes allowed at a time
# MaxRequestsPerChild: maximum number of requests a server process serves until death
StartServers          <%= apache_processes %>
MinSpareServers       <%= apache_processes %>
MaxSpareServers       <%= apache_processes %>
MaxClients            100
MaxRequestsPerChild   0

ErrorLog "<%= current_release %>/log/apache_error_log"
# Possible values include: debug, info, notice, warn, error, crit, alert, emerg
LogLevel info
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog "<%= current_release %>/log/apache_access_log" combined
#RewriteLog "<%= current_release %>/log/apache_rewrite_log"
# 0-9, 0 = off, 2 and 3 is good for debugging
#RewriteLogLevel 3

ServerTokens Prod
ServerSignature Off

DefaultType text/plain
TypesConfig /usr/local/apache2/conf/mime.types
AddType application/x-compress .Z
AddType application/x-gzip .gz .tgz

<% if behind_https %>
# for envs that sit behind an https load balancer
RequestHeader set X-FORWARDED_PROTO https
<% end %>

# using Passenger (http://www.modrails.com/) to serve Rails
LoadModule passenger_module <%= gem_home %>/passenger-1.0.1/ext/apache2/mod_passenger.so
RailsSpawnServer            <%= gem_home %>/passenger-1.0.1/bin/passenger-spawn-server
RailsRuby             <%= ruby_binary %>

RailsUserSwitching    off
# max number of Rails processes to be spawned at a time
RailsMaxPoolSize      <%= max_rails_processes %>
# spawner processes will be killed if idle for 60 minutes
RailsPoolIdleTime     3600
RailsEnv              <%= environment %>

RailsAllowModRewrite  on
RewriteEngine On
# Check for maintenance file and redirect all requests
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ %{DOCUMENT_ROOT}/system/maintenance.html [L]

DocumentRoot "<%= current_release %>/public"
<Directory <%= current_release %>/public>
    Options FollowSymLinks
    AllowOverride None
    Order allow,deny
    Allow from all

    # Set the max size for file uploads to 10M
    LimitRequestBody 10485760
</Directory>

<Directory />
    Options FollowSymLinks
    AllowOverride None
    Order Deny,Allow
    Deny from all
</Directory>

Here is the cap task to generate httpd.conf and upload it to the server:

desc "Generate the apache httpd.conf file from the template"
task :generate_apache_httpd_conf_file, :roles => :app do
  require 'erb'
  # set up defaults for some vars to go into the template. others are required.
  apache_processes = fetch(:www_procs, 2)
  max_rails_processes = fetch(:max_rails_procs, 2)
  ruby_binary = fetch(:ruby_binary, "ruby")
  gem_home = fetch(:gem_home, '/usr/local/lib/ruby/gems/1.8/gems')
  behind_https = fetch(:fronted_by_https, false)
  template = ERB.new(File.read('config/templates/httpd.conf.erb'), nil, '<>')
  result = template.result(binding)
  put(result, "#{current_release}/httpd.conf")
end

It runs in an after_update_code task. This way, all server configs are under code control. This is huge! Each deployed environment’s settings are known and tracked - not just loosely keyed on each server. I treat Apache as part of our application - not a service that happens to be running that we deploy into. Using this technique, along with vendor_everything, we’ve minimized our host dependencies.