If I get this wrong, please let me know! In reading this, I'm assuming, you've got Rails installed, can run ruby commands in any folder, and have at least written your first Rails Hello World app. I'm also assuming you've got Rails 1.2.3 on Mac OSX 10.4.10 with Ruby 1.8.6. This guide should help you on your way as you write your first few apps during which you need a really quick common reference. This is a work in progress, so there are lots of gaps. Please get in touch if you found this useful, or if you'd like to see me write more on a specific subject.
BTW, I'm not a Rails genius, or a pure Software Engineer, so please accept my apologies if I get things wrong, or trivialise things which aren't trivial. In the course of me learning Rails, I'm finding myself use these documents again and again...
Easy. Rails will generate all the code for you. Go to the directory you want you app folder structure to live in and type
> rails schoolmanager
where schoolmanager is the name of the app. Rails will create a schoolmanager folder and fill it with the normal Rails app folder structure.
To run the app, open a new terminal window - one you can keep open in the background - and type..
> cd schoolmanager > ./script/server
This will start WEBrick or Mongrel. Go to http://localhost:3000 in your browser and you'll see the Rails welcome screen. Remember your app will tend to work at the URL domain.com/controller/action/variable - i.e. http://localhost:3000/article/view/320 to View the 300th Article. You can stop the Rails server by running ALT + C.
Rails has three operating mode: development, testing and production. I bet you can guess what they're used for. By default, when you run up a new Rails app, its in production mode. This means you can make changes to the source code and they run immidiately without having to bounce the web server.
Migrations allow you to set database versions with an UP and DOWN method - allowing you to form step changes to your database. You never need to execute SQL directly, instead just define your DB changes in a Migration script, and Rails will do it all for you.
To create an empty Migration script ready for you to fill out, run..
> ruby script/generate migration name_for_migration_in_human_readable_text
This will create a new migration file in <approot>/app/db/migrate. Open this file, and add you'll see it contains self.up and self.down methods. In self.up you place code to take your DB from its previous version to the new version, and in self.down to write the code to take it back the the previous version - in case you need to rollback. So what code can you add?
Here's how you might update an existing table called users and create a new table called products... and then the down method to undo these changes...
def self.up rename_column :users, :password, :hashed_password
remove_column :users, :email create_table :products do |t| t.column :name, :text t.column :description, :text end end def self.down
rename_column :users, :hashed_password, :password
add_column :users, :email
drop_table :products:
end
Remember, Rails automatically creates 'id' coulmns for each table so no need to do that manually in a self.up statement.
Some other useful examples are... (optional attributes shown in [ ])
Also read the full Rails documentation on Migration syntax.
Once you have created your migration, you need to run it on your DB. To do this, run...
> rake db:migrate
This will run all the migrations scripts necessarry to take your current DB up to the latest version. This means that if you're on version 004, and there are migrations 005, 006 and 007 in the <approot>/app/db/migrate folder, then it will run each of these in turn, and your DB will then be at version 007. Neat.
For more on Migrations - see this Rails Migrations Screencast.
Rails is clever, we know that. There are certain column names that if you create a column with one of these fieldnames, it will do clever stuff.... (the important fieldnames are in bold)
For a full list of Magic Field Names, see the Rails documentation.
Models represent chunks of data. They generally map to database tables - like People, Rooms, Lessons, Subjects in a School app for example. To create a blank model, type...
> ruby script/generate model mymodelname
The name of a model should be singular i.e. Task not Tasks, Person not People. If it maps to a database table (as is normal) you make the database table name plural. DB Table lessons maps to a model called lesson. Rails is clever at this pluralisation - it can understand that people is the plural of person and histories is the plural of history.
Its also good practice that when you run the generate script, the model name is all lowercase. The actual model generated will have a capital letter thus the script command above generates a model file called mymodelname.rb but if you look in this file, the model will actually be called 'Mymodelname' .
Rails can't reliably get mappings between models by examining the database, so you have to define them in the models. This is supereasy however.
Say you have a database of rooms in buildings. This is a one to many relationship. A buildling has many rooms, but a room is only in one building. To model this, you have two database tables: buildings and rooms - and two models: building and room.
First if you haven't already, create a database migration to ensure the rooms table in the db has a field called building_id, and that this is an integer. This maps the room record to a building ID.
Secondly update the models to hold this relationship. In the room model add the line
belongs_to :building
then in the building model, add the line
has_many :rooms
er, thats it. Rails now understands these two models are connected - knows how, and can deal with it. This means you can now access rooms as objects within a building object i.e....
Building.find(23).room.create(params[:title])
...which means we are creating a new room with the passed 'title' parameter within Building with an ID of 23. Seriously, in PHP, this'd take an age. In this code, we're taking a Building object, getting a specifc one, then creating a new child Room object - and all this is done in one line.
Now, its often useful to be able to join two models together in multiple ways - many to many relationship. One example might be tags to new articles. An article has multiple tags, and a tag might appear in multiple articles. Many to Many. Fortunately, its easy in Rails to model this too.
There are two ways to create many-to-many relationships - 1) the easy but inflexible way using has_and_belongs_to_many and 2) the cleverer way of creating a join model. The latter (known as 'has_many :through') allows you to store information about the relationship i.e. ordering, created_at etc - the former doesn't, but is much easier to setup. Lets look for now at the simple has_and_belongs_to_many way.
Say we have a table articles and a table tags - and their associated models article and tag. We create a new join table (using a migration script) and call this articles_tags. A common slip up is this... the name of the join table must be the name of the two tables to join in ALPHABETICAL ORDER. We give it two fields, article_id and tag_id. This is pretty normal. Another thing to remember is that in a join table you CANNOT HAVE AN 'ID' COLUMN. If you do, Rails will get confused when it tries to add more than one relationship to the table. To to this, add the ':id => false' declaration. The migration self.up statement might look like this...
create_table :articles_tags, :id => false do |t| t.column :article_id, :integer t.column :tag_id, :integer end
Now the clever part. In the article model add the following line...
has_and_belongs_to_many :tags
and in the tag model add..
has_and_belongs_to_many :articles
And thats pretty much it. Rails now understands that these two models interconnect, and it will majically manage the jointable for us. However, as creating a user interface for managing many-to-many relationships is hard (there are loads of ways to do it, and it depends on the models having title and/or description fields etc), we have to do this bit ourselves - normally after generating a static scaffold and then modifying the views and controller.
Heres a great screencast explaining the two ways of doing many-to-many associations... http://railscasts.com/episodes/47.
In Rails, the model is the gatekeeper to the Database. This mean's its the obvious place to put data validation - like ensuring fields have valid data. This means incorrect data can never be written to the DB, and we can build an automated clientside form validation system to provide a nice user interface to the validation settings - and these validation settings propagate through the entire app from the model upwards. Best of all, its really easy to do.
In essence, you add a line to your model for every database table you want to validate. If you had a users table and wanted to make sure every use had to have a name and email address, and that the email address was a valid one, you'd add this to your user model...
validates_presence_of :name
validates_presence_of :email
validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
Annoyingly, theres no built-in email format checking, but you can use a regular expression to match to an email - and the one used above was lifted from the Rails API documentation itself. There are a number of these validation statements avaliable in Rails, and using them is as easy as picking one, and telling it which field to apply it to. As you guessed, in the example above name and email are database column (field) names. Heres a list of some of the avaliable validation methods...
Full list avaliable here.. http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
Controllers are the glue between the user (view) and the database (model). They control the 'flow' of the app (i.e. what happens when you click what) and interrogate the database to get info which it can pass to the View to be formatted in a useful way (normally HTML).
To create Controlers, theres another handy script... (controller name is general singular i.e. project not projects)
> script/generate controller my_controller_name
A scaffold is a way to get started. Once you have a model, it will generate CRUD (Create, Read, Update, Delete) operation code so you can manipulate data in the model. You can render scaffold on the fly (really useful for very early development) or have it rendered to files to you can take it and run with it by hand.
To use a dynamic scaffold, add the following line to a controller.
scaffold :modelname
again where modelname is singular. To generate a static scaffold, invoke another generator script...
> script/generate scaffold my_scaffold_name
Again, the scaffold name is normally the singular aspect of the model - i.e task or project. Generating a scaffold will create an appropriate controller (if one does not exist) and will also generate a new model if it can't find one to match the name you entered. See also Rails Wiki Scaffold Generator page.
Views are the Rails term for output templates. Normally, they are HTML, but can be XML, YML, JSON or any other textual output format. They use Embedded Ruby (ERB) which means they look like templates, that is the raw output code with code which gets replaced at runtime. In ERB, the Ruby code is contained in <% %> parentheses.
Rails has a nice way to get the same look and feel all your app's pages (the skin of the onion if you will) and theres not a single server-side include in sight. Its called Layouts. Layouts are the HTML page structure which surrounds your page content. It generally contains the<html>,<head>,</head> and <body> elements - then a gap for your main page - then the </body> and </html> tags. When you create a Scaffold, a layout file will be generated for each controller. This means if you have user_controller.rb, you'll usually also have <approot>/app/views/layouts/user.rhtml as your layout for the user controller.
While this is very useful, its not great if you want the same header and footer across every page on every controller on your site. www.mydomain.com/users needs to look the same as www.mydomain.com/someothercontroller.
To do this is really easy. In <approot>/app/views/layouts/ create a new file, perhaps called main.rhtml. Copy and paste in the code from one of your existing layouts into your new one. This will become the layout we use everywhere. Next, we have to tell each controller not to use it's own default layout, but to use the new generic site-wide one. Just add the following line in to each of your controller....
layout "main"
Magically, each controller will now use the same main.rhtml layout file. At this point, if you want to, you can delete the layout files which were generated by the Scaffold generator which we're no longer using.
There's a couple of things to be aware of. Firstly, if we want a manubar with links to the list page for each controller, when each controller had its own layout, you could use the following statement (for example in a 'project' controller's layout file) without worrying about it...
<%= link_to 'Projects List, :action => 'list' %>
But now, if we put this into the main.rhtml global layout, its not always going to link to the project list view. Instead, it will link to the list view of whatevery controller we're currently viewing. Not cool. So, instead we can use a slighly different version of the link_to helper call...
<%= link_to "Master Help Index", { :controller => "help", :action => "index" } %>
Much nicer. More info here http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html.
A View is a way of laying out data. But how do you know exactly which data you have avaliable to you? Well, in a view, you can use the following statement
<%= debug @objectname %>
And this will output a Ruby Array of all the data avaliable in that object. Note, it will only list data directly associated with that object. If you want associated object data (perhaps that associated to the main object with has_many in the model, then use...
<%= debug @task.histories %>
For history objects associated to the task object for example.
Rails deals with datetime stamps in clever way which allows for timezone mapping etc, but they're not very human readable. Soooo you can format them so they are. This is best done in the model or in partial so that your human readable format can be used in multiple placed, but for ease, lets imagine we're doing it in the view.
A line of code like this
<%= @history.created_at %>
might produce "Thu Dec 27 20:31:00 +0000 2007" If you don't like this, you could reform this string as the result of passing it through the strftime function. In this case...
<%= @history.created_at.strftime('%a %d %b %Y, %I:%M%p') %>
might give you "Fri 28 Dec 2007, 02:06PM". Much nicer. This is very similar to PHP's date() function. Full documentation on the % codes to use is on the Rails Define Your Own Date Format wikipage.
One weird thing is that if your created_at variable happes to be nil (perhaps because it's old data from the table before you created the created_at field, then this line will error. This is because you can't pass nil values to strftime. To get around this, put some error checking and/or substitution in your model. Or you could use the horrible hacky way and append the command above so it reads...
<%= history.created_at.strftime('%a %d %b %Y, %I:%M%p') unless history.created_at.nil? %>
But I didn't tell you to do this right? It just demo's that you have to be careful about Data Integrity.
Alternatively, rather that writing your own date time format, there are a number of human-readable formatting commands built into Rails. For these try these which extend the previous example...
<%= @history.created_at.to_formatted_s(:db) # => "2007-01-18 06:10:17" %> <%= @history.created_at.to_formatted_s(:short) # => "18 Jan 06:10" %> <%= @history.created_at.to_formatted_s(:long) # => "January 18, 2007 06:10" %> <%= @history.created_at.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10" %> <%= @history.created_at.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600" %>
More details in the Rails documenation on Time and Date conversions.