Thursday, June 19, 2008

Ruby on Rails Security Guide

Ruby on Rails does a decent job in handling security concerns in the background. You will have to configure your application to avoid few security attacks while plugins would be required for many security concerns which are not at all or poorly managed by rails.
In this article I have described the security issues related to a ruby on rails web application. I have followed DRY by linking to articles with good explanation and solutions to security concerns wherever required. This guide can also be used as a quick security check for your current web application.

Authentication
Authentication is the foremost requirement of most of the web applications to authenticate and give privileges to their users. Apart from normal authentication mechanism rails have plugins for OpenID, CAS and Access Control. Build your own authentication system only if your requirements are very unique or you do not trust other implementations.Plugin - Restful Authentication (recommended) - easy to use and you can tweak it according to your requirements.
http://railscasts.com/episodes/67 http://svn.techno-weenie.net/projects/plugins/restful_authentication/ Build your own authentication. You should rarely need to do this ... Restful Authentication is quite flexible.
http://www.aidanf.net/rails_user_authentication_tutorial OpenID - a universal authentication system to avoid use of multiple username and password on the Internet. OpenID is getting quite famous now-a-days.
http://media.railscasts.com/videos/068_openid_authentication.mov http://agilewebdevelopment.com/plugins/openidauthentication Access Control : To easily proivde different priviliges to your users. There are a lot of cool plugins available for access control.
https://opensvn.csie.org/traccgi/tobionrailshttp://code.google.com/p/rolerequirement/http://agilewebdevelopment.com/plugins/activeacl_rails_authorization_system Centralized Authentication Server - is used to implement single login/password for your users across multiple application. It can also be used for a single sign-on system. For example, Gmail and Google Reader have a single sign-on between them.
http://agilewebdevelopment.com/plugins/cas_authentication_filter Use Google Authentication API to let your users login using their google username and password.
http://rubyforge.org/projects/asgoogleaccount/ More Plugins :
Rails inbuilt Authentication -
http://ryandaigle.com/articles/2006/12/4/whats-new-in...Acts_as_authenticated - http://technoweenie.stikipad.com/plugins/show/User+AuthenticationSuper Simple Authentication - http://ariejan.net/2007/08/24/super-simple-...
- Model -
SQL Injection
The problem arises when metacharacters are injected into your queries to database. Rails has a very good support to avoid SQL injection if you follow conventions in issuing queries to your database.Description :
http://manuals.rubyonrails.com/read/chapter/43 Alternate Solution - use hash for specifying conditions in #find
http://weblog.rubyonrails.org/2006/11/26/1-2-new-in-activerecord
Activerecord Validation
To validate the contents of model object before records are created/modified in the database. Activerecord validations are very useful over database data-type constraints to ensure values entered into the database follow your rules. You might have javascript validations for forms but javascript can easily be switched off. Use javascript validations only for better user experience.Description :
http://rails.rubyonrails.com/classes/ActiveRecord/Validations/ClassMethods.html Conditional validation using :on and :if options. Checkout this cool video
http://media.railscasts.com/videos/041_conditional_validations.mov Be careful using validates_uniqueness_of, it has problems when used with :scope option. Open bug tickets :
http://dev.rubyonrails.org/ticket/5608http://dev.rubyonrails.org/ticket/9235http://dev.rubyonrails.org/ticket/8811http://dev.rubyonrails.org/ticket/8774 Use :allow_blank to pass validations if value is nil or empty string
http://ryandaigle.com/articles/2007/9/5/what-s-new-in-edge-rails-validations-now-allow_blank Testing Validations - do read the comments in this article
http://blog.jayfields.com/2006/12/rails-unit-testing-activerecord.html Useful Tips
Its easy to manage 'nil' values using :allow_nil, its quite handy. For ex: set :allow_nil => true in validates_uniqueness_of to check uniqueness of non-nil values and ignore nil values
validates_presence_of is not required if you are using validates_format_of, unless regular expression accepts empty string.

Creating records directly from parameters
While creating database records directly from form params, a malicious user can add extra fields into the params and manually submit the web page which will set values of fields which you do not want user to set.Description :
http://manuals.rubyonrails.com/read/chapter/47 Alternate Solution - Trim the parameters to keep the required keys and remove the others.
http://wiki.rubyonrails.org/rails/pages/HowToPreventFormInjection
- Controller -
Exposing methods
Use private and protected in controller for methods which should not be actions. Actions are pubic methods and can be invoked from the browser.hide_action : If non-action controller methods must be public, hide them using hide_action.
http://www.mathewabonyi.com/articles/2006/08/11/hide_action-a-hidden-treasure Be careful of bypassing private and protected using meta-programming
http://dev.zeraweb.com/design-blog-14
Authorize parameters
Always authorize user request. By tweaking form parameters or url a user can send request to view/modify other users information if there is no proper authorization of parameters.For example :
1
2
3
4
5
6
7
## To find information of an order which belongs to a particular user.
#Incorrect :
@order = Order.find(order_id)
#Correct :
@order = @user.orders.find(order_id)Do not ignore hidden fields - a user can easily modify their value, so suspect them similar to params[:id]
http://searchsecurity.techtarget.com/tip/1,289483,sid14_gci1153816,00.html
Filter sensitive logs
Prevent logs of sensitive unencrypted data using #filter_parameter_logging in controller. The default behavior is to log request parameters in production as well as development environment, and you would not like logging of password, credit card number, etc.Video Tutorial
http://railscasts.com/episodes/9
Cross Site Reference(or Request) Forgery (CSRF)
In a CSRF attack, the attacker makes victim click on a link of his choice which would contain a GET/POST request and causes web application to take malicious action. The link could be embedded in a iframe or an img tag. Its recommended to use secret token while communicating with user to avoid this attack.
Its little complex to understand this attack. So, only those readers who are very enthusiastic to know about it, please read the Description below. Rest can directly move ahead to use the plugin.Description :
http://isc.sans.org/diary.html?storyid=1750 http://en.wikipedia.org/wiki/Cross-site_request_forgery Use Get and Post appropiately (note : Both get and post are vulnerable to CSRF)
http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 Example - Gmail CSRF security flaw
http://ajaxian.com/archives/gmail-csrf-security-flaw Plugin - CSRF Killer (recommended) - it requires edge rails
http://svn.techno-weenie.net/projects/plugins/csrf_killer/http://activereload.net/2007/3/6/your-requests-are-safe-with-us
Secure action -
http://code.google.com/p/secure-action-plugin/Security extension - http://svn.aviditybytes.com/rails/plugins/security_extensions/
Minimize session attacks
If an attacker has session-id of your user, he can create HTTP requests to access user account. An attacker can get session-id by direct access to user machine or is able to successfully run malicious scripts at user machine. In this section we will talk about how to avoid or minimize the risk if attacker has user session-id. Following steps are helpful:
Store IP Address, but creates problem if user moves from one network to another.
Create a new session everytime someone logs in.
Expire session on user logout, user is idle for a time period or on closing of browser/tab. For maximum security expire sessions on all the three conditions. Code for session expiry on timeout
1
2
3
4
5
6
7
8
9
10
11
## Timeout after inactivity of one hour.
MAX_SESSION_PERIOD = 3600
before_filter :session_expiry
def session_expiry
reset_session if session[:expiry_time] and session[:expiry_time] <>
http://www.naffis.com/2007/5/22/automatically-expiring-sessions-in-rails Do not put expiry time in the cookie unless your cookie information is properly encrypted. If not, use server side session expiry.
http://wiki.rubyonrails.org/rails/pages/HowtoChangeSessionOptions Persistent session / login in rails - global setting in enviornment.rb
ActionController::Base.session_options[:session_expires] = say after two yearsPersistent session / login in rails - to give your users a feature - remember me
http://blog.codahale.com/2006/04/08/dynamic-session-expiration-times-with-rails/ http://www.onrails.org/articles/2006/02/18/auto-login http://livsey.org/2006/6/30/persistent-logins-in-rails
Stop spam on your website from DNS Blacklist
Avoid access to your website from IP addresses which are present in DNS Blacklist(DNSBL).Plugin - DNSBL check
http://spacebabies.nl/dnsbl_check/
Caching authenticated pages
Page caching does bypass any security filters in your application. So avoid caching authenticated pages and use action or fragment caching instead.

- View -

Cross site scripting(XSS) attack
Cross Site Scripting is a technique found in web applications which allow code injection by malicious web users into the web pages viewed by other users. An attacker can steal login of your user by stealing his cookie. The most common method of attack is to place javascript code on a website that can receive the session cookie. To avoid the attack, escape HTML meta characters which will avoid execution of malicious Javascript code. Ruby on Rails has inbuilt methods like escape_html() (h()), url_encode(), sanatize(), etc to escape HTML meta characters.Description
http://manuals.rubyonrails.com/read/chapter/44 Can we avoid tedious use of h() in views?
http://wiki.rubyonrails.org/rails/pages/HowToEscapeHTML Sanitize() is used to escape script tags and other malicious content other than html tags. Avoid using it ... its unsecure. Use white_list instead.
http://www.rorsecurity.info/2007/08/17/dont-use-strip_tags-strip_links-and-sanitize/ White_list plugin
http://svn.techno-weenie.net/projects/plugins/white_list/README
Anti-spam form protection
Use
Captcha or Javascript based form protection techniques to ensure only human can submit forms successfully.
When using Captcha do ensure the following :
Images are rendered on webpage using send_data and are not stored at the server, because its not required to store images and are redundant.
Avoid using algorithm used by standard Catpcha plugins as they can easily be hacked, instead tweak an existing algorithm or write your own.
Use a Captcha which does not store secret code or images in filesystem, as you will have trouble using Captcha with multiple servers. Tutorial - a nice article on concepts of captcha
http://revolutiononrails.blogspot.com/2007/04/pedo-mellon-minno-or-captcha-on-rails.html Plugin - ReCaptcha (recommended)
http://recaptcha.net/http://ambethia.com/recaptcha/ Plugin - BrainBuster - a logic captcha based on simple puzzles, math and word problems. By default, it has limited set of problems and you would have to come up with large set of your own problems.
http://robsanheim.com/brain-buster Plugin - Simple Captcha (not recommended) as it breaks all the must have features of a good Captcha implementation.
http://expressica.com/2007/03/23/simple_captcha_1_0/ For less critical systems like blogs, a more user-friendly option can be use of CSS based technique or JavaScript based plugin unlike Captcha. Both JavaScript and CSS based techniques can only avoid spam from dumb or general bots. If an hacker specifically targets your site or bot is smart enough, you are dead, so be careful.
CSS based Negative Captcha -
http://damienkatz.net/2007/01/negative_captch.htmlInverse Captcha for Mephisto - http://www.artweb-design.de/projects/mephisto-plugin-inverse...JavaScript based Form Spam Protection - http://form-spam-protection.googlecode.com/svn/form... Captcha with Multiple Servers
http://upstream-berlin.com/blog/2007/08/17/captchas-with-rails-and-multiple-servers/
Hide mailto links
Mailto links in a webpage can be attacked by e-mail harvesting bots. Use the plugin CipherMail to generate a 1024 bit random key and obfuscate the mailto link.Plugin - CipherMail
http://agilewebdevelopment.com/plugins/ciphermail
Use password strength evaluators
A lot of people have used password strength evaluators simply because its used by google in their registration form. You can use it to help your users register with strong password. But I don't think its a must have security addon. Uptill now I have not found a good algorithm to assess strength of a password, but some of them are reasonable.
Also, if there is an open source tool or algorithm for evaluating password strength, it can easily be broken. So, you might consider tweaking the algorithm or building one from scratch.Tools
http://www.certainkey.com/demos/password/ http://www.jeffro2pt0.com/ajax-powered-password-strength-meter/ http://www.geekwisdom.com/dyn/passwdmeter http://www.jvoorhis.com/articles/2006/04/06/automatic-password-suggestion-for-your-rails-app
- Miscellaneous -
Transmission of Sensitive information
Use SSL to encrypt sensitive data between transfer from client to server. SSL hits server performace, so you might consider using SSL only for few pages which transfer sensitive data to and fro.Plugin ssl_requirement
http://svn.rubyonrails.org/rails/plugins/ssl_requirement/README Mongrel, rails, apache and SSL
http://blog.innerewut.de/2006/06/21/mongrel-and-rails-behind-apache-2-2-and-ssl Controller in SSL subdomain
http://www.railsonwave.com/railsonwave/2007/7/10/howto-put-a-controller-under-a-ssl-subdomain Sample SSL code in rails
http://blog.caboo.se/articles/2007/4/21/sample-rails-app-branch-with-ssl
File upload
Be very careful when you allow your users to upload files and make them available for other users to download.Description
http://www.rorsecurity.info/2007/03/27/working-with-files-in-rails/ Must read - Section 26.7 of Agile web development with rails - 2nd edition
http://www.pragmaticprogrammer.com/titles/rails2/ In place file upload
http://kpumuk.info/ruby-on-rails/in-place-file-upload-with-ruby-on-rails/ 3 plugins for file upload reviewed at :
http://www.flex888.com/2007/03/21/three-ruby-on-rails-file-upload-plugins-reviewed.html
Secure your setup / environment
http://www.igvita.com/blog/2006/10/10/securing-your-rails-environment/
Proper Mysql configuration
http://www.rorsecurity.info/2007/02/25/securing-mysql/ http://www.rorsecurity.info/2007/02/27/rails%e2%80%99-friends-securing-mysql-continued/
Use good passwords
http://en.wikipedia.org/wiki/Password_strength http://www.rorsecurity.info/2007/06/05/use-good-passwords/
Security plugins directory
http://agilewebdevelopment.com/plugins/category/1http://www.railslodge.com/pluginshttp://railsify.com/categories/security-production Note : I will keep this security guide updated. Any additions/improvements are welcome.

Thursday, February 7, 2008

Eager loading with cascaded associations

Finder Methods, Eager loading with cascaded associations


Cascading association in rails is a wonderful development which allows you to use the Model Associations very easily and comfortably. Here with i will be explaining few techniques to use that:

Normal find method in rails is like this,

Author.find(:all)

With conditions,

Author.find(:all, :conditions => ".....")

Now let us suppose we are having a relation between a author and book, like an Author has many books and Book belongs_to author

To find books written by a particular Author, we will use some thing like

Author.find(:all, :include => :books)

Author.find(:all,:include => [:books,:novels]) if at all we are having association with the novels table also and if we want to retrieve the novels too.


But here comes the new area which i am about to tell you specifically.

That is if we are having a relation between Table A and Table B. Another relation between Table B and Table C.
But in one finder method if we wish to find the table C's content, querying through Table A, it is not possible. So we will be using cascading associations at this situation:

Starting from scratch,

:include Option recognizes the following options

Symbol - Base table simply Joins it.
Eg: Author.find(:all, :include=>:books)

String - Same as Symbol
Eg: Author.find(:all, :include => "books")

Array - The Base table joins those elements directly
Eg: Author.find(:all,:include=>[:books,:novels,:magazines])

Hash - The base table JOINS the KEY part and it JOINS the VALUE part.(Here value is processed as same as the :include option)
Eg: Author.find(:all,:include=>{:books,:movies})

Now i will explain about the depth of such kind of joining tables. Well , to say, that is not limited.As of now, i will explain uptil 2 and +2 methods

Normal with one association: One level deep
Author.find(:all, :include=>:posts)
=> authors
+ posts

Normal with two associations: One level deep

Author.find(:all, :include=>[:posts, :categorizations])
=> authors
+- posts
+- categorizations

Cascading Associations with two levels

Author.find(:all, :include=>[{:posts=>:comments}, :categorizations])
=> authors
+- posts
+- comments
+- categorizations

Author
.find(:all, :include=>{:posts=>[:comments, :categorizations]})
=> authors
+- posts
+- comments
+- categorizations

Cascading associations with more than two levels....

cascaded in three levels
>> Company.find(:all, :include=>{:groups=>{:members=>:favorites}})
=> companies
+- groups
+- members
+- favorites


>> Author.find(:all, :include=>{:posts=>{:author=>{:posts=>...}}})
=> authors
+- posts
+- author
+- posts
+- ...


It also has a feature that automatically aliases table name when conflicted. So we can use eager loading on same table names such as acts_as_tree.

TreeMixin.find(:all, :include=>"children")
=> mixins
+- children




Tuesday, January 15, 2008

Merb and Data Mapper

A couple of new frameworks making waves in the Ruby community are Merb and Data Mapper. Merb and Data Mapper seem to go together like peanut butter and jelly in that Merb gives you pretty much everything Rails gives you except for an ORM framework, and Data Mapper is an ORM framework like Active Record.

Data Mapper has a fantastic Why Data Mapper? page. I love this page because it assumes you the reader are a Ruby on Rails web developer, so you know Active Record, and you want to know why you'd want to use Data Mapper instead of Active Record. After reading that page, I was definitely intrigued. It's amazing what good documentation can do for the success of a framework. Merb has documentation on the differences between Merb and Rails as well.

The most obvious difference that jumps out at you between Active Record and Data Mapper is that in the Data Mapper, you define the properties (a.k.a fields, attributes, columns) of your models inside the model itself, as opposed to Active Record, where you define no properties in your model and reflect the properties from the columns in your database. This has long been a point of contention for many developers critical of Rails.

The most obvious difference between Rails and Merb is that with Merb, controller actions always return the data that is to be sent to the client. A Merb action can simply return a String, or it could return a File, and IO or even a Proc. That sounds cool.

It will be interesting to see if Merb/Data Mapper gain popularity in the Ruby community. Rails is a full-stack solution, where as Merb is a MVC framework and leaves it up to you to choose an ORM framework and Data Mapper is just an ORM, not tied to any one MVC framework. This is more like the Java web framework world, where you have MVC frameworks like Struts, Stripes, etc. and ORM frameworks like Hibernate, and you glue them all together with something like Spring or Guice. The difference here is that you glue them together with Ruby, still no XML or Annotations needed.

One thing I thought I liked about Rails is that it's the only web framework people in the Ruby community use to build web apps. In the Java world, managers and business types would say "We're a Java shop" or "We're building our app in Java", but the reality is that there are so many different frameworks and saying you are building it in "Java" could mean one of a hundred different things. That might really mean "JSF/Struts 2/Spring/Hibernate" or "Freemarker/Stripes/Guice/iBatis". Matt Raible has basically built his entire career around analyzing all the different options there are for Java web frameworks, as well as developing a meta-framework to help tie them all together. Up until now, on the Ruby side of things, if someone says we're building an app with Ruby on Rails, you know what you are talking about.

But it seems this could be the start of trend, where we get more frameworks within the Ruby community, which is a good thing because you can make choices, but on the other hand it could start to fragment the community. Overall I think it's a win and the reality you come to realize after developing with Rails for a year or so is that Ruby is really the thing that makes the whole thing go and that knowledge is easily portable to other Ruby frameworks. To make it even easier, Merb and Data Mapper both have many of the same concepts as Rails, so I can't imagine the learning curve of switching from one to the other to be particularly steep.

Another crazy idea is that with specialized frameworks like Merb and Data Mapper that are meant to be just one part of a stack rather than the full stack like Rails, and JRuby becoming a legitimate option, you could start to see some weird hybrid Ruby/Java apps, like Merb+Hibernate, or Stripes+Data Mapper. I doubt that would be common, but you could do it.

So talk is cheap, let's get something going. To install Merb and Data Mapper, I followed these instructions for setting up Merb and Data Mapper.

sudo gem install merb --include-dependencies
sudo gem install datamapper
sudo gem install ruby2ruby --include-dependencies
sudo gem install merb_datamapper
cd /usr/local/lib/ruby/gems/1.8/gems/datamapper-0.2.3
sudo bash -c "ARCHFLAGS='-arch i386' rake dm:install:mysql"

Believe it or not that actually worked for my on my Macbook. That last step just outputs the following if it works:

(in /usr/local/lib/ruby/gems/1.8/gems/datamapper-0.2.3)

Here's to hoping I can just do this at some point in the future:

sudo gem install merb_datamapper_mysql --include-dependencies

But you can't for now. So I continued following the instructions and got this error when I tried to run rake:

paulbarry@paulbarry: ~/projects/foo $ rake
(in /Users/paulbarry/projects/foo)
Using pure ruby JSON lib
rake aborted!
no such file to load -- json/pure
/Users/paulbarry/projects/foo/rakefile:10
(See full trace by running task with --trace)

Why didn't gem install merb --include-dependencies install json and json_pure for me if they are required dependencies? Good question, I don't know, but we'll just install them now to get things going:

sudo gem install json json_pure

So after that, everything goes off without a hitch. Picking up where the instructions left off, let's at least create a hello world app. Create a file in app/controllers called hello_world.rb with the following contents:

class HelloWorld < Application
def index
"

Hello, World!

"
end
end

Go to http://localhost:4000/hello_world and bask in the glory of your fist Merb web application. This should seem familar, except for the fact that you are naming it HelloWorld instead of HelloWorldController. How do you avoid namespace collisions when you have a User controller and a User model? Good question, I'll let you know when I find the answer.

So as promised, you can just return a string and that's when gets rendered in the browser. But you don't want to do that for real, so let's render a template. Create a file app/views/hello_world/index.html.erb and put the html in there. Change the index method to just call render, like this:

class HelloWorld < Application
def index
render
end
end

And there's our hello world with a template. Ok, so what about data mapper? So step one is create file in app/models called foo.rb with the following contents:

class Foo < DataMapper::Base
property :bar, :string
property :created_at, :datetime
property :updated_at, :datetime

validates_presence_of :bar
validates_length_of :bar, :minimum => 1
end

This also should make sense if you are Rails developer. The only difference is that we are defining our properties in the class, rather than in a migration. To create our table and a test record, start the merb console, which you do by executing merb -i, and then enter these commands:

irb(main):004:0> DataMapper::Base.auto_migrate!
=> [Foo]
irb(main):005:0> Foo.create(:bar => 'Hello, World!')
=> #

Ok, so them we modify our controller and view to use this:

class HelloWorld < Application
def index
@foo = Foo.first
render
end
end

and:

<%= @foo.bar %>


And there we go, Merb and Data Mapper working together as one.

Merb - Alternative for Rails?

Like Ruby on Rails, Merb (Mongrel + ERB) is an MVC framework. Unlike Rails, Merb is ORM-agnostic, JavaScript library agnostic, and template language agnostic, preferring plugins that add in support for a particular feature rather than trying to produce a monolithic library with everything in the core. In fact, this is a guiding principle of the project, which has led to third-party support for the ActiveRecord, DataMapper, and Sequel ORMs.

In addition, it means that the core code in Merb is kept simple and well organised. This has multiple benefits. It means it‘s faster for one thing. It‘s also easier to understand, maintain and extend.

Get Merb

The simplest way to get Merb is to install the gem:

  $ sudo gem install merb --include-dependencies

If you want to contribute (or just use the latest code), you can build the gem from the svn trunk:

  $ sudo gem install mongrel json json_pure erubis mime-types rspec hpricot mocha rubigen haml markaby mailfactory Ruby2Ruby -y
$ svn co http://svn.devjavu.com/merb/trunk merb
$ cd merb
$ rake install

To generate a new merb app after the gem is installed:

  $ merb myapp

Dependencies

Currently, Merb itself depends on the following gems:

  • mongrel
  • json_pure
  • erubis
  • mime-types
  • rspec
  • rubigen
  • ruby2ruby
  • rake

** If you are on windows see this blog post oj how to get up and running: www.ghostonthird.com/2007/11/17/merb-on-windows-it-works/

You must also have either the json or json_pure gem installed. Note that the json gem provides a faster library but will not work with jRuby.

Optionally, merb can take advantage of the following gems:

  • mailfactory (if you wish to use merb‘s mailers)
  • haml 1.8 or greater (if you wish to use HAML templates, i.e. .haml files)
  • markaby (if you wish to use Markaby template, i.e. .mab files)
  • builder (if you wish to use Builder templates, i.e. .rxml, .rerb or .builder files)
  • memcache-client (for use with Danga Interactive‘s memcached)
  • swiftiply
  • eventmachine
  • rcov
  • ruby-debug (if you want to use debugging functionality)

You will also probably need to install your ORM of choice as well as any gem plugins you want to use (see below).

The merb server

right now you add your routes in the appdir/config/router.rb file. So by default it runs on port 4000

        $ cd /path/to/your/merb/app
$ merb

Or to start merb on a different port:

        $ merb -p 3500

To start a cluster of merb servers you specify the first port and then how many servers you want spawned. SO this command will start a merb instance on ports 3000, 3001, 3002

        $ merb -p 3000 -c 3

To start a Merb IRB console where all your models and other classes are pre loaded use the -i flag

        $ merb -i

To enable ruby-debug support, start Merb with

  $ merb -D

Now you can use the debugger method everywhere in your code to open an rdebug session.

To see all the available command line flags use:

        $ merb -h

Using Merb

Merb uses the Model-View-Controller (MVC) pattern. Incoming requests are matched in the Router and directed to an appropriate controller action.

Model

Merb does not come with its own model layer. While you are free to use whatever data system you like, the merb core team does maintain plugins for the following Object Relational Mappers (ORM‘s):

ActiveRecordThe same ORM that Rails uses. (sudo gem install merb_activerecord)
DataMapperFairly new ORM. (sudo gem install merb_datamapper)
SequelFairly new ORM. (sudo gem install merb_sequel)

To use your choice ORM, install the appropriate gem and uncomment the appropriate use_orm line in Merb.root/config/dependencies.rb

Controllers

(Merb.root/app/controllers/*)

Merb controllers inherit from Merb::Controller and contain built in view/template rendering. Incoming requests are usually mapped to a specific method in a controller. For example, using the default routes, a request to www.yourapp.com/posts/show/1 would call the the show method of your PostsController (and params[:id] would = 1.)

The return value of your action function gets sent back to the client as the view. In most cases, you are going to want to end your functions with a call to render. By default, render will render the view template associated with your action (in default merb that would be Merb.root/app/views//.html.erb - see the View section for more info.)

Controllers can be generated by calling Merb.root/script/generate controller ControllerName. By default, generated controllers inherit from the Application class (Merb.root/app/controllers/application.rb) which itself inherits from Merb:Controller. Application is a good place to put code pertinent to all controllers. An example would be setting a filter to check if a user is logged in or to preload user data for each controller.

before and after filters

Use the before method in your controllers. before accepts either a symbol, string or a Proc/lambda object. If you give it a symbol it will call a method with the same name as the symbol. If you give it a proc that takes one argument it will call the proc with the current controller as that argument. You can use :only and :exclude as options to your filters to exclude or include actions from certain filters. :only and :exclude take :symbols or [:sym, :sam] array of symbols.

        class Foo < only =""> :foo
before lambda {|c| c.headers['X-Foo] = 'bar' }, :exclude => [:foo, :baz]

def setup_user
# blah blah
end

def foo
# blah
end

def regular_action
# blah
end

end

To stop the before filter chain you use throw :halt with a few options:

        # halts the filter chain and calls filters_halted which you can override
# in your controller to specialize it.

throw :halt

# halts the filters and calls the method named after the symbol:

throw :halt, :other_action

# halts the filter chain and returns the result of the Proc being called

throw :halt, Proc.new{ |c| c.redirect "/foo" }

# halts the chain and returns whatever is in the string

throw :halt, "

You don't have permissions dude!

"

or even render templates:

throw :halt, render 'foo'
throw :halt, partial 'foo'

After filters accept a symbol, string or Proc and call that proc with the controller:

        after Proc.new {|c| Tidy.new(c.body) }, :only => :index

Views

(Merb.root/app/views/*)

A view can be loosely defined as any data sent back to the client (a "view" of your data.) By default, Merb controllers send the return value of your controller action as the view. The Controller#render method simply renders the specified view and returns it as a string. By default, a call to render without any options renders the view template associated with that controller action. Using the default ERB templating system, this means that a call to render in Posts#index would render the file Merb.root/app/views/posts/index.html.erb

Layouts

(Merb.root/app/views/layout/*)

Layouts are generic templates in which your specific view templates are rendered. A sample layout could look like:

 
My Application Layout



<%= catch_content :layout %>



By default, render will look for a corresponding layout for your controller in the form of Merb.root/app/views/layout/.html.erb . If no specific layout is present, render will attempt to use Merb.root/app/view/layout/application.html.erb

See render for more details/options, as well as how to use different templating systems in your app.

You can return several different types of values from your controller actions:

  • String: Any string will get sent to the browser as standard text/html
  • File/IO: Any file descriptor will get handed over to mongrel to be streamed to the client.
  • Proc Object: The object will be called and the return value sent to the client.

That last point has some cool connotations if you think about it. Merb does have a mutex lock around the call to your controller’s action anywhere that you can call AR objects. Merb’s lock is way smaller then rails giant lock though and allows for many more concurrent requests to be handled by one process. By returning a Proc object from your action, you allow merb to release the lock and the proc is called in multi threaded way. This allows for all kinds of cool streaming and ‘futures’ where you return the proc and release the mutex. It’s basically like handing over the proc to mongrel and mongrel handles calling it in a thread safe manner.

Helpers

(Merb.root/app/helpers/*)

app/helpers/global_helper.rb will be available to all of your views.

 Helpers named after your controller plus _helper.rb will be included in the views
for that controller only.

File uploads

When a file is uploaded with Merb, it gets put in a Tempfile. So you just want to copy it to the right place on the filesystem.

        def upload
puts params[:file].inspect
FileUtils.mv params[:file][:tempfile].path, Merb.root+"/uploads/#{params[:file][:filename]}"
render
end

A file upload will have a hash of params like this:

        {
:filename => File.basename(filename),
:content_type => content_type,
:tempfile => ,
:size => File.size(body)
}

Merb app layout

        merb_app:

app
controllers
helpers
mailers
models
parts
views
config
gems
lib
log
public
Rakefile
script
spec
test
unit

Sunday, November 25, 2007

Basic User Authentication in Rails

This article walks through creating a basic authentication system in rails.

The question of user authentication comes up regularly on the rails mailing list and there are several articles and discussions around the web on whether it should be part of the rails framework. Its not included in rails and it is not likely to be.

The reason given is that its difficult to generalize it to suit everybody. When I started using rails I tried out several different plugins and generators for user authentication. Each time I ended up spending more time installing, figuring out and adapting the code than I would have spent if I wrote it from scratch. Each project I’ve worked on has had different authentication requirements so now I write the authentication code from scratch every time.

The advantages to writing your own are:

* You will fully understand it.
* It will match exactly the needs of your application.
* It will be easier for you to adapt it.
* In many cases its actually quicker than using a plugin.
* You have to write your own tests.

If you’re not interested in rolling your own, check these out (even if you don’t use them, reading through the code for each one is a good way to learn different approaches to doing rails authentication):

* Login Engine
* Login Generator
* Acts_as_authenticated

So in this article I will walk through creating a simple user authentication system with rails. Hopefully this tutorial will be useful to others who are starting to learn rails and need to do some basic authentication.

Its worth taking a moment to discuss how I wrote the code. The sequence for this article is to help explain what’s going on, but its not really the sequence of how I wrote the code. When writing the code I sketched out the views on paper and used this to figure out what methods I would need in the model and controller. Then I grabbed some tests from login generator and login engine and added some more tests based on the functionality I wanted. Then I implemented the model followed by the controller, one test at a time. When all the tests passed, I coded up the views. This was a bit of an epiphany for me as before I started using rails and for a while after I started to use it I rarely used unit tests. Now they’re an integral part of my coding process. For the purposes of this tutorial I’m not going to pay much attention to the tests other than to list them but I have commented them so I would strongly encourage you to go through them.

Lets start by thinking about our views. This gives us an idea of the functionality that we want. We can draw the pages that our app will have. It can be helpful at this stage to draw the views using pen and paper. By forcing ourselves to draw the interface now we force ourselves to think about the functionality we actually need. To signup the user will need to supply a username, a password and confirm their password. When logging in they will provide a username and password. If a user forgets their password they can have it emailed to their account - so we need to get their email when they signup too. When the user changes their password they just need to supply the new password and confirm it.

Users can signup, login and logout. Users can also change their password and have a password emailed to themselves if they forget it. Access to actions can be restricted depending on whether a user is logged in or not. So from thinking about our views we need to store for each user a username, password and email address. Now lets move on to creating the model.

> ruby script/generate model User
exists app/models/

exists test/unit/
exists test/fixtures/
create app/models/user.rb
create test/unit/user_test.rb
create test/fixtures/users.yml
create db/migrate
create db/migrate/001_create_users.rb

This generates the user model and test stubs and creates our initial migration. Open your user migration. It has already been created in db/migrate/001_create_users.rb

class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.column :login, :string
t.column :hashed_password, :string
t.column :email, :string
t.column :salt, :string
t.column :created_at, :datetime
end
end

def self.down
drop_table :users
end
end

This code defines our database table that will store users. login is the user’s username and email is their email address - we will use this to send them a new password if they forget it. The other thing we need to store is their password so that we can authenticate them and log them into the system.

Its not a good idea to store the password in cleartext in the database. Instead we will store a hashed version of the password. This is stored in the hashed_password field. Before comparing the password to the one stored in the database we encrypt it and then check if the encrypt of the entered password matches the one stored in the database. We will use the SHA1 algorithm to encrypt the password.

Note: By default all your post parameters including cleartext passwords will appear in the rails production log. If you dont want this to happen you should take steps to avoid it. E.g. Filter Logged Params Plugin

The other database column is salt. Adding a salt to the hashed password make it more difficult to break the password. The salt is a random string that is generated and stored for each user. We then add this salt to the password before encrypting it. Every user has a different salt, but we store each user’s salt in the database so that we can authenticate the user’s password.

Running the migration will create the database

> rake db:migrate
== CreateUsers: migrating =====================================================
-- create_table("users")
-> 0.6538s
== CreateUsers: migrated (0.6554s) ============================================

and running

> rake db:test:clone

sets up the test database.
The model

We need to decide what functionality goes in the model and what goes into the controllers. Rails is based on the MVC pattern and the idea that the business logic goes into the model. This is sometimes confused with the 3-tier architecture where the models are just used to interface with the database and the business logic appears in the controllers. The rails way seems to be to put as much logic as possible into the models. To think of it another way, you should be able to run your application through all its important processes by calling methods on model objects at the console. More concretely core logic such as authentication and sending a new password should be in the model, not the controller.

require 'digest/sha1'

class User < ActiveRecord::Base
validates_length_of :login, :within => 3..40
validates_length_of :password, :within => 5..40
validates_presence_of :login, :email, :password, :password_confirmation, :salt
validates_uniqueness_of :login, :email
validates_confirmation_of :password
validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message => "Invalid email"

We start out by defining validations for the user model. password and login must be within pre-defined length. login and email must be unique. email must match a certain format. validates_confirmation_of ensures that password must be confirmed using password_confirmation. login, email, password, password_confirmation and salt must all be present. When creating a new user or saving an existing one, all these conditions must be met or the save will fail.

Looking back at the database definition we see that we didn’t have a password or password confirmation field. We had hashed_password. We will store the hashed password in the database but we will create variables to hold the raw text version of password and password_confirmation. These values will not be stored in the database but storing them as variables enables us to take advantages of the rails validation methods.

attr_protected :id, :salt

We make the id and salt attributes protected. This makes sure that users can’t set them by sending a post request - you have to update them in the model. For example if you extended this model to include a roles field that specified if a user was an admin or normal user it would be important to specify that field as protected. Any field that you don’t want to be updatable from your web forms should be protected.

We set the salt for the user to a random string if it hasn’t already been set. This happens the first time the users password is set. When we set the users password it calls the protected random_string method to generates a random string of digits and numbers of a pre-defined length.

def self.random_string(len)
#generate a random password consisting of strings and digits
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
newpass = ""
1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
return newpass
end

We now need to make sure that the password that is stored in the database is encrypted. We create an instance method password=. This method gets called whenever a password is assigned to a user e.g. u.password=”secret”.

def password=(pass)
@password=pass
self.salt = User.random_string(10) if !self.salt?
self.hashed_password = User.encrypt(@password, self.salt)
end

This sets the variable @password to the text value of password and stores the hashed version in the database. The password is hashed using the protected encrypt class method. This generates a hash from the password and the users salt. The salt is set to a random value if it hasn’t already been set. (You could set the salt to a different value every time you change the password if you wanted).

def self.encrypt(pass, salt)
Digest::SHA1.hexdigest(pass+salt)
end

So that takes care of encrypting the password. The user model also needs to be able to authenticate a user. The class method authenticate returns a user if they’re hashed password matches the one stored for that user in the database.

def self.authenticate(login, pass)
u=find(:first, :conditions=>["login = ?", login])
return nil if u.nil?
return u if User.encrypt(pass, u.salt)==u.hashed_password
nil
end

First we find the user that corresponds to the login. If we didn’t find the login authentication fails. We then compute the users hashed password using the supplied password and the users salt. Authentication is successful if these values match.

The last piece of functionality for the user is the ability to send them a new password if they forget their password. We can’t send them their existing password because we don’t store it - we only store a salted hash of it. Instead we generate a new random password, change the users password to the new random password and email this new password to the user (we will describe the Notifications mailer method later).

def send_new_password
new_pass = User.random_string(10)
self.password = self.password_confirmation = new_pass
self.save
Notifications.deliver_forgot_password(self.email, self.login, new_pass)
end

Here is a full listing of the model:

require 'digest/sha1'

class User < ActiveRecord::Base
validates_length_of :login, :within => 3..40
validates_length_of :password, :within => 5..40
validates_presence_of :login, :email, :password, :password_confirmation, :salt
validates_uniqueness_of :login, :email
validates_confirmation_of :password
validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message => "Invalid email"

attr_protected :id, :salt

attr_accessor :password, :password_confirmation

def self.authenticate(login, pass)
u=find(:first, :conditions=>["login = ?", login])
return nil if u.nil?
return u if User.encrypt(pass, u.salt)==u.hashed_password
nil
end

def password=(pass)
@password=pass
self.salt = User.random_string(10) if !self.salt?
self.hashed_password = User.encrypt(@password, self.salt)
end

def send_new_password
new_pass = User.random_string(10)
self.password = self.password_confirmation = new_pass
self.save
Notifications.deliver_forgot_password(self.email, self.login, new_pass)
end

protected

def self.encrypt(pass, salt)
Digest::SHA1.hexdigest(pass+salt)
end

def self.random_string(len)
#generat a random password consisting of strings and digits
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
newpass = ""
1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
return newpass
end

end

Testing the model

With an authentication system the behaviour that you want your code to exhibit and the behaviour you don’t want it to exhibit are very well defined. Writing tests can be a good way of specifying the behaviour before writing the code. Write unit tests that specify how the model will behave. Write functional tests that specify how the controller will behave. Once these tests pass, you can be fairly confident that your model and controller are working as you expect. More importantly you can be confident that it still works in the future if you make changes to it by running the tests again and adding new ones. In other cases you can develop your code and tests side by side.

I’ve been harping on about tests so here are the unit tests for the user model. These tests go in the file test/unit/user_test.rb

To run the tests we need some fixtures. Our fixtures are in test/fixtures/users.yml

bob:
id: 1000001
login: bob
salt: 1000
email: bob@mcbob.com
hashed_password: 77a0d943cdbace52716a9ef9fae12e45e2788d39 # test

existingbob:
id: 1000002
salt: 1000
login: existingbob
email: exbob@mcbob.com
hashed_password: 77a0d943cdbace52716a9ef9fae12e45e2788d39 # test

longbob:
id: 1000003
login: longbob
email: lbob@mcbob.com
hashed_password: 00728d3362c26746ec25963f71be022b152237a9 # longtest
salt: 1000

Here is a listing of tests for the model:

require File.dirname(__FILE__) + '/../test_helper'

class UserTest < Test::Unit::TestCase
self.use_instantiated_fixtures = true
fixtures :users

def test_auth
#check that we can login we a valid user
assert_equal @bob, User.authenticate("bob", "test")
#wrong username
assert_nil User.authenticate("nonbob", "test")
#wrong password
assert_nil User.authenticate("bob", "wrongpass")
#wrong login and pass
assert_nil User.authenticate("nonbob", "wrongpass")
end


def test_passwordchange
# check success
assert_equal @longbob, User.authenticate("longbob", "longtest")
#change password
@longbob.password = @longbob.password_confirmation = "nonbobpasswd"
assert @longbob.save
#new password works
assert_equal @longbob, User.authenticate("longbob", "nonbobpasswd")
#old pasword doesn't work anymore
assert_nil User.authenticate("longbob", "longtest")
#change back again
@longbob.password = @longbob.password_confirmation = "longtest"
assert @longbob.save
assert_equal @longbob, User.authenticate("longbob", "longtest")
assert_nil User.authenticate("longbob", "nonbobpasswd")
end

def test_disallowed_passwords
#check thaat we can't create a user with any of the disallowed paswords
u = User.new
u.login = "nonbob"
u.email = "nonbob@mcbob.com"
#too short
u.password = u.password_confirmation = "tiny"
assert !u.save
assert u.errors.invalid?('password')
#too long
u.password = u.password_confirmation = "hugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehuge"
assert !u.save
assert u.errors.invalid?('password')
#empty
u.password = u.password_confirmation = ""
assert !u.save
assert u.errors.invalid?('password')
#ok
u.password = u.password_confirmation = "bobs_secure_password"
assert u.save
assert u.errors.empty?
end

def test_bad_logins
#check we cant create a user with an invalid username
u = User.new
u.password = u.password_confirmation = "bobs_secure_password"
u.email = "okbob@mcbob.com"
#too short
u.login = "x"
assert !u.save
assert u.errors.invalid?('login')
#too long
u.login = "hugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhug"
assert !u.save
assert u.errors.invalid?('login')
#empty
u.login = ""
assert !u.save
assert u.errors.invalid?('login')
#ok
u.login = "okbob"
assert u.save
assert u.errors.empty?
#no email
u.email=nil
assert !u.save
assert u.errors.invalid?('email')
#invalid email
u.email='notavalidemail'
assert !u.save
assert u.errors.invalid?('email')
#ok
u.email="validbob@mcbob.com"
assert u.save
assert u.errors.empty?
end


def test_collision
#check can't create new user with existing username
u = User.new
u.login = "existingbob"
u.password = u.password_confirmation = "bobs_secure_password"
assert !u.save
end


def test_create
#check create works and we can authenticate after creation
u = User.new
u.login = "nonexistingbob"
u.password = u.password_confirmation = "bobs_secure_password"
u.email="nonexistingbob@mcbob.com"
assert_not_nil u.salt
assert u.save
assert_equal 10, u.salt.length
assert_equal u, User.authenticate(u.login, u.password)

u = User.new(:login => "newbob", :password => "newpassword", :password_confirmation => "newpassword", :email => "newbob@mcbob.com" )
assert_not_nil u.salt
assert_not_nil u.password
assert_not_nil u.hashed_password
assert u.save
assert_equal u, User.authenticate(u.login, u.password)

end

def test_send_new_password
#check user authenticates
assert_equal @bob, User.authenticate("bob", "test")
#send new password
sent = @bob.send_new_password
assert_not_nil sent
#old password no longer workd
assert_nil User.authenticate("bob", "test")
#email sent...
assert_equal "Your password is ...", sent.subject
#... to bob
assert_equal @bob.email, sent.to[0]
assert_match Regexp.new("Your username is bob."), sent.body
#can authenticate with the new password
new_pass = $1 if Regexp.new("Your new password is (\\w+).") =~ sent.body
assert_not_nil new_pass
assert_equal @bob, User.authenticate("bob", new_pass)
end

def test_rand_str
new_pass = User.random_string(10)
assert_not_nil new_pass
assert_equal 10, new_pass.length
end

def test_sha1
u=User.new
u.login = "nonexistingbob"
u.email="nonexistingbob@mcbob.com"
u.salt="1000"
u.password = u.password_confirmation = "bobs_secure_password"
assert u.save
assert_equal 'b1d27036d59f9499d403f90e0bcf43281adaa844', u.hashed_password
assert_equal 'b1d27036d59f9499d403f90e0bcf43281adaa844', User.encrypt("bobs_secure_password", "1000")
end

def test_protected_attributes
#check attributes are protected
u = User.new(:id=>999999, :salt=>"I-want-to-set-my-salt", :login => "badbob", :password => "newpassword", :password_confirmation => "newpassword", :email => "badbob@mcbob.com" )
assert u.save
assert_not_equal 999999, u.id
assert_not_equal "I-want-to-set-my-salt", u.salt

u.update_attributes(:id=>999999, :salt=>"I-want-to-set-my-salt", :login => "verybadbob")
assert u.save
assert_not_equal 999999, u.id
assert_not_equal "I-want-to-set-my-salt", u.salt
assert_equal "verybadbob", u.login
end
end

Running these tests gives

> ruby test/unit/user_test.rb
Loaded suite test/unit/user_test
Started
..........
Finished in 4.767613 seconds.

10 tests, 63 assertions, 0 failures, 0 errors

The controller

> ruby script/generate controller User signup login logout delete edit forgot_password
exists app/controllers/
exists app/helpers/
create app/views/user
exists test/functional/
create app/controllers/user_controller.rb
create test/functional/user_controller_test.rb
create app/helpers/user_helper.rb
create app/views/user/signup.rhtml
create app/views/user/login.rhtml
create app/views/user/logout.rhtml
create app/views/user/delete.rhtml
create app/views/user/edit.rhtml
create app/views/user/forgot_password.rhtml


Since the model contained the business logic, the controller methods are a bit easier. Here is the code for the controller.

class UserController < ApplicationController

before_filter :login_required, :only=>['welcome', 'change_password', 'hidden']

def signup
@user = User.new(@params[:user])
if request.post?
if @user.save
session[:user] = User.authenticate(@user.login, @user.password)
flash[:message] = "Signup successful"
redirect_to :action => "welcome"
else
flash[:warning] = "Signup unsuccessful"
end
end
end

def login
if request.post?
if session[:user] = User.authenticate(params[:user][:login], params[:user][:password])
flash[:message] = "Login successful"
redirect_to_stored
else
flash[:warning] = "Login unsuccessful"
end
end
end

def logout
session[:user] = nil
flash[:message] = 'Logged out'
redirect_to :action => 'login'
end

def forgot_password
if request.post?
u= User.find_by_email(params[:user][:email])
if u and u.send_new_password
flash[:message] = "A new password has been sent by email."
redirect_to :action=>'login'
else
flash[:warning] = "Couldn't send password"
end
end
end

def change_password
@user=session[:user]
if request.post?
@user.update_attributes(:password=>params[:user][:password], :password_confirmation => params[:user][:password_confirmation])
if @user.save
flash[:message]="Password Changed"
end
end
end

def welcome
end
def hidden
end
end

The code for the controller is pretty straight-forward as all the tricky business logic is in the model. In each controller method, it is just a matter of deciding which action to direct to next depending on what input it gets.

For example the signup action creates a new user using the parameters it receives. It it is a post request (the form was submitted) it tries to save the new user. If the save operation was successful the user is authenticated and redirected to the welcome screen. If we fail to save the user (e.g. if validation fails) we add a warning to the flash and the page renders again.

The login action attempts to authenticate the user using the given parameters. If successful it redirects them to a page stored in the session or a default.

The forgot password action finds a user using the email address provided as a parameter and the tries to send them a new password. If successful it redirect them to the login action.

There are also some methods that we would like to be available to all controllers in the application. These are stored in app/controllers/application.rb. These are

class ApplicationController < ActionController::Base

def login_required
if session[:user]
return true
end
flash[:warning]='Please login to continue'
session[:return_to]=request.request_uri
redirect_to :controller => "user", :action => "login"
return false
end

def current_user
session[:user]
end

def redirect_to_stored
if return_to = session[:return_to]
session[:return_to]=nil
redirect_to_url(return_to)
else
redirect_to :controller=>'user', :action=>'welcome'
end
end
end

login_required is a filter that allows us to control access to actions. In the user controller we have three actions, welcome, hidden and forgot_password that can only be accessed by logged in users. The line

before_filter :login_required, :only=>['welcome', 'change_password', 'hidden']

ensures that the login_required method is run before the hidden and welcome actions. Processing of these actions only continues if this filter returns true. The login_required method returns true if session[:user] is set i.e. if the user is logged in. Otherwise it stores the page to return to in the session and redirects to the login page. The redirect_to_stored method is used to redirect to a page stored in the session (It redirects to the url stored in the variable session[:return_to]).

current_user is a convenience method for accessing the currently-logged-in user. Using this to get the user in our application instead of accessing session[:user] directly will allow us to change this in the future. For example instead of storing the entire user object in the session we might change our implementation to store only the users id and retrieve the user object from the database with a before_filter (see the extensions at the end for more on this).
Testing the controller

Here are the controller tests. These specify how each controller method should behave.

require File.dirname(__FILE__) + '/../test_helper'
require 'user_controller'

# Re-raise errors caught by the controller.
class UserController; def rescue_action(e) raise e end; end

class UserControllerTest < Test::Unit::TestCase

self.use_instantiated_fixtures = true

fixtures :users

def setup
@controller = UserController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@request.host = "localhost"
end



def test_auth_bob
#check we can login
post :login, :user=> { :login => "bob", :password => "test" }
assert_session_has :user
assert_equal @bob, session[:user]
assert_response :redirect
assert_redirected_to :action=>'welcome'
end

def test_signup
#check we can signup and then login
post :signup, :user => { :login => "newbob", :password => "newpassword", :password_confirmation => "newpassword", :email => "newbob@mcbob.com" }
assert_response :redirect
assert_not_nil session[:user]
assert_session_has :user
assert_redirected_to :action=>'welcome'
end

def test_bad_signup
#check we can't signup without all required fields
post :signup, :user => { :login => "newbob", :password => "newpassword", :password_confirmation => "wrong" , :email => "newbob@mcbob.com"}
assert_response :success
assert_invalid_column_on_record "user", "password"
assert_template "user/signup"
assert_nil session[:user]

post :signup, :user => { :login => "yo", :password => "newpassword", :password_confirmation => "newpassword" , :email => "newbob@mcbob.com"}
assert_response :success
assert_invalid_column_on_record "user", "login"
assert_template "user/signup"
assert_nil session[:user]

post :signup, :user => { :login => "yo", :password => "newpassword", :password_confirmation => "wrong" , :email => "newbob@mcbob.com"}
assert_response :success
assert_invalid_column_on_record "user", ["login", "password"]
assert_template "user/signup"
assert_nil session[:user]
end

def test_invalid_login
#can't login with incorrect password
post :login, :user=> { :login => "bob", :password => "not_correct" }
assert_response :success
assert_session_has_no :user
assert flash[:warning]
assert_template "user/login"
end

def test_login_logoff
#login
post :login, :user=>{ :login => "bob", :password => "test"}
assert_response :redirect
assert_session_has :user
#then logoff
get :logout
assert_response :redirect
assert_session_has_no :user
assert_redirected_to :action=>'login'
end

def test_forgot_password
#we can login
post :login, :user=>{ :login => "bob", :password => "test"}
assert_response :redirect
assert_session_has :user
#logout
get :logout
assert_response :redirect
assert_session_has_no :user
#enter an email that doesn't exist
post :forgot_password, :user => {:email=>"notauser@doesntexist.com"}
assert_response :success
assert_session_has_no :user
assert_template "user/forgot_password"
assert flash[:warning]
#enter bobs email
post :forgot_password, :user => {:email=>"exbob@mcbob.com"}
assert_response :redirect
assert flash[:message]
assert_redirected_to :action=>'login'
end

def test_login_required
#can't access welcome if not logged in
get :welcome
assert flash[:warning]
assert_response :redirect
assert_redirected_to :action=>'login'
#login
post :login, :user=>{ :login => "bob", :password => "test"}
assert_response :redirect
assert_session_has :user
#can access it now
get :welcome
assert_response :success
assert flash.empty?
assert_template "user/welcome"
end

def test_change_password
#can login
post :login, :user=>{ :login => "bob", :password => "test"}
assert_response :redirect
assert_session_has :user
#try to change password
#passwords dont match
post :change_password, :user=>{ :password => "newpass", :password_confirmation => "newpassdoesntmatch"}
assert_response :success
assert_invalid_column_on_record "user", "password"
#empty password
post :change_password, :user=>{ :password => "", :password_confirmation => ""}
assert_response :success
assert_invalid_column_on_record "user", "password"
#success - password changed
post :change_password, :user=>{ :password => "newpass", :password_confirmation => "newpass"}
assert_response :success
assert flash[:message]
assert_template "user/change_password"
#logout
get :logout
assert_response :redirect
assert_session_has_no :user
#old password no longer works
post :login, :user=> { :login => "bob", :password => "test" }
assert_response :success
assert_session_has_no :user
assert flash[:warning]
assert_template "user/login"
#new password works
post :login, :user=>{ :login => "bob", :password => "newpass"}
assert_response :redirect
assert_session_has :user
end

def test_return_to
#cant access hidden without being logged in
get :hidden
assert flash[:warning]
assert_response :redirect
assert_redirected_to :action=>'login'
assert_session_has :return_to
#login
post :login, :user=>{ :login => "bob", :password => "test"}
assert_response :redirect
#redirected to hidden instead of default welcome
assert_redirected_to 'user/hidden'
assert_session_has_no :return_to
assert_session_has :user
assert flash[:message]
#logout and login again
get :logout
assert_session_has_no :user
post :login, :user=>{ :login => "bob", :password => "test"}
assert_response :redirect
#this time we were redirected to welcome
assert_redirected_to :action=>'welcome'
end
end


Running these tests gives:

> ruby test/functional/user_controller_test.rb
Loaded suite test/functional/user_controller_test
Started
.........
Finished in 3.526899 seconds.

9 tests, 97 assertions, 0 failures, 0 errors

The Mailer

We need a mailer to send the forgotten password emails.

> ruby script/generate mailer Notifications forgot_password
exists app/models/
create app/views/notifications
exists test/unit/
create test/fixtures/notifications
create app/models/notifications.rb
create test/unit/notifications_test.rb
create app/views/notifications/forgot_password.rhtml
create test/fixtures/notifications/forgot_password

We configure the mailer in the environment configuration file. In this case we configure it to use an smtp server.

ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.server_settings = {
:address => "mail.mydomain.com",
:port => 25,
:domain => "mydomain.com",
:user_name => "MyUsername",
:password => "MyPassword",
:authentication => :login
}

We referred to the Mailer function earlier. We saw that our model called a method Notifications.deliver_forgot_password(self.email, self.login, new_pass) to send an email with a new password to a user. When rails sees deliver_forgot_password it will look for a method called forgot_password in the mailer to setup the email. This is in the file app/models/notifications.rb. We set the subject and the recipient of the email. We also pass it the users username and password by setting values in the @body variable. These values are then available as local variables in the view for this email.

class Notifications < ActionMailer::Base
def forgot_password(to, login, pass, sent_at = Time.now)
@subject = "Your password is ..."
@body['login']=login
@body['pass']=pass
@recipients = to
@from = 'support@yourdomain.com'
@sent_on = sent_at
@headers = {}
end
end

The view used to create the email is stored in app/views/notifications/forgot_password.rhtml
_____________
Your username is <%= @login %>. Your new password is <%= @pass %>. Please login and change it to something more memorable.

-------------

This uses the variables from the body hash to construct the email.
The views

All thats left is to have a look at our views. The views just use the rails form helpers to construct the forms we need to send the appropriate parameters to each controller method and display messaged and results. These views just use the text_field and password_field methods. For these, the first argument is the model name and the second argument is the field name followed by optional arguments such as :size or :value. So <%= text_field "user", "login", :size => 20 %> indicates that the field is for the login field of the user model.

Here are the views.

app/views/user/signup.rhtml
<%= start_form_tag :action=> "signup" %>

<%= error_messages_for 'user' %>




<%= text_field "user", "login", :size => 20 %>




<%= password_field "user", "password", :size => 20 %>




<%= password_field "user", "password_confirmation", :size => 20 %>




<%= text_field "user", "email", :size => 20 %>


<%= submit_tag "Signup" %>

<%= end_form_tag %>

app/views/user/login.rhtml
<%= start_form_tag :action=> "login" %>

Login





<%= text_field "user", "login", :size => 20 %>




<%= password_field "user", "password", :size => 20 %>


<%= submit_tag "Submit" %>

<%= link_to 'Register', :action => 'signup' %> |
<%= link_to 'Forgot my password', :action => 'forgot_password' %>

<%= end_form_tag %>

app/views/user/forgot_password.rhtml
<%= start_form_tag :action=>'forgot_password'%>

Forgotten password



Email: <%= text_field "user","email" %>


<%= submit_tag 'Email password' %>

<%= end_form_tag %>

app/views/user/change_password.rhtml
<%= error_messages_for 'user' %>

Change password



<%= start_form_tag :action => 'change_password' %>



<%= password_field "user", "password", :size => 20, :value=>"" %>




<%= password_field "user", "password_confirmation", :size => 20, :value=>"" %>


<%= submit_tag "Submit" %>

<%= end_form_tag %>
Security

Since this is an authentication system we should pay attention to security. Here are some things to pay attention to:

Protect attributes. Protect any attributes that you don’t want to be updated by the user.

Guard against sql injection. Sql injection is where a malicious user passes sql as a parameter to your application in an attempt to get the sql to run on your database. Rails model methods such as create and update_attributes guard against this. When you passing parameters to your database always contruct your queries like this:

find(:all, :conditions=>["created_on=? and user_id=?",date, uid])

This will take care of quoting uid and date. Never use the ruby string substitution #{date} to construct queries.

Make sure you check ids against users. If you are controlling access to items based on the user, make sure all your find methods include the user_id in the query. E.g. if you have a multi-user blog with an edit method takes the id of the post to edit as a parameter. Dont just use the id to find the post - use the user_id in the query too:

def edit
@item=Blog.find(:first, :conditions=>["user_id=? and id=?", current_user.id, params[:id]])
...
end

See here for more about securing rails application.
Enhancements

This is a pretty basic authentication system but it could be useful for a lot of simple web apps. But your web app probably needs something slightly different. Some ways you might extend it are:

*

Store user_id instead of user. We stored the entire user object in the session. If your user has many more attributes than this model or has lots of child objects you could end up with a lot of stuff in the session. In this case you could change it to store the users id in the session and use a before_filter to send a query to the database to get the user.
*

Captcha and email validation. This doesn’t use any validation of users identity. You might want to make it a bit harder for spammers to automate account creation. You could add a captcha to the registration page. Check out rubyforge for some captcha libraries. Alternatively you could require users to validate using email. This would mean adding a field to the database with a validation key. Put a random unique hash in this field when the user is created. Don’t allow a user to login unless this field is null. Add a controller method and view to validate users using this hash key and email them a link to this with their own key as a parameter when they signup.
*

Roles / admin user. This approach only controls access based on whether a user is logged in or not. You might need more fine grained access control. You could add a role field to the user table that describes the users role. Then add a before filter the the application controller for each role. E.g. admin_required only returns true if the user is an administrator.
*

Creating a generator. If you find yourself using the same code over and over in different applications you could write a generator to automatically generate your basic authentication and start customizing it from there. Update: see my follow tutorial on creating a generator.

Update - Getting the code

The code from this article is now available as a generator.
Update - deprecation warnings with Rails 1.2

The code from this tutorial will generate some deprecation warnings when run with Rails 1.2. The updated version of the generator has some minor changes to the code to avoid getting deprecation warnings.
Update - Filtered Parameter logging

As of rails 1.1.6 you can filter form data that you don’t want saved in the log, such as passwords or credit card numbers. Adding filter_parameter_logging "password" to ApplicationController will prevent any field that matches /password/ from being logged.

Tuesday, September 11, 2007

How to set up Ruby on Rails on BLUEHOST


This is intended to be a brief introduction to developing ruby on rails applications on a bluehost account. At the bottom of this article you will find a number of resources to help you learn more about ruby on rails and related information, as well as links to some rails tutorials that will go into more depth than this document.

Before you start digging your feet into Ruby on Rails, you should understand exactly what it is. Ruby on Rails is an advanced object-oriented Model-View-Controller application framework. If you didn't fully understand the meaning of the previous sentence, you're going to need to put in some study time before you can jump into rails programming. Ruby On Rails is aimed at advanced programmers; jumping into it before you're ready is likely to be very very hard. This tutorial should be easy enough for anyone to follow, but there's a lot more to rails than you'll be learning here. This tutorial serves as a first step into ruby on rails development on bluehost.

The Model-View-Controller (abbreviated to MVC) design pattern is fairly straight-forward, it simply means that your program is split into three separate components: The Model, View, and Controller. The "Model" is your data, no matter how it's stored. If you're writing a blog, this is where all of your posts and comments would go. The "View" is your interface. In the case of ruby on rails, we're talking about the part that displays your HTML. The controller handles the business logic, and ties the model to the view. MVC programming is beneficial for many reasons.

From this point on it is assumed that you have an understanding of both object-oriented design and MVC, and now you can get into how to develop rails applications on bluehost. A few additional notes before you start:

First of all, you need to have SSH access enabled. You'll need to contact customer support for this, either by emailing support@bluehost.com or by calling support at 1-888-401-4678.

Secondly, you'll see a lot of tutorials referring to a program called "script/server" or "webrick". This is NOT NECESSARY on a bluehost account, and you should never have to use it. This is designed for people who are developing their rails application on their own computer where there is no apache install which is pre-configured to use ruby on rails. However, you do have access to such a server on bluehost, so you do not need to worry about script/server. Do not run it, as it will not even work.

This tutorial is loosely based on the excellent official ruby on rails tutorial which is located at http://wiki.rubyonrails.com/rails/pages/Tutorial, although this document has been adapted to fit some bluehost-specific things.

To begin, log into the server using SSH. You'll need a work area for your rails application. Assuming ahead of time that you may eventually want multiple applications, you should make a work directory and then cd into it. You can name it whatever you would like, but this document assumes that it is called "rails".

% mkdir ~/rails
% cd ~/rails

Now you may create your application. As we are just making a simple Hello World application, we'll assume that the application is named "first".

% rails first
% cd first

Next, we're going to set up a subdomain for this application to run on. Log into your cPanel, click on 'subdomains', then type 'first' into the first text box and click 'Add'. You've now created a new subdomain, first.yourdomain.com, which will be the new home of your ruby on rails application. Now, we're going to make your application's "public" directory be the rootdir of that subdomain with the following commands:

% cd ~/public_html/
% rm -r first
% ln -s /home/YOUR_USERNAME/rails/first/public first

You should now be able to go to http://first.yourdomain.com/, where you will see the Ruby on Rails welcome message. As the welcome page suggests, it is now time to set up your databases.

In cPanel, click on 'MySQL Databases'. The first thing you'll want to do here is add an SQL user for rails to use. You can name this whatever you'd like. We will assume you used 'rails'. cPanel prepends your username to the user name, so you should take note of the actual name created (it should be username_rails).

Next, we're adding a database. Name this database 'first', to match your application name. You will again notice that username_ has been prepended. Finally, we're going to link this username to the database. Select username_rails and username_first from the dropdowns and make sure the 'All' checkbox below them is checked, then click the 'Add User To DB' button.

Now you should repeat this step, with 'firstdev' as the database name, and linking username_rails to it.

Now we're going to edit the database.yml file. Open ~/rails/first/config/database.yml in your favorite editor, and modify the 'development' and 'production' sections to contain the username, password, and database that you just created.

production:
adapter: mysql
database: username_first
host: localhost
username: username_rails
password: password

development:
adapter: mysql
database: username_firstdev
host: localhost
username: username_rails
password: password

Next you should create the actual database data. From the mySQL page in cPanel, you should find a 'phpmyadmin' link. Within phpmyadmin, select the "_first" database from the dropdown on the left, then click on the "SQL" tab along the top. Paste the following into your box and click 'Go':

CREATE TABLE `people` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(50) NOT NULL default '',
`street1` varchar(70) NOT NULL default '',
`street2` varchar(70) NOT NULL default '',
`city` varchar(70) NOT NULL default '',
`state` char(2) NOT NULL default '',
`zip` varchar(10) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `name` (`name`)
) TYPE=MyISAM AUTO_INCREMENT=2 ;

Now, click on the SQL tab one more time, and run this query:

INSERT INTO `people` VALUES (1, 'Superman', '123 Somewhere', '', 'Smallville', 'KS', '123456');

Now repeat these two sql queries for your _firstdev database.

The next step is to create a controller.

% ./script/generate controller First list view new edit

After that has been created, you will create your model.

% ./script/generate model Person

Now we're going to modify two files. First, open app/views/first/view.rhtml and make it look like this:



Friends#view


This page will display one friend



<%= @person.name %>

<%= @person.street1 %>

<%= @person.street2 %>

<%= @person.city %>

<%= @person.state %>

<%= @person.zip %>





Next, open app/controllers/first_controller.rb and modify the 'view' method to look like this:

def view
@person = Person.find(1)
end

Congratulations, You now have a working ruby on rails application which reads information from a database. Go to http://first.yourdomain.com/first/view and you should see Superman's information.

You should now go on to read other ruby on rails tutorials. You can find a lot of helpful information at http://wiki.rubyonrails.org/, as well as at http://rubyonrails.org/docs. You should also watch the Ruby On Rails Screencasts, which show you, among other things, how an experienced ruby on rails developer can create a fully functional application in a matter of minutes using ruby on Rails.

ADDITIONAL INFORMATION AND TUTORIALS:
More information on MVC: http://wiki.rubyonrails.com/rails/pages/UnderstandingMVC
Official Ruby On Rails Screencasts. You should watch these: http://media.rubyonrails.org/screencasts
Ruby on Rails wiki. The tutorials listed here are quite helpful: http://wiki.rubyonrails.org/
Why's poignant guide to ruby: You will either enjoy this or hate it, but it's a nice intro to the ruby language: http://poignantguide.net/ruby/

Sunday, September 9, 2007

Installing Ruby On Rails(WINDOWS)

Installing Rails on Windows (step-by-step tutorial)

Ok, so this will basically be somewhat a repeat of the information made by Curt Hibbs in this great hands-on tutorial. However, the versions of all products changed from the time Curt made his tutorial, and in some areas I felt that additional description was required. So, in this tutorial you’ll get a step-by-step instructions on installing Rails on Windows 2000 Server (Windows XP would be very similar).

In order to have a fully working development environment, you can use your PC. You will need to install:

  • Ruby - the language
  • Ruby Gems - the plug-in manager for Ruby
  • Scite or FreeRIDE - IDE for Ruby
  • MySQL - the database
  • MySQL query builder / MySQL admin tools - GUI to create databases, add users and create tables
  • Rails Framework

I. Installing Ruby
This is easy. Just get the latest One-click Installer for Windows (currently 1.8.2-15). Installation is simple, and indeed one-click. Once the installation is complete, check that path to ruby\bin directory is in your PATH variable (Run “cmd”, then type “path” at the prompt and check that the path is there.

The great thing about the one-click installer is that it comes with Ruby Gems, Scite and FreeRIDE preinstalled. The thing to watch out for is that one-click installer does not have the very latest version of Ruby, so when you are reading Ruby Docs, make sure you know which version of Ruby you have (run “ruby -v” in the command prompt).

II. Installing MySQL
This might be more tricky, depending on your home computer’s set up. First, download and run the latest Windows Essentials (x86) version of MySQL (currently 5.0.18). After the files are unzipped, an Instance Config should run automatically (you can run it manually at any time from your MySQL bin directory just run “MySQLInstanceConfig.exe”).

Below are the options to use for the Instance Configuration.

1. Select “Detailed Configuration”

p1.jpg

2. Choose “Developer Machine”

p2.jpg

3. Choose “Multifunctional Database”

p3.jpg

4. Leave the next screen unchanged

p4.jpg

5. Choose “Decision Support / OLAP”

p5.jpg

6. From the security perspective it is better to have “Enable TCP/IP Networking” unchecked, however I wasn’t able to make MySQL work with Rails in that case. So, choose “Enable TCP/IP Networking” for now and leave “Strict mode” checked.

p61.jpg

7. Choose “Best Support for Multilingualism” (MySQL will use Unicode for stored data).

p7.jpg

8. Have both options checked.

p8.jpg

9. Now, create a password for root account. Make sure that you do not use an easily guessable password and do NOT leave these fields blank (ie, do not use blank password for root). Also, leave the “Enable root access from remote machines” unchecked.

p9.jpg

10. Click “Execute”. If everything went ok, you should have green tick marks for all installation steps.

p10.jpg

11. Now, you need to do some basic security tweaking, to make sure nobody hacks your database from the outside. Go to the MySQL directory (c:\mysql by default), open my.ini and add the following line to the [mysqld] section of your server configuration file:

bind-address=127.0.0.1

This line will make sure that only services from your localhost will be able to access database (so, this means you and your web server).

Now, you need to restart MySQL. Go to Services (In Windows 2000: Start -> Programs -> Accessories -> Administrative tools -> Services), find “MySQL”, highlight it, click “Stop” and then “Start” icon.

12. Quickly try if your mysql set up is working, by running this command in the prompt:

mysql.exe -h 127.0.0.1 -u root -p

You will be asked to type your password and if everything is fine, should be presented with the message “Welcome to the MySQL Monitor” and the mysql prompt. It works! You are almost there.

If it does not work - first, make sure that you are entering the password right (check Caps Lock and Language Indicator). Check that MySQL service is running (see step 11). Another usual suspect is your local Firewall (your router firewall will not interfere with the local MySQL installation). A quick fix would be to allow communication for ruby.exe and mysqld-nt.exe on all ports (check your Firewall manual or help).

III. Installing Rails

1. Go into the ruby bin directory and run this command from the command prompt:

gem install rails --include-dependencies

(If this command gives you an error, you do not have the latest version of gems installed. Run “gem -v”, it should be 0.8.10. If it’s an earlier version, I recommend to reinstall the newest package - see beginning of the tutorial).

This command will install rails 1.0 and RDoc documentation for it. It should take up to 5-10 minutes, so do not worry when for a couple of minutes you just see a message “Updating Gem source index for: http://gems.rubyforge.org/” - there is no progress bar or any other indication of activity, but it is doing the work.

At the end you should see something like this:

p11.jpg

2. After that, create a directory for all of your projects. I just have one in the ruby directory. Go into this directory by using the “cd c:\ruby/myprojectsdir” and run

rails firstproject

You should see something similar:

p12.jpg

3. Finally, “cd firstproject” and run

ruby script/server

This will run the file “server” (no extension) in the script subdirectory of “firstproject directory”, which will in turn run the Rails built-in web server WEBrick. Test the web server by opening the following address in your browser:

http://127.0.0.1:3000/

You should see the following pic:

p131.jpg

One of the drawbacks of having a Ruby-based web server is that WEBrick needs to run in its separate command prompt window, so you should be careful not to close the window down incidentally, or you will need to restart the web server.

Congratulations! YOU ARE ALMOST DONE!

IV. MySQL Admin

Here, you have several options, however the tool I love the most - MySQL Control Center - is no longer in development. You can still download it here (v.0.9.2) or here (v.0.9.4). Or you can download MySQL AdministratorQuery Browser from MySQL.com. I haven’t tried those tools yet, so I will show how to work with MySQL CC, but the steps will be very similar. and

Note on MySQL CC and MySQL Administrator / Query Browser:
MySQL CC is not fully compatible with MySQL 5.x, so it might be better to use Administrator and Query Browser. However, MySQL CC is a very easy to use bare-bones program and it allows to do everything you need in this tutorial. If you opt for Administrator / Query Browser set-up, you would use the Administrator only for creating database users (and in future for setting up security, back-ups and restarting MySQL). For all other things you will use the Query Browser (in the Query Browser databases are called “Schemas”, and when you first run QB, you can safely ignore the warning to supply the schema name).

1. Install MySQL Admin and run it.

2. Create a new database connection by clicking the icon shown (or press Ctrl-N):

p14.jpg

3. Fill out the information as shown in the picture below:

p15.jpg

Name: This is any name you want - for GUI purposes only. Call it “My Development Database”.
Host name: This would be either “localhost” or “127.0.0.1″. On some weird configs “localhost” might not work, so try to use either one.
User name: “root”. Leave it as is.
Password: your root password. Remember setting it when installing MySQL? ;)
Make this server the default connection: If you do not have any other databases, tick this box. By running MySQL CC next time, you will automatically connect to the database.

Now, click the “Test” icon, and if successful, click “Add”.

4. Create the database.

Go to the main console. It now should have your database “My Development Database”. Check that it is connected, if not click the “Connect” icon.

Right click on the “Databases” folder and select “New database”. You will be presented with the following prompt. Just name the database “firstproject”, although it does not really matter how you call the database.

p16.jpg

5. Now, back to the main console window, you should see the database name “firstproject” in the list of databases for your server. Double click on the database and then click on the “Tables” subfolder. It should look something like this:

p17.jpg

6. Now, the database for your project has been created, you need to create a test table. Right click the “Tables” folder and choose “New table”. Let’s make two fields: id (type: int) and title (type: varchar).

“id” field is required by Rails and is the usual feature for the majority of tables. You do not usually insert the “id” directly, so we need to make sure that “id” is never empty (not null) and is automatically incremented (0,1,2,3,…). Also, make sure that the “id” is written in lowercase, for Rails to understand that this is your unique id field.

p18.jpg

Now, let’s define “id” as our primary field. Go into the “indexes” tab, select “id” field and press the right arrow, it should end up looking something like this:

p21.jpg

Now, add the “title” and choose the type “varchar”, like this (length of 100 symbols should be enough for your title, but when you will be adding a field for url you would want to change it to 255):

p19.jpg

Finally, click the “Save” icon and you will be asked to name your table. Rails has a special notation for naming tables - basically they have to be in plurals, as they will hold instances of particular class (objects).

Let’s call the table “Stories” (Rails is supposed to know the plural form for “Story”, so let’s try it out). Go back to the main console window and double click the “Tables” subfolder. On the right you will see the list of tables (we have only one at the moment - called “Stories”). Double click the table, it should return an empty set, as you do not have any records yet. (Do not try to create a record - MySQL CC will not correctly do this in MySQL 5.x).

7. Finally, it would be wrong to run your project using a root password. So, you need to create the database user especially for your project. It is very easy to do. In the main console right-click on the “User Administration” folder and select “New User”. Fill out the form as shown:

p22.jpg

Username: rails (this would be the username exclusively used for your project)
Host: localhost
Password: password you will use to access this user account
Priviliges: You should provide the least possible priviliges for the user. The ones selected by default are ok, but you can actually safely leave priviliges to only “Select, Insert, Update and Delete”, as we do not plan to create, drop (delete) or alter (change structure of) tables from our Rails scripts. Finally, select, to which databases you are allowing access. Select ONLY the “firstproject” database and click “Add”.

V. Rails in action

1. Now that the database model was prepared, we need to switch it on for our project. Go into your rails project subdirectory (you remember where you created it, right?). In the “config” subfolder you will find file database.yml. Open it and find the “development” section. In it, change database name to “firstproject”, username to “rails”, password to rails user password. Leave socket unchanged. Here is how it will look like:

development:
adapter: mysql
database: firstproject
username: rails
password: railspass55
socket: /path/to/your/mysql.sock

In order for those changes to work, you will need to restart the WEBrick server. Close the WEBrick window and run the server command again:

ruby script/server

2. You have the database schema defined and you configured Rails to access your database. Now, what we need is to create the class called “Story”. The easiest way to go is to create a scaffold, which is basically a default script to work with the standard database operations under the acronym of CRUD (Create Read Update Delete or in SQL terms: INSERT, SELECT, UPDATE and DELETE). To do this, again go to your script’s directory and run this command:

ruby script\generate scaffold Story

Make sure that you use the capital “S” - this is actually a Ruby rule that you need to follow - class names need to be capitalized. So, this is where the magic happens - you do not need to define the class in Ruby the “hard-way”. You define the class variables (so-called states) only once - in the database, and then those variables are automatically recognized by Rails and a standard CRUD framework is applied, which you can replace with your own code as you go.

3. Finally, check that everything works by going to this link: http://127.0.0.1:3000/stories/

For fun, try creating, updating and deleting records and see how the table records are automatically updated in the MySQL CC or Query Browser.

IN CONCLUSION
Installing Ruby on Rails is not a difficult process, but it takes some time. However, once installed you will not only feel comfortable with basic MySQL administration, but will also get a development environment which is much faster and easier to use than a hosting based alternative. Once you create a web service that can be released to public, you can easily move it to one of the Rails hosting providers.