We’ve been using God to monitor our Thin processes on Freebootr and set it up to notify us through our Google Apps account. Thought our God config file might be useful to anyone trying to use God with Thin and Google Apps.

Thin configuration

# == God config file
# http://god.rubyforge.org/
# Authors: Gump and michael@glauche.de
#
# Config file for god that configures watches for each instance of a thin server for
# each thin configuration file found in /etc/thin.
# In order to get it working on Ubuntu, I had to make a change to god as noted at
# the following blog:
# http://blog.alexgirard.com/2007/10/25/ruby-one-line-to-save-god/
#
require 'yaml'
config_path = "/etc/thin" 

Dir[config_path + "/*.yml"].each do |file|
  config = YAML.load_file(file)
  num_servers = config["servers"] ||= 1

  (0...num_servers).each do |i|
    # UNIX socket cluster use number 0 to 2 (for 3 servers)
    # and tcp cluster use port number 3000 to 3002.
    number = config['socket'] ? i : (config['port'] + i)

    God.watch do |w|
      w.group = "thin-" + File.basename(file, ".yml")
      w.name = w.group + "-#{number}" 

      w.interval = 30.seconds

      w.uid = config["user"]
      w.gid = config["group"]

      w.start = "thin start -C #{file} -o #{number}" 
      w.start_grace = 10.seconds

      w.stop = "thin stop -C #{file} -o #{number}" 
      w.stop_grace = 10.seconds

      w.restart = "thin restart -C #{file} -o #{number}" 

      pid_path = config["pid"]
      ext = File.extname(pid_path)

      w.pid_file = pid_path.gsub(/#{ext}$/, ".#{number}#{ext}")

      w.behavior(:clean_pid_file)

      w.start_if do |start|
        start.condition(:process_running) do |c|
          c.interval = 5.seconds
          c.running  = false
          c.notify   = 'developers'
        end
      end

      w.restart_if do |restart|
        restart.condition(:memory_usage) do |c|
          c.above  = 150.megabytes
          c.times  = [3,5] # 3 out of 5 intervals
          c.notify = 'developers'
        end

        restart.condition(:cpu_usage) do |c|
          c.above  = 50.percent
          c.times  = 5
          c.notify = 'developers'
        end
      end

      w.lifecycle do |on|
        on.condition(:flapping) do |c|
          c.to_state     = [:start, :restart]
          c.times        = 5
          c.within       = 5.minutes
          c.transition   = :unmonitored
          c.retry_in     = 10.minutes
          c.retry_times  = 5
          c.retry_within = 2.hours
          c.notify       = 'developers'
        end
      end

      w.transition(:up, :start) do |on|
        on.condition(:process_exits) do |c|
          c.notify = 'developers'
        end
      end
    end
  end
end

Email through Google Apps configuration

require 'tlsmail'
Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE)

God::Contacts::Email.message_settings = {
  :from => 'user@domain.com'
}

God::Contacts::Email.server_settings = {
  :address        => 'smtp.gmail.com',
  :tls            => 'true',
  :port           => 587,
  :domain         => 'domain.com',
  :user_name      => 'user@domain.com',
  :password       => '******',
  :authentication => :plain
}

God.contact(:email) do |c|
  c.name  = 'Dev 1'
  c.email = 'dev1@domain.com'
  c.group = 'developers'
end

God.contact(:email) do |c|
  c.name  = 'Dev 2'
  c.email = 'dev2@domain.com'
  c.group = 'developers'
end

References

We’ve been playing around with the Sphinx full-text search engine and Ultrasphinx, the Ruby on Rails configurator and client to the Sphinx full text search engine. Sadly, it was giving us a warning about spell checking:

ultrasphinx: spelling support not available (raspell configuration raised "uninitialized constant Ultrasphinx::Spell::Aspell")

This is pretty easy to get rid of, though. You just need to install the aspell, spell checking library and raspell, the Ruby interface to aspell. The raspell README has instructions for installing aspell and raspell on both Mac and Ubuntu.

After we did this, we got one additional error:

ultrasphinx: spelling support not available (raspell configuration raised "No word lists can be found for the language "ap".")

Follow the instructions for setting up the custom wordlist needed by Ultrasphinx and you should be good to go.

Good luck and happy searching!

Error after upgrading SliceHost

September 23rd, 2008

If you happen to see this error after upgrading your SliceHost account, be calm.

ActionView::TemplateError (Define INLINEDIR or HOME in your environment and try again)

The simple solution is to add an environment declaration to your environment/production.rb file:

ENV['INLINEDIR'] = '/path/to/.ruby_inline'

Once that was set, we were in business again… whew!

References

Here’s a relevant reference that took a while to find:

Quick Tip: Rails 2.1 Time Zones

September 13th, 2008

My last quick tip involved setting your time zone in Ubuntu Hardy, so now, how do you set your time zone in a Ruby on Rails application? Rails 2.1 makes it much easier to manage time zone settings than it was previously.

Add the following to your environment configuration file:

config/environment.rb

config.time_zone = 'Arizona'

Replace Arizona with your own time zone. You can find a list of valid values by running any of the following rake tasks:

rake time:zones:all
rake time:zones:local
rake time:zones:us

Your data will still be stored in UTC time, but it will be converted into the specified time zone when it is type cast on retrieval.

Update: If you set both your server’s time zone and your applications time zone then you may see some incorrect times. I believe this is because you’ll be storing local times and the Rails app will be trying to convert them to local – a double conversion. So be careful.

References

Nginx 405 Not Allowed Error

September 5th, 2008

405 Not Allowed

So, I got this error today and it took me a few minutes to track down why. Seems like the kind of thing that might be interesting to people. I’m using nginx on a project and this error was being thrown by nginx, not by Rails. What could be the cause?

Nginx configuration and REST

Do you have something in your nginx config file that looks like this?

if (-f $request_filename) {
  break;
}

Basically, render an existing, static file directly, aka. bypass Rails. This is great, we don’t incur the overhead of hitting Rails for static files, including cached files, right? Wrong.

You see, with REST, your URL will be the same for a POST to create as it will for a GET to index. For example, viewing the users index and creating a user:

GET  /users <= user's index
POST /users <= create a user

See the problem?

That’s right, if you cache the user’s index using a technique like caches_page then you have a static file (e.g., users.html) that matches regardless of the HTTP method. Therefore, it will bypass rails and attempt to serve the static file on a POST request, resulting in a 405 error. Whew!

Caching with nginx

So what do you do? I don’t know. But this is what I did and I hope it helps. Add the following to your nginx config prior to the serving of static files.

if ($request_method != GET) {
  proxy_pass http://foobar;
  break;
}

Uh, where foobar is the name you specified in the upstream declaration in the nginx config. This should pass any non-GET request to Rails immediately since we don’t want to statically serve files for anything other than GET.

References

Quick Tip: Form Partials

September 1st, 2008

Partials are a great way to keep your view code separated logically. Prior to Rails 2.1 if you wanted to reuse a form partial in, for example, a new and edit view, then you needed to pass the form into the partial somehow.

Given the following form partial:

views/users/_form.html.erb

<div>
  <%= form.label :name -%>
  <%= form.text_field :name -%>
</div>

Pre-Rails 2.1

You might consider passing the form in as a local, like so.

views/users/new.html.erb or views/users/edit.html.erb

<% form_for @user do |form| -%>
  <%= render :partial => 'form', :locals => { :form => form } -%>
<% end -%>

Rails 2.1

There’s now a shortcut for this common method of rendering a form partial.

views/users/new.html.erb or views/users/edit.html.erb

<% form_for @user do |form| -%>
  <%= render :partial => form -%>
<% end -%>

Nice! Cleans things up a bit.

Quick Tip: named_scope

August 31st, 2008

Have you ever found yourself writing queries like this?

User.find(:all, :conditions => ['state = ? AND created_at > ? AND created_at <= ?', 'active', start_date, end_date], :limit => 5)

I suppose you could refactor this into a custom finder that did the heavy lifting for you…

User.find_all_active_in_date_range(start_date, end_date)

But what if you need that same query where the state is ‘pending’? Create another custom finder? Modify it so it takes another parameter for state? Wouldn’t it be nice if you could create some kind of reusable snippet that could be chained together to create a custom finder? Well, look no further. As of Rails 2.1, such a thing exists and it’s called named_scope.

Let’s refactor that code using named_scope.

app/models/user.rb

class User < ActiveRecord::Base
  named_scope :active, :conditions => { :state => 'active' }
  named_scope :between, lambda { |starts, ends| { :conditions => ['created_at > ? AND created_at <= ?', starts, ends] } }
end

The lambda is simply so we can accept parameters into the named scope call. Named scopes are easily chainable, so to get the desired query we can call:

User.active.between(start_date, end_date)

Plus, if we want to add another condition, we just created another reusable named_scope and add it to the end.

app/models/user.rb

class User < ActiveRecord::Base
  named_scope :active, :conditions => { :state => 'active' }
  named_scope :between, lambda { |starts, ends| { :conditions => ['created_at > ? AND created_at <= ?', starts, ends] } }
  named_scope :limit, lambda { |num| { :limit => num } }
end

And we have our original query. This is much more readable and maintainable than the previous code we were looking at. The named scopes can also be reused when another query comes up with those conditions.

User.active.between(start_date, end_date).limit(5)

There’s so much more you can do with named_scope, so check it out.

References

Quick Tip: Route Associations

August 26th, 2008

Are you used to writing your routes like this?

map.resources :notes do |notes|
    notes.resource  :author
    notes.resources :comments
    notes.resources :attachments
  end

Don’t fret, there may be hope for you yet. For these simple routes you can use the has_one or has_many route association options.

  • has_one – use it for a singleton resource
  • has_many – use it for plural resources

Refactored routes

map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]

Give it a try!

Keep in mind I said simple. If you’re doing something more complex they might not be the best choice.

Additional Resources

It’s really easy to create a many-to-many relationship that can be assigned through checkboxes. Check it out!

Let’s say you have Users and Groups. A User can belong to a Group and a Group can have many Users – we call this a Membership, like so (migrations omitted for brevity):

app/models/user.rb

class User < ActiveRecord::Base
  has_many :memberships, :dependent => :destroy
  has_many :groups, :through => :memberships
end

app/models/group.rb

class Group < ActiveRecord::Base
  has_many :memberships, :dependent => :destroy
  has_many :users, :through => :memberships
end

app/models/membership.rb

class Membership < ActiveRecord::Base
  belongs_to :group
  belongs_to :user
end

We can now assign groups to members in a relatively easy manner with no extra work needed in the models. Behold!

app/views/users/edit.html.erb

<h1>User <%= @user.id -%></h1>

<h2>Group Memberships</h2>
<% form_for @user do -%>
  <% Group.all.each do |group| -%>
    <div>
      <%= check_box_tag :group_ids, group.id, @user.groups.include?(group), :name => 'user[group_ids][]' -%>
      <%= label_tag :group_ids, group.id -%>
    </div>
  <% end -%>
  <%= submit_tag -%>
<% end -%>

Errr… something like that. Anyway, the important thing to note is the use of group_ids. The values will get submitted as group_ids, a member of the User. Where did that come from? We don’t have an attribute or method on the model for it, so where’d it come from? Well, seems that it is auto-generated for you to allow something like I just showed.

When this form is submitted, any checked Groups will be associated through Memberships to the User by way of the magic *_ids= method. Should work the other way too with user_ids checkboxes on a group. No extra code needed. Awesome, right?

Bonus: If you uncheck all the checkboxes, then nothing gets posted, doh! So make sure to merge a default value with your parameters like this to ensure the *_ids= method gets called:

app/controllers/users_controller.rb

@user.attributes = {'group_ids' => []}.merge(params[:user] || {})

Super Bonus: When you’re defaulting the group_ids in the controller make sure to use the key as a string, not a symbol. Or if you do use a symbol then make it a Hash with_indifferent_access.

Super Monkey Ball: A monkey encased in a ball who collects bananas.

If you have changed the SSH port number on your server, then you need to let Capistrano know how to connect. Luckily, it’s pretty easy.

Add the following to your deployment file, replacing 8888 with your port number.

config/deploy.rb

ssh_options[:port] = 8888

This will apply that port number to connections made by Capistrano. If you need to specify the port for each server (app, web, db) then tack it on to the end of their declarations.

role :app, "65.74.169.199:8030" 
role :web, "65.74.169.199:8031" 
role :db,  "65.74.169.199:8032", :primary => true

I haven’t verified that one, but it supposedly works. Happy deployments!