Setting up a Ruby on Rails stack on Rackspace Cloud

I have been using Rackspace Cloud for several months and it is the most advanced web hosting I have ever seen by a long shot.   Rackspace has always had great customer service, but I always thought the pricing was ridiculous compared to similar options.  Not anymore.. with the Cloud you can get a cheap, flexible and scalable solution (I’m still keeping an eye on reliability.. time will tell).  Currently I have 9 servers running on the cloud, but I am still waiting to move my flagship site to the cloud..  reliability and performance seem great so far, but I want to give it a few more months before I commit my highest traffic site to it.  Its pretty easy to get a Rails stack running on the cloud using a simple shell script.  Here is what I am currently using.  Note that some of the versions may be outdated when you read this article, so you may need to update the script.  Once you have this server loaded, you can create a backup and then create more servers based on that backup.  You can literally have a working dedicated Rails server in less than 5 minutes.  I am using a CentOS 5.4 server on the cloud

Server setup shell script:  setup.sh

##############
#  SETUP.SH  #
##############

#————————————————————————————————————-
# INSTALL SOFTWARE AND DEPENDENCIES WITH YUM
#————————————————————————————————————-
rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-3.noarch.rpm
yum -y install mysql mysql-server mysql-devel gcc make zlib zlib-devel openssl openssl-devel git expect pcre pcre-devel readline-devel libxml2-devel libxslt-devel

#————————————————————————————————————-
# CREATE A DIRECTORY FOR DOWNLOADED FILES AND INSTALL RUBY
#————————————————————————————————————-
mkdir /home/Downloads
cd /home/Downloads
wget ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p174.tar.gz
tar -xvf ruby-1.8.7-p174.tar.gz
cd ruby-1.8.7-p174
./configure
make
make install

#———————————————————
# INSTALL RUBYGEMS
#———————————————————
cd ..
wget http://rubyforge.org/frs/download.php/60718/rubygems-1.3.5.tgz
tar -xvf rubygems-1.3.5.tgz
cd rubygems-1.3.5
ruby setup.rb
cd /

#————————————————————————————————————-
# INSTALL GEMS – YOU WILL NEED RAILS, MONGREL AND PROBABLY MYSQL.
# THE OTHERS ARE JUST SOME COMMON ONES I USE.
#————————————————————————————————————-
gem install –no-rdoc –no-ri rails -v=2.3.2
gem install –no-rdoc –no-ri mime-types
gem install –no-rdoc –no-ri mysql — –with-mysql-lib=/usr/lib64/mysql
gem install –no-rdoc –no-ri fastercsv
gem install –no-rdoc –no-ri mongrel mongrel_cluster
gem install –no-rdoc –no-ri json
gem install –no-rdoc –no-ri mechanize

#————————————————————————————————————-
# CHANGE MYSQL TO STARTUP SCRIPT AND START SERVER
#————————————————————————————————————-
mv /etc/rc3.d/K36mysqld /etc/rc3.d/S36mysqld
/etc/init.d/mysqld start

#————————————————————————————————————-
# INSTALL NGINX WEB SERVER & copy a working config file (you create this) to the config dir
#————————————————————————————————————-
wget http://sysoev.ru/nginx/nginx-0.7.64.tar.gz
tar -xvf nginx-0.7.64.tar.gz
cd nginx-0.7.64
./configure –sbin-path=/sbin/nginx –conf-path=/usr/local/nginx/nginx.conf –pid-path=/usr/local/nginx/nginx.pid –with-http_ssl_module –with-md5=auto/lib/md5 –with-sha1=auto/lib/sha1
make
make install
# THIS IS A DEFAULT NGINX CONFIG THAT I UPLOAD TO THE SERVER.  COPY NGINX CONFIG TO THE APPROPRIATE DIR & RESTART
cp /root/nginx.conf /usr/local/nginx/nginx.conf
/sbin/nginx

#————————————————————————————————————-
# OPEN UP SECURITY FOR MONGREL
#————————————————————————————————————-
echo 1 >/selinux/enforce
/usr/sbin/setsebool -P httpd_can_network_connect=1

#————————————————————————————————————-
# OPTIONAL – SECURITY – THIS IS A DEFAULT IPTABLES SCRIPT I UPLOAD TO THE SERVER.  COPY IPTABLES SCRIPT OVER
#————————————————————————————————————-
cp /root/iptables_config /etc/sysconfig/iptables
/etc/init.d/iptables restart

#*******************************************************************************************************
# NOTE: THE REST OF THIS SCRIPT IS USED TO AUTOMATE PULLING YOUR APP CODE FROM GITHUB,
# RAKING THE DB, AND ADDING STARTUP SCRIPTS FOR MONGREL, AND STARTING MONGREL INSIDE
# YOUR RAILS APP DIR.  IF YOU ARE USING CAPISTRANO, SOME OF THIS IS PROBABLY NOT NECCESSARY, BUT I
# HAVE BEEN TOO LAZY TO LEARN CAP.. THIS METHOD WORKS FINE FOR WHAT I DO
#*******************************************************************************************************

#————————————————————————————————————-
# OPTIONAL – CREATE RAILS DIR.. I ALWAYS USE THIS DIRECTORY.. MODIFY TO YOUR TASTE-> /RAILS_APPS
#————————————————————————————————————-
mkdir rails_apps

#————————————————————————————————————-
# OPTIONAL – IF YOU ARE USING GITHUB, YOU WILL WANT TO COPY YOUR SSH KEYS.
# I JUST FTP THE KEY FILES TO MY SERVER ALONG WITH THE SETUP SCRIPT.
# I PLACE ALL THE FILES I AM USING IN /ROOT/
#————————————————————————————————————-
mkdir /root/.ssh
cp /root/id_rsa.pub /root/.ssh/id_rsa.pub
cp /root/id_rsa /root/.ssh/id_rsa
cp /root/known_hosts /root/.ssh/known_hosts
chmod 700 /root/.ssh/*

#————————————————————————————————————-
# OPTIONAL – GET THE APP FROM GITHUB
#————————————————————————————————————-
cd /rails_apps
git clone git@github.com:yourusername/yourproject.git
mkdir /rails_apps/yourproject/log
mkdir /rails_apps/yourproject/tmp/pids

#————————————————————————————————————-
# OPTIONAL – RAKE THE DB
#————————————————————————————————————-
cd /rails_apps/yourapp
rake db:migrate

#————————————————————————————————————-
# OPTIONAL – ADD MONGREL STARTUP SCRIPT AND PUT IN RC.LOCAL AND START MONGREL
#————————————————————————————————————-
cp /root/server_start.sh /rails_apps/server_start.sh
chmod 700 /rails_apps/*.sh
echo “/rails_apps/start_server.sh” >> /etc/rc.local
/rails_apps/server_start.sh

This worked for me last time I tried it. YMMV

Using named_scope for object filtering

One of the cool new features in Ruby on Rails is the named_scope.  You can put a named_scope in your model as a way to do a customized find by just calling a name.  Here is an example of a simple named_scope.  This named scope gets all the widgets that are blue.  You can call it like this: Widget.blues

class Widget < ActiveRecord::Base
  named_scope :blues, :conditions=> "color='blue'"
end

Thats pretty cool huh? Very DRY if you are going to be using this quite a bit. Here is a more complex example where you can insert a parameter using lambda.

  named_scope :above_price, lambda { |*args| {:conditions => ["price>?",args.first] } }

  #get all widgets with price above $25
  Widget.above_price(25)

Even cooler…
Now here is something I like even better that I was not expecting, but it seems to work. I am able to chain these together to filter out different widgets based on criteria. This is useful in an application that needs to allow users to filter out certain content based on criteria. Lets say you have a list full of widgets of all kinds, but the user only wants to see widgets that are blue and cost more than $25. You can add some check boxes and inputs to your view with a submit button and let the user choose different criteria so they can find the widget they are looking for. Then you can filter out the widgets with named scopes in your controller

widgets = Widget.all
if params[:is_blue]
  widgets = widgets.blues
end
if params[:above_price]
  widgets = widgets.above_price(params[:above_price])
end
#now you would have all the blue widgets above whatever price the user entered in the field "above_price"

#If I remember correctly, you can also call them by chaining them together if you need to
widgets = widgets.blue.above_price(25)

I think this is a pretty elegant solution for doing object filtering in Rails. Go named_scope! You crazy!

Creating a Facebook (iFrame) app using Ruby on Rails & Facebooker

I recently had the pleasure of creating a Facebook app with Ruby on Rails. At first I thought I might try to write my own code to interact directly with the Facebook API, but after looking at it a bit, I decided I better find some existing software to save me some time. Really the only good choice is Facebooker. rFacebook is also another choice, but it looks like it was abandoned a while ago. Apparently Facebook has made quite a few changes to the API in the last year or so. After diving into development I found that Facebooker is not exactly up to date either. A workaround is pretty easy to code, so I will show you that here.

First thing you want to do when creating a facebook app is to log on to Facebook Developer -> http://www.facebook.com/developers/. They have 2 sites for this and dont ask me why. The other one is developer.facebook.com. Do not use that one, because there is no way to edit your app from that page.

From http://www.facebook.com/developers/, click “Set up New Application” and enter an app name. After that you will go to the “Edit” screen for your application. There are a ton of different things you can configure, like the icon, etc.., but for now you just need to worry about your canvas settings. Click “Canvas” in the left side menu. Enter a name for your app: http://apps.facebook.com/your-app-name-goes-here . Next is the most important part, the “Canvas Callback URL”. For this, you need to enter the URL of your application. Since this is going to be an iFrame application, your app will be retrieved from your servers. So I entered: http://www.myapp.com/facebook/canvas. Next make sure you select “iFrame” under canvas render method. Save your changes and its time to start coding with Rails

Here is a screenshot of my settings:
Picture 3

You will need to install facebooker as a plugin. Follow the instructions on github – http://github.com/mmangino/facebooker

Rails Stuff
1. Create a controller called “facebook_controller.rb”
2. Add an action for your canvas page. This will be like the welcome page for your application
3. Add “ensure_authenticated_to_facebook” to the top of your controller. I will talk about this in more detail later.

4. Set up your view (canvas.html.erb) with whatever you want in it. “Hello world”

class FacebookController < ApplicationController
  ensure_authenticated_to_facebook

  def canvas
  end
end

Since you are the developer and you set up the app in facebook, it will already be installed in your facebook account. You can test it by going to http://apps.facebook.com/name-of-your-app-goes-here
You should see your app render in the iframe. Now lets move on to where the app actually does something. Create a link in your view to go to another action that will count your friends

canvas.html.erb


<%=link_to "count_friends", :action=>"count_friends" %>

Now you need to add the action to your facebook_controller.rb:

def count_friends
  @count = 0
  #this session variable is set automatically by the facebooker plugin
  fbsession = session[:facebook_session]
  #iterate thru your friends.  fbsession.user grabs your user account and then .friends pulls in all your friends
  fbsession.user.friends.each do |user|
    @count += 1
  end
end

count_friends.html.erb

You have <%=@count%> friends

Woopty doo! You have a working facebook app using Ruby on Rails and Facebooker.

Now if you try to send this to your friends it will not work, because of some changes in the facebook api that were not changed in Facebooker. The main problem lies in the install_url. When your friend tries to install the facebook app, it will send them to your website (no iFrame, just your site outside facebook..no good), instead of directing them to your canvas page which contains your website in an iFrame. To fix this I just hardcoded :canvas=>”true” in this method:

starting at line #189 in vendor/plugins/facebooker/lib/facebooker/rails/controller.rb. I commented out one line and hard coded in a value for canvas. Not sure if this breaks anything else, but it seems fine so far. I am guessing facebook changed something with the fb_sig_in_cavas parameter.

      def create_new_facebook_session_and_redirect!
        session[:facebook_session] = new_facebook_session
        next_url = after_facebook_login_url || default_after_facebook_login_url
        #top_redirect_to session[:facebook_session].login_url({:next => next_url, :canvas=>params[:fb_sig_in_canvas]}) unless @installation_required
        top_redirect_to session[:facebook_session].login_url({:next => next_url, :canvas=>"true"}) unless @installation_required
        false
      end

Push this change to your server and restart your app and this should make everything show up in the iframe after the facebook user installs your app.

On a side note: if you try to use “ensure_application_is_installed_by_facebook_user” in your controller, instead of “ensure_authenticated_to_facebook”, you will get the opposite effect when you click on a link within your application. It will turn into an infinite iframe loop. Every time you click it will create another page inside the iFrame

Change a column data type with Rails migration

I always forget the syntax for this and I have to look it up, so I am going to post it here.  Maybe this will help some others out.  Basically, what I need to do is change the data type of a column in my database table.  The table “widgets” contains a column/field named “count”.  I want to change count from an integer to a float.  So first I create a rails migration with the following command.

>script/generate migration change_data_type_for_widget_count

Then I edit the migration file:   app_root/db/migrate/20091007151516_change_data_type_for_widget_count.rb.  Here is the migration syntax to change the data type of a column:

class ChangeDataTypeForWidgetCount < ActiveRecord::Migration
  def self.up
    change_table :widgets do |t|
      t.change :count, :float
    end
  end

  def self.down
    #.....
  end
end

Then you just run the rake task “db:migrate” and it will change the data type in the database table.

>rake db:migrate

Appending parameters on a will_paginate link

I was working on a section of my application that uses pagination.  I was running into a problem, though.  When will_paginate creates the pagination link it did not include some of the parameters I am using to keep the state of my application.  I tried adding the parameters onto the end of the will_paginate link like so:

<%= will_paginate @widgets, :param1=>@param1, :param2=>@param2 %>  #not working

That does not work. So I did some searching and found out that you have to include a hash named “params”.  After I added this I was able to get the parameters in my controller.

<%= will_paginate @widgets, :params=>{:param1=>@param1, :param2=>@param2} %>  #Now it works :-)

Returning an image from a Ruby on Rails controller

I am working on an app that needs to return an image file in the controller.  I send over some request parameters to the app, it goes to the controller which pulls out the params and then creates an image based on those parameters.  So instead of rendering the view, the app would need to return a png file.  The send_data method was designed to do exactly this.  Here is the code:

data = File.open(your_image_path,’rb’).read
send_data(data , :filename => your_image_name, :type=>’image/png’)

Customizing Rails scaffolding to suit your app

OK…. I wish I would have tried this earlier.  It would have saved me alot of time.  The default scaffolding that comes with Rails 2.3 is pretty good out of the box, but it is never going to fit exactly what you need for every app.  One of my apps is a bit different than the default scaffolding setup.  I have integrated Highslide Javascript Library into my app for the administration functions.  It works very well.  If you havent used it, its basically a lightbox script that allows you to use iframe content (and alot of other stuff) inside the modal popup.  So when I need to edit or add something, I just click on it and a modal popup appears.  When I save it, it closes the modal popup and refreshes the page via ajax or a meta refresh depending on the needs of that page.  It gives you a nice smooth workflow.  Some of the calls to highslide are baked into the controller and views.  This is just one example of things I change from the default scaffolding.  There are plenty of other things I like to add to every controller and view, such as my home-brewed security methods and a shared form for edit/new.  All of this takes alot of time.

In the past I used scaffolding as my base point before I made all the changes.  It was still faster than coding it from scratch, but what if I could make scaffolding generate exactly what I want every time?!  It is actually much easier than I anticipated.  It took me about as much time to change the scaffolding as it did to change one of the resources I created with the scaffolding.  Here is what you need to do.

1.  Locate your Rails source directory – on my Mac it is located here: /Library/Ruby/Gems/1.8/gems/rails-2.3.2    I use Aptana Studio for development (yeah I know.. I should use Textmate.. whatever dude), so I just added the rails source as a project so I could edit it directly from the IDE.

2. Go to this directory ->  rails-2.3.2/lib/rails_generator/generators/components/scaffold/ and first take a look at scaffold_generator.rb.   This class runs the scaffolding which creates all the files using the templates in the subdirectory /templates.  The first thing I wanted to customize in the scaffolding was adding a partial named form.  So I edited the method scaffold_views and added it to the end of the array like so:

def scaffold_views
#ADDED BY JOHN – _form
%w[ index show new edit _form]
end

Next I wanted to get rid of the layout file and css that are created when scaffolding runs.  I rarely use them since I use the base layout and main css for my whole site, so I commented out these lines:

#m.directory(File.join(‘app/views/layouts’, controller_class_path))

……

# Layout and stylesheet.
#m.template(‘layout.html.erb’, File.join(‘app/views/layouts’, controller_class_path, “#{controller_file_name}.html.erb”))
#m.template(’style.css’, ‘public/stylesheets/scaffold.css’)

The possibilities in this file are endless.  Just add/remove whatever you want to/from def manifest

3.  Next you can edit the templates that you want to customize.    /Library/Ruby/Gems/1.8/gems/rails-2.3.2/lib/rails_generator/generators/components/scaffold/templates

I edited the controller template and all of the views.  You will notice that the template uses ruby code for some of the generation:

<% for attribute in attributes -%>
<th><%= attribute.column.human_name %></th>
<% end -%>

Basically you can code it with ruby and it the generator will spit out a string which it writes to the .rb files.  Think of it as code inside code.

I am not exactly sure what this means though, maybe someone can enlighten me:  <%%=

<%%= link_to ‘Show’, <%= singular_name %> %>

I am assuming its something like escaping a quote.. but I dont really have a clue.  I was still able to get it to output the code I wanted without knowing exactly what it does.

Anyways… You can change these files around to spit out the code you want.  Its very easy to customize and it can save you a ton of time if you are a heavy scaffolding user.  There is probably a way to turn this into a plugin or use a symlink, so you don’t have to edit the original files, but I guess I am lazy.

Ruby on Rails & MySQL Master Slave replication tutorial

I have been running a web app on 1 server since 2005 and we have seen a good bit of traffic. We are currently ranked 8200 according to quantcast.com with just over 200,000 visitors a month. Traffic is picking up and I expect it to almost double in Q4.  NewRelic shows all green right now with about 250ms response time which is fine.  But I am worried that if traffic doubles, the database may become a little stressed.  So I have decided to be pro-active and go ahead and add another server to the database layer.  Right now, I have Nginx in front of mongrel cluster (4 instances) and MySQL as the database.  All of this is running on the same server.  So my new setup will be:

Server 1:  Nginx > Mongrel Cluster > MySQL master

Server 2: MySQL slave

NOTE:  RailsEnvy has a really good video series on scaling your database (and many other great videos on other subjects), so you may want to check that out just to get an overview of the process.

In order to get this working, Rails will need to be able to perform writes only on master and reads on both master and slave.  But, lets talk about setting up the replication first.  Here is how you set it up:

1. Set binary logging on the master server.  Edit /etc/my.conf

[mysqld]
# add this line anywhere in the [mysqld] section
log-bin=mysql-bin

2. Set the server ids on master and slave in /etc/my.conf

#MASTER
[mysqld]
server-id=1
#SLAVE
[mysqld]
server-id=2

3. Grant the slave permission to pull the data from the master (replace XX.XX.XX.XX with your slave’s IP address)

mysql>grant replication slave on *.* to ‘replication’@XX.XX.XX.XX identified by ’slavepass’;

4. Perform a read-lock on the master

mysql> FLUSH TABLES WITH READ LOCK;

5. Record the file and position on the master.  It should give you output like this.  Record the file and position to use later

mysql> SHOW MASTER STATUS;
+——————+———-+————–+——————+
| File                            | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+——————+———-+————–+——————+
| mysql-bin.000001 |  1467771 |              |                  |
+——————+———-+————–+——————+

6. Export data on master using mysql_dump

> mysqldump –all-databases –lock-all-tables >dump.db

7. Unlock the tables on master

mysql> UNLOCK TABLES;

8. FTP the tar file of the mysql snapshot to your slave box

9. Stop MySQL on the slave

/etc/init.d/mysqld stop

10. You need to start slave using –skip-slave option.  I changed the init.d script to do this.  Probably an easier way to do it, but whatever..  It was line 61 in /etc/init.d/mysqld.  Just make a backup of that file, so you can replace it after you start mysql

# Pass all the options determined above, to ensure consistent behavior.
# In many cases mysqld_safe would arrive at the same conclusions anyway
# but we need to be sure.
/usr/bin/mysqld_safe   –skip-slave –datadir=”$datadir” –socket=”$socketfile” \
–log-error=”$errlogfile” –pid-file=”$mypidfile” \
>/dev/null 2>&1 &

11. Start mysql on slave.  Then change /etc/init.d/mysql back to the original (without –skip-slave option)

> /etc/init.d/mysqld start

12. Import the data into the slave

> mysql < dbdump.db

13. Configure master on the slave box using the values for your host,user, and password.  Fill in the last 2 with the values you recorded from step 5

mysql> CHANGE MASTER TO
-> MASTER_HOST=’your_host’,
-> MASTER_USER=’replication’,
-> MASTER_PASSWORD=’user_password’,
-> MASTER_LOG_FILE=’mysql-bin.000001′,
-> MASTER_LOG_POS=1467771;

14. Start the slave replication on the slave server

mysql> START SLAVE;

Now your slave should be getting updates from the master. Go ahead and change some stuff on the master and see if the slave replicates.  You can also view the status of the slave by using this mysql command:

mysql> SHOW SLAVE STATUS;

CONFIGURING RAILS TO WORK WITH MASTER/SLAVE

The next step in the process is to get rails to communicate with the master/slave setup. The easiest way to do this is to use the machoism plugin.  You can get it at github.  There are a few different branches of this, but I am using the one from technoweenie (I had problems getting the mislav version to work)

http://github.com/technoweenie/masochism/tree/master

It is easy to install.  Just go to your rails app root directory and issue the following command

> script/plugin install http://github.com/technoweenie/masochism.git

Next you need to configure your database.yml to for masochism.  One interesting thing I found out that I was not aware of is that you can set the login as a variable, so you can reuse it later in the config file.  Here is my config

login: &login
adapter: mysql
database: yourapp_production
username: yourusername
password: yourpassword# default configuration (slave)
production:
<<: *login
host: XX.XX.XX.XX

# setup for masochism (master)
master_database:
<<: *login
host: XX.XX.XX.XX

Then add the following lines to your environment.rb file

# in environment.rb
config.after_initialize do
  if Rails.env.production?
    ActiveReload::ConnectionProxy::setup!
  end
end

Restart your app server and you should be good to go.

I thought I was until I figured out that there was a conflict between masochism and another plugin I use called use_db.  Masochism seems to be overriding the database config for use_db, so it is restricting me to only the master/slave databases.  I will post when I figure out a solution to this.

Time Zone troubles

Well… I have been ignoring this for a few weeks, because it is not a huge deal, but I thought I might as well fix this.

Problem: The created_at and updated_at dates do not match my system time.  Every time I save a record, ActiveRecord records the date 5 hours ahead of my local time.  This was messing up some of my cronjobs that run 5 minutes before midnight.  Some of the records were not being found, because the date was recorded five hours in the future.

Solution:  After some digging on Google, I figured out that this is set in config/environment.rb.

# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run “rake -D time” for a list of tasks for finding time zone names.
config.time_zone = ‘UTC’

I did some more digging and found that UTC is 5 hours ahead of ET, so ActiveRecord was using this timezone instead of my local timezone.  The first fix I tried did not work, which was to chagne config.time_zone =”Eastern Time (US & Canada)’  I restarted my app server and still saw created_at was five hours ahead of my server’s system time.  So then I just commented it out and restarted the app again.  Walah.. it works now.  So the correct config to use system timezone is as follows:

# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run “rake -D time” for a list of tasks for finding time zone names.
#config.time_zone = ‘Eastern Time (US & Canada)’

No more worries