Thursday, December 20, 2012

Deploying Rails Application on Windows Azure (Ubuntu VM)

In the last weeks, I've been exploring my options for hosting a Ruby on Rails application. An important step was Windows Azure. I registered for a 90-day trial to give it a try. One of the great services Microsoft has offered was the Ubuntu virtual machine ( :D ). So this tutorial can be useful even with a normal Ubuntu machine (except the Endpoints part) since I'm treating it like an Ubuntu machine regardless of the Azure service it is hosted on.

In this tutorial, I will create a new Ubuntu 12.04 LTS virtual machine, install Ruby ( 1.9.3 ) and Rails ( 3.2.9), install Passenger gem, install Apache server and connect it to Rails application. And finally, some important notes about getting your project up and running.

Create an Ubuntu 12.04 LTS VM:

- In you portal, from the bottom right corner click: New -> Compute -> Virtual Machine -> From Gallery.

- Scroll the list to Ubuntu Server 12.04 LTS and click it.

- Fill the required data about the VM name, the username to create in that machine and password (you will need them A LOT), and number of cores running this machine (I'm on trial version, so 1 is enough to try it).

- Choose the appropriate DNS name (your service URL) and the suitable place for the service.

- You are done with the VM creation.








Create Endpoints for SSH, FTP, Apache:

- The endpoint is the way of communication between the VM and the outer world. Each endpoint takes a public port (the one you call) and a private port (the one the VM listens to).

- Click on the created VM, select the ENDPOINTS tab. You will find that the SSH endpoint is already created on port 22.

- Now create two other ports for FTP (port 21) and Apache service (default port, port 80). Names of the endpoints do not matter, but you'd better give them meaningful names.

- Do not forget to start the VM before the next step.



Access the Ubuntu Server Through SSH:

- All you interaction with the server will be though SSH via port 22. Although you can then install a GUI package and remote access the server, I choose not to do this because of the extra space and CPU cycles it takes, and common, this is Linux, you only need a terminal to have fun!

- Since I'm using windows, I use a nice SSH client: PuTTY. Just download, run, enter your server's name and click 'open', enter usrename/password in the terminal that shows up.

- Now you are on board, let's install Ruby and Rails.

Note: You can copy and paste inside PuTTY terminal as follows: highlight the terminal text with the mouse to copy it, and right click on the terminal to paste.

Install Ruby and Rails:

- Some people prefer to install Ruby using 'rbenv' or 'rvm', but I prefer simplicity. I'll just install Ruby directly.

- If you cannot 'sudo apt-get install ruby -v 1.9.3' directly, you may follow these steps:

sudo apt-get update

sudo apt-get install ruby1.9.1 ruby1.9.1-dev \
  rubygems1.9.1 irb1.9.1 ri1.9.1 rdoc1.9.1 \
  build-essential libopenssl-ruby1.9.1 libssl-dev zlib1g-dev

sudo update-alternatives --install /usr/bin/ruby ruby /usr/bin/ruby1.9.1 400 \
         --slave   /usr/share/man/man1/ruby.1.gz ruby.1.gz \
                        /usr/share/man/man1/ruby1.9.1.1.gz \
        --slave   /usr/bin/ri ri /usr/bin/ri1.9.1 \
        --slave   /usr/bin/irb irb /usr/bin/irb1.9.1 \
        --slave   /usr/bin/rdoc rdoc /usr/bin/rdoc1.9.1

# choose your interpreter
# changes symlinks for /usr/bin/ruby , /usr/bin/gem
# /usr/bin/irb, /usr/bin/ri and man (1) ruby
sudo update-alternatives --config ruby
sudo update-alternatives --config gem

# now try
ruby --version

Then install Rails
sudo gem install rails --no-rdoc --no-ri

Install Passenger and Apache:

- The passenger gem is the connection between Apache server and your rails app. We need to install Passenger, Apache, and the Passenger's Apache module.
(edit: I've been facing some issues with v3 when restarting the server, so I added --pre to install the yet-not-released v4. In case you have v4 by the time you read this, just proceed with the command as it is.)
sudo gem install passenger --no-rdoc --no-ri
sudo passenger-install-apache2-module

- The previous line will open an installation wizard to guide you though the installation of the module. It is supposed to ask you for extra packages to install. In my case, these were the packages:

sudo apt-get install libcurl4-openssl-dev apache2-mpm-prefork apache2-prefork-dev libapr1-dev libaprutil1-dev

- After installing the missing packages, run the module installation one more time.
sudo passenger-install-apache2-module

- Now the module will install and ask you to add some paths to the apache config file which is supposed to be in 'etc/apache2/apache2.conf'.

- It will also give you an example of how to add a virtual host to get your rails app running through Apache.

- To open the file from terminal, use:
sudo nano etc/apache2/apache2.conf

- Here is what I had to add to my config file:
LoadModule passenger_module /var/lib/gems/1.9.1/gems/passenger-3.0.18/ext/apache2/mod_passenger.so
PassengerRoot /var/lib/gems/1.9.1/gems/passenger-3.0.18
PassengerRuby /usr/bin/ruby1.9.1

<VirtualHost *:80>
  ServerName adly-test.cloudapp.net
  # !!! Be sure to point DocumentRoot to 'public'!
  DocumentRoot /home/adly/apps/testrails/public

  <Directory /home/adly/apps/testrails/public>
     # This relaxes Apache security settings.
     AllowOverride all
     # MultiViews must be turned off.
     Options -MultiViews
  </Directory>
</VirtualHost>



Upload Your Project Using FTP:

- Some tutorials go for using Github and some packaging tools, but I do not like that. I want to upload my files myself. So I'm using "vsftpd" for the Ubuntu Server side, and FileZilla on the my Windows side.
(You can use Git without the need to Capistrano or Unicorn gems, remember it is just a Linux machine. Git is better for continuous development.) (you can also use WinSCP client for SFTP protocol and forget about FTP client and end point)

- Install vsftpd
sudo apt-get install vsftpd

- Edit the config file:
sudo nano /etc/vsftpd.conf

- Modify the following lines:
#local_enable=YES
to
local_enable=YES

#write_enable=YES
to
write_enable=YES

anonymous_enable=YES
to
anonymous_enable=NO

- Restart the FTP server
sudo service vsftpd restart


- Now we are done with the server side. On the client side, install FileZilla and open it.

- From "Edit" -> "Settings", make sure the settings are as follows:



- From the main screen, enter your service URL, username, password, and port 21 and click "Quick Connect".

- Now you will have the server folders on the right, and your local files on the left. Move and modify however you want.




Fine tuning to get things working right (do not trust HelloWorld tutorials):

If you create a dummy project using 'rails new testrails', it will probably work fine. But those tutorials do not give the complete case of a real application, so here are some extra steps to do to avoid errors or unknown behavior.

Rails and Apache log file:

- It happens that the Rails app will write to the Apache log file, so it is better to locate a custom path to that log file instead of the default path. I prefer logging errors in a log file in the same directory of Rails log folder. So I add the following line to the VirtualHost configuration in the apache config file. I also prefer declaring the Rails working environment explicitly to avoid any human/machine errors in the future.

ErrorLog /home/adly/apps/testrails/log/error.log
RailsEnv production

so it is now like

LoadModule passenger_module /var/lib/gems/1.9.1/gems/passenger-3.0.18/ext/apache2/mod_passenger.so
PassengerRoot /var/lib/gems/1.9.1/gems/passenger-3.0.18
PassengerRuby /usr/bin/ruby1.9.1

<VirtualHost *:80>
  ServerName adly-test.cloudapp.net
  # !!! Be sure to point DocumentRoot to 'public'!
  DocumentRoot /home/adly/apps/testrails/public
  ErrorLog /home/adly/apps/testrails/log/error.log
  RailsEnv production

  <Directory /home/adly/apps/testrails/public>
     # This relaxes Apache security settings.
     AllowOverride all
     # MultiViews must be turned off.
     Options -MultiViews
  </Directory>
</VirtualHost>


Assets Compilation:

- You have to set this line to true
config.assets.compile = true
in {app_root}/config/environments/production.rb

and do not forget to compile the assets whenever changed:
rake assets:precompile
[check the "ExecJS Error, and libv8 / therubyracer version errors" section at the bottom if you have an error]

DB migration:

- If you have a database in your project, the declaration of "production" environment in the apache config file is not enough, you have to declare it explicitly when migrating:

rake db:migrate RAILS_ENV="production"


FILES PERMISSIONS:

- This is a very important step, since some files are still owned by you with zero permissions to other users.

- The quick and not-so-liberal permission is 755 for all the application folder
sudo chmod -R 755 /path/to/your/app/


- (just in case you face problems later, make this step) Double check that you give rwx privileges to the DB, assets, log and tmp folders for the user running this app whenever you add new files. For example:
sudo chmod -R 755 /path/to/your/app/app/assets
sudo chmod -R 755 /path/to/your/app/db
sudo chmod -R 755 /path/to/your/app/tmp
sudo chmod -R 755 /path/to/your/app/log

- Note: Do not forget to check the permissions of any new file you upload.


ExecJS Error, and libv8 / therubyracer version errors:

- Now this is something very common with a lot of workarounds. It is on Rails 3 because of some compatibility issues. You should get an error like "Could not find a JavaScript runtime. See https://github.com/sstephenson/execjs for a list of available runtimes.".

- To solve this problem, open your Gemfile, and add the following lines under the "group :assets do" section:

gem 'therubyracer', '0.11.0beta8', :platforms => :ruby
gem 'libv8', '~> 3.11.8.3', :platform => :ruby
then do not forget to "bundle install". No need for 'sudo', let the gems be installed locally.

[update: libv8 compilation may take several minutes if there is no matching binary for your OS version and specified version number. On another server I had to change the version to '3.11.8.3' to save time and CPU usage. Click here for more: Installing with native extensions stall ]

One final note: Do not forget to restart Apache server for the changes to take effects.
sudo service apache2 restart

Sunday, December 16, 2012

Ruby on Rails Facebook Application using Koala Gem

Here is a another Facebook application example (check the python example), but this time it is with Ruby on Rails using Koala gem. The great thing about Koala is how it is easy integrated and straight forward. Here are the steps from the very beginning:

1- Create a new Ruby on Rails project:
rails new rorApp


2- Delete ./public/index.html file as it is not needed.


3- In Gemfile add:
gem 'koala', '1.3.0'


4- In ./config/initializers folder, add a constants.rb file with the following data:
APP_ID= '123456789' # please change!
APP_SECRET= '1b2n3n5n6n7m8m9n9m0m' # please change!
SITE_URL = 'http://localhost:3000/' # please change!


Where APP_ID and APP_SECRET are the values you have from the Facebook application you create in the developers section, and the SITE_URL is the root URL of your application website ('http://localhost:3000/' if our case of testing locally)


5- In ./config/routes.rb, add the following routes:
root :to => 'home#index'

match '/index' => 'home#index'
match '/login' => 'home#login'

The first line makes calling the root of your application points to your index page.

6- In ./app/views folder, create a 'home' folder and inside it create a 'index.html.erb' with any content you want to show. It will only be available after user logs in.

7- In ./app/controller folder, create a 'home_controller.rb' file with the following content:
class HomeController < ApplicationController
            
    def index   
        if params[:code]
            # acknowledge code and get access token from FB
            session[:access_token] = session[:oauth].get_access_token(params[:code])
        end  

        # auth established, now do a graph call:
        @api = Koala::Facebook::API.new(session[:access_token])

        begin
            @user_profile = @api.get_object("me")
        rescue Exception=>ex
            puts ex.message
            #if user is not logged in and an exception is caught, redirect to the page where logging in is requested
            redirect_to '/login' and return
        end

        respond_to do |format|
         format.html {   }    
        end
    end
    
    #########################################################
    
    def login
        session[:oauth] = Koala::Facebook::OAuth.new(APP_ID, APP_SECRET, SITE_URL + '/')
        @auth_url =  session[:oauth].url_for_oauth_code(:permissions=>"read_stream publish_stream")  

        redirect_to @auth_url
    end
    
    #########################################################
end

The code explains itself, but here is a quick explanation: When the index page is called, the application looks for the access token for the current user in the cookies ('session[]'). If not found, it will redirect to the login page. The login handler will do the authentication headache for you with the extra permissions you want and passes the root URL as a call back so that Facebook will call it after authentication is done. Then when the index page is called one more time with an authentication code, it will be used to get the access token needed for any later requests.
So when the user opens the index page, he will notice some redirects and message boxes asking for allowing your application and for extra permissions then he is redirected to your index page.

8- Now you have your application ready. Just run 'bundle' from your terminal (make sure you are in the root folder of your application) to install Koala and then 'rails s' to start the web service.
bundle
rails s


9- Finally here are some Graph API calls that you may use to get profile data, friends list, post text/image and text to user's wall.
@api = Koala::Facebook::API.new(session[:access_token])
@user_profile = @api.get_object("me")
@friends = @api.get_connections(user_profile["id"], "friends")
@api.put_wall_post("hi")
@api.put_picture("http://www.example.com/image.png", {:message => "an image of mine!"})


You can learn more about Koala gem from this link: https://github.com/arsduo/koala