Rss Feed Tweeter button Facebook button Technorati button Reddit button Linkedin button Webonews button Delicious button Digg button Flickr button Stumbleupon button Newsvine button

A Waage Blog

Ruby, Rails, Life

Archive for the ‘Ruby and Rails’ Category

Rails Rotating Log Files with logrotate

with one comment

I know there’s a way to specify Rails log rotation parameters directly in the app. This works for some people:

# Can place this in environment.rb
# 2nd argument - number of log files to keep
# 3rd argument - size (bytes) that log files are allowed to reach before rotation
config.logger = Logger.new(config.log_path, 8, 1024)

However…. I like the customizability of using logrotate better!
Here’s my logrotate config file that handles weekly log rotation, delayed compression and uses the copy-truncate method:

I place this config in the /etc/logrotate.d folder (ubuntu)
(ie. /etc/logrotate.d/<rails_app_name>)

/var/www/rails//shared/log/production.log {
  weekly
  missingok
  rotate 8
  compress
  delaycompress
  notifempty
  copytruncate
}

This config will rotate my production.log file weekly, keeping at most 8 log files. It delays compression until next rotation (extra precaution, simply to make sure the log file is not in use), and uses the ‘copytruncate’ method which basically copies the current log file, and then truncates this log file, so the Rails app maintains file pointer for continued writing.

Written by Andrew Waage

June 15th, 2011 at 11:56 am

RSpec Request Spec to Test Rails / Grape API Functionality

with 8 comments

I finally got around to trying Grape – a “RESTful API microframework built to easily and quickly produce APIs for Ruby-based web applications”. This is a project still in baby stages, but has a lot of potential and worth exploring for anyone creating a Rack-based API in Ruby, not necessarily Rails!

Now, after creating a pretty basic API that used HTTP Basic Authentication, I was inclined to write some RSpec tests to make sure my API was working the way I thought it was (.. or because I am obsessed with well-tested, beautiful code..).

After some thought, I decided that the best way to test my API was with RSpec “request” specs. Now, if you are at all relatively new to RSpec (I was a Test::Unit kinda guy before), it might not be completely obvious that “request specs” are basically what I have come to know as “integration tests”, testing high-level functionality that spans multiple controllers and multiple requests – (think: a user’s interaction with the app).

My reasoning for choosing request specs is because I want to test specific API URL endpoints routed the way I expected. (Routing is handled magically by Grape with a simple mount in the config/routes.rb file). API testing just kinda makes sense to handle in request specs.

Anyways, I ran into a couple issues because in REQUEST specs, you do not have access to the @request object (haha?), as you do in controller specs. Now, in order to mock HTTP Basic Authentication, you need to mock the request object to send headers along with the GET request.

Well, solution: It turns out you can pass headers into your get() method! I only wish I had discovered that an hour ago!

Here’s a simple excerpt from my API request specs that shows how to mock the HTTP basic authentication and test your API functionality:

With NO basic auth, it’s just a simple GET request

  it 'should return a 401 with no basic auth to /api/v1/rewards' do
    get '/api/v1/rewards'
    response.code.should == '401'
    response.body.should == "Unauthorized - Please check your username and password"
  end

To mock the basic auth, simply pass header hash as argument to the GET request! No need to access the request object here.

  it 'should return a 200 with valid basic auth to /api/v1/rewards' do
    # Uses basic_auth helper method
    credentials = basic_auth('testuser','test')
    get '/api/v1/rewards', nil, {'HTTP_AUTHORIZATION' =>  credentials }
    response.code.should == '200'
    response.body.should == "..."
  end

# You can define this at the bottom of your spec file, or in spec_helper for convenience
def basic_auth(user, password)
  ActionController::HttpAuthentication::Basic.encode_credentials user, password
end

Hope this helps someone else. Now go write some request specs! :)

Written by Andrew Waage

May 26th, 2011 at 1:29 am

Ruby Multi-level Nested Hash Value

with 5 comments

Often in my Ruby code or Rails application, I will need to find a value in a nested hash. Frequently this also comes in handy when dealing with JSON and parsing JSON to a hash. For example, I might have a hash of user information that looks like this:

user_hash = {:id => 1, :name => 'John doe', :extra => {:birthday => {:month => 11, :day => 16, :year => 1951}}}

Now, when I want to find the birthday year, I have to do something messy like this:

year = user_hash[:extra] && user_hash[:extra][:birthday] && user_hash[:extra][:birthday][:year]

How inconvenient is this?! Every level of the hash I am checking for existence of the hash-key. Here’s a helper method that I use so that I can avoid these verbose statements and get the value I want in 1 line. It adds a ‘hash_val’ method to any hash, and takes in the hash-keys as arguments. If one of the nested hash keys is missing, it will simply return nil.

# I usually define this in an initializer, so it can be used all over my app:
# Eg. Place in config/initializers/hash_val.rb
class Hash
  # Fetch a nested hash value
  def hash_val(*attrs)
    attr_count = attrs.size
    current_val = self
    for i in 0..(attr_count-1)
      attr_name = attrs[i]
      return current_val[attr_name] if i == (attr_count-1)
      return nil if current_val[attr_name].nil?
      current_val = current_val[attr_name]
    end
    return nil
  end
end

Now, getting a nested hash value is so easy!

user_hash.hash_val(:extra, :birthday, :year)
 => 1951

And, if the hash-key does not exist, it simply returns nil:

user_hash.hash_val(:extra, :trouble)
=> nil

Written by Andrew Waage

March 18th, 2011 at 8:27 pm

Rails 3 – How to Rename a Project

with one comment

As opposed to previous versions of Rails, Rails 3 namespaces your entire project according to your project name. As an example, notice that in your config/routes.rb file, the first line is:

ProjectName::Application.routes.draw do
...

This means that changing a project name involves changing a number of files to reference the new project name as well. Here’s a quick list of the standard files to change:

Rakefile
config.ru
config/application.rb
config/database.yml
config/environment.rb
config/environments/*.rb
config/initializers/secret_token.rb
config/initializers/session_store.rb
config/routes.rb

Besides these, it’s a good idea to also check all files in config/ and config/initializers/
If you want to be thorough, run this grep command in your project root, and you will get a list of all files that contain your old project name:

grep -Ri 'oldprojectame' * | cut -f1 -d':' | sort | uniq

Written by Andrew Waage

February 28th, 2011 at 2:17 pm

Facebook base64 url decode for signed_request

with 9 comments

I ran into a problem as I was trying to decode and parse the Facebook signed_request for their new Registration plugin (http://developers.facebook.com/docs/plugins/registration).

Folowing the PHP example, I attempted to decode and read the signed_request returned by Facebook. Unfortunately, it seemed like the decoded JSON returned was malformed! It was missing the end hash character “}”. This may not happen in all cases, but the reason is due to the padding in Base64 encoding (See Base64 for URLs in Wikipedia).

To account for the padding in Base64, I used the following helper method to do the base64_url_decode. Hope it helps someone else trying to base64 decode Facebook’s signed_request in Ruby on Rails!:

 def base64_url_decode(str)
   str += '=' * (4 - str.length.modulo(4))
   Base64.decode64(str.tr('-_','+/'))
 end

Notice there’s two things that must happen before decoding the string:

  1. Pad the encoded string with “=”
  2. Replace the character ‘-’ with ‘+’, and ‘_’ with ‘/’

I wish Facebook mentioned this clearly on their API !

Written by Andrew Waage

February 8th, 2011 at 1:27 pm