Freebootr update

December 30th, 2008

Chris Irish and I have been hard at work making another release of Freebootr. We released it in mid-December with improved search and speed as well as a redesigned look and feel. Shortly after this release, we got a write up in the Phoenix Business Journal.

To date we have over 23,000 listings in 17 states. We’re excited about the progress made on Freebootr and hope that 2009 continues the progress we’ve made in waste reduction. We hope you’re excited too! We’d love to hear your thoughts or ideas about Freebootr, so please send us your feedback.

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: Setting an SPF record

September 23rd, 2008

I recently noticed a lot of rejected emails coming from a production app that had just switched to send mail through Google Apps. For some reason Google felt we might be spammers, so it began rejecting our emails out-of-hand. We would continuously get the following error message:

Technical details of permanent failure:
Message rejected.  See http://mail.google.com/support/bin/answer.py?answer=69585 for more information.

That wasn’t very helpful, but with a little digging it led me to find out about something called an SPF and convinced me that I should set this up. It was really easy and I suggest you do it if you’re sending mail from your Ruby on Rails application through Google Apps (or some other means).

When using Google Apps, simply add a TXT record to your DNS settings with the following value:

v=spf1 mx include:aspmx.googlemail.com ~all

Note also that Google wants you to use include and specify all with a tilde(~), not a plus(+) or minus(-).

Publishing an SPF record that lacks include:aspmx.googlemail.com or specifying -all instead of ~all may result in delivery problems.

I also contacted Google to let them know of the error and if you start seeing this, be sure to let them know, too. Either that or setting the SPF record corrected our problem and we no longer get the rejected emails…

References

Freebootr Ahoy!

September 19th, 2008

I wrote recently about Phoenix Hacknight (that happens every Wednesday) because I want people to know that there is a vibrant, active community in Phoenix centered around technology. Hacknight has become a place where people meet consistently to network with each other and hack on various projects. I have attended every single Hacknight and closed shop on most of them, but besides the amazing 6 hour hacknight website, I’ve yet to really release a new idea to the public.

That’s why I’m happy to announce the launch of

Freebootr Logo

The Idea

Freebootr is an idea hatched at Hacknight by Chris Irish and me. It provides a place for people to easily and quickly find free items in their area as well as post free items they no longer want or need. We hope that this will result in less landfill waste and reduced cost to consumers. We believe this type of service should be free and open to the public and that’s exactly what we’ve done. What’s more, we believe it should be fun to use, which is why the site is pirate themed! Why, you ask?

freebooter |ˈfrēˌboōtər|

noun

a pirate or lawless adventurer.

That’s right, a freebooter is a pirate, so naturally we themed the site after these denizens of the sea. Arrr!

The Journey

Freebootr has been in the works for about 8 weeks at Hacknight. Chris and I have put in about 40 hours each throughout that time and set a goal to release the first version of the product today, on International Talk Like a Pirate Day. We also took a 2 week hiatus to implement the Hacknight website. It has been a fun, and somewhat bumpy, ride to get here, but we did it.

The Rest

Now the real work begins, for us and for you. We want… nay, need your feedback to make Freebootr.com a great, useful site. So check it out and be sure to call us out when we suck (and occasionally when we kick ass).

Now go out there, me hearty, and become a Freebootr!

Hacknight

September 16th, 2008

About 24 weeks ago, we started hosting a regular Wednesday night gathering at our Chandler, Arizona office called hacknight (originally hack-a-mania). We open our office to the public from 6pm until whenever to allow people in the Phoenix area to meet, network, play, eat, have fun and, of course, hack on projects. After almost 6 months, hacknight is still going strong!

Check out this hacknight video


Hack Night from Integrum Technologies on Vimeo.

Also check out the people and projects at hacknight, on the new site devoted to everyone who makes these wonderful events happen! If you’re interested in what hacknights are doing for the Phoenix community, be sure to come by the office located at

290 E. El Prado Ct., Chandler, AZ 85225

We’re there every Wednesday night, hacking away… you should be too!

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

I’ve been pretty enamored with SliceHost recently. They make it very easy to setup a slice, configure it and get your product deployed quickly (I’m down to 30 min). I hadn’t noticed until just recently that I’ve never set a time zone on any slice I’ve configured. So, here’s how you do it, simple and easy.

  1. SSH into your slice
  2. Run this command: sudo dpkg-reconfigure tzdata
  3. Select your geographic area and location

That’s it, hope it helps!

If you want your own slice, consider using my Slicehost referral link. Thanks!

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: SSH Backspace

August 26th, 2008

I just ran across this informative post by Jonathan Tron that solved an annoyance I’ve had for a while: backspace not doing what I want when I use SSH.

Change your Terminal Preferences

  1. In Terminal, select Preferences | Settings | Advanced.
  2. Select ‘Delete sends Ctrl-H’

You may need to do this again if you change your terminal style.

Specify a setting for nano

  1. On your server open ~/.nanorc
  2. Add ‘set rebinddelete’

And now you should have backspace working correctly for at least a few things…

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

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!