User:WillWare/Learning Ruby on Rails

Getting started
I've moved notes about the Ruby language itself to their own separate page.

Good books: Rails tutorials:
 * The Ruby on Rails Wikibook
 * Agile Web Development with Rails by Thomas Heinemeier Hansson, published by Pragmatic Bookshelf
 * Rails Pocket Reference by Eric Berry, published by O'Reilly
 * http://betterexplained.com/articles/starting-ruby-on-rails-what-i-wish-i-knew/ -- I respect this guy's opinion and he recommends getting set up with the following.
 * Agile Web Development with Rails, better than the online tutorials
 * InstantRails : A .zip file containing Ruby, Apache, MySQL and PHP (for PhpMyAdmin), packaged and ready to go.
 * Aptana/RadRails (like Eclipse) or Ruby In Steel (like Visual Studio) for editing code.
 * Subversion and/or TortoiseSVN for source control. Personally, I prefer Mercurial.
 * Browse popular ruby on rails links at del.icio.us/popular/rubyonrails, Rails documentation and Ruby syntax and examples . I would have provided a link for the del.icio.us site except Wikipedia considers them a spam source, and prevents linking to them.
 * http://guides.rails.info/getting_started.html
 * http://railscasts.com/

Install Ruby and Rails on Ubuntu, and create a Rails application called "first". Look at all the files and directories. The app-specific stuff is in first/app. Now we can turn on the server for this application. Go to your web browser and look at http://localhost:3000/ and behold, Rails is working. Hit control-C on the server process to get your prompt back.

A controller that does something
Rails follows the model-view-controller pattern. Let's set up an app. This has produced a controller, but it doesn't do anything yet. It's simply a class that inherits from ApplicationController but is otherwise empty. Remember how amazingly ugly CGI scripts were? Rails offers the same functionality without making you throw up a little bit in the back of your mouth.

In Rails parlance, an action is something the controller can do. Controller actions correspond to the "action" of an HTML form, the thing to be done when you the Submit button. To go with each action, there is a view that tells what HTML to send back as a response.

So let's do some HTML form stuff. In this example, "there", "handleCheckbox", and "handleMultipleSelect" are controller actions. The at-sign (@) in front of "data" indicates an instance variable of this class.

Here are some views
Put this in first/app/views/hello/there.rhtml Put this in first/app/views/hello/handleCheckbox.rhtml. Here we are using "&lt;%" and "%&gt;" to embed bits of Ruby code into the HTML. You may have seen things like this done in PHP or JavaServer Pages or Active Server Pages. This has an acronym in Rails parlance: "ERb", for "embedded Ruby". Put this in first/app/views/hello/handleMultipleSelect.rhtml. Here we're using "&lt;%=" instead of "&lt;%", meaning that the value of the Ruby expression should appear as part of the HTML rendered in your browser.

Accessing a database
Ruby works with databases like MySQL or Oracle using ActiveRecords which implement object-relational mapping, also used in Django and Hibernate. SQL tables are represented as classes, rows are represented as instances of those classes, and columns are represented as properties of the class.

By default, Rails uses SQLite, but here I'll use MySQL because it's more scalable to large datasets. Once you've got MySQL working, it's a short hop to even bigger databases like Oracle, if you suddenly discover that your web app is popular beyond reason. On Ubuntu, you'll first need to provide Ruby with access to MySQL.

Create an application which will use the database. Let's make it a list of our favorite movies. The database.yml file (a YAML file) is used to configure how the database will be accessed. development: adapter: mysql database: Movie username: root password: **** pool: 5 timeout: 5000

Creating a scaffold
Next generate a scaffold for a simple database-backed app, create the database in MySQL, and start the application server. The scaffold is a starting point, minimalist in every sense. The objects are POJOs, the HTML presentation is bare, the app logic is a direct viewer/editor of database table contents. This creates the following table in MySQL. It also creates app/controllers/movies_controller.rb, ready to handle a variety of HTTP requests. Now visit http://localhost:3000/movies in your browser, and you've got a very simple little movie database. For your first movie, enter a classic. Title: Alien Description: Sci-fi horror Url: http://www.imdb.com/title/tt0078748/

Customizing the app
The scaffold command is intended only to give you a starting point for your app. It lays out a minimal framework which you can then modify. So feel free to tweak the logic and presentation to suit your needs and preferences. I put a visible border around the cells in the HTML table, and replaced the URL with an HTML link. Notice the "&lt;%=h" prefix used below, where the "h" means that the text will be URL-escaped. We don't want that with the URL. ...

ActiveRecord and Object-Relational Mapping
Like Django and Hibernate, Rails uses classes to represent database tables, with getters and setters for columns, and class instances represent rows in the table. This is called object-relational mapping or ORM. In Rails, these classes extend a base class called ActiveRecord.

Let's look at how ORM works in Rails. The BetterExplained.com website has some good explanatory stuff.

We've just finished running the scaffold command, and we look to see what's been done. The file app/models/movie.rb is nearly empty, and probably intended for subsequent customization. The setters and getters for the fields are hiding inside the ActiveRecord base class, but we can override them to do special things if desired. Next we have db/schema.rb, which (SURPRISE) specifies the database schema. In addition to the fields specified in the scaffold command, we also have "created_at" and "updated_at", and our database schema has been assigned a version number based on a timestamp.

Queries
If ORM is a way to encapsulate the database, there must be ORM equivalents for common SQL queries. A typical SQL query would request a set of database rows that matched zero or more criteria, like this.
 * SELECT * FROM movies WHERE title="Gone with the Wind"

Here is the equivalent thing in Rails. More information is available.

Migration
Migration is the process of making changes to the database schema without trashing all the data you've collected. The version number (20100228142908) helps Rails to keep track of things while migrating. The actions to be performed for a migration of a particular version of a particular SQL table appear in source files like db/migrate/20100228142908_create_movies.rb. Rails maintains a table called schema_migrations, notating which migrations have already been done, so it doesn't repeat them.

Connecting Rails to a pre-existing MySQL database
I've been asked by a customer to connect his already-in-place MySQL database to a Rails app. Looking at the table schemas, I can see two potential problems. The first is that there are plenty of "VARCHAR(N)" fields where N is not 255, the value that Rails assigns by default. Luckily this number can be changed, so we could switch to a movie title of VARCHAR(100) like this. My other concern is that Rails likes to append two timestamps at the end of a table schema, "created_at" and "updated_at", but these aren't present in my customer's database. I can remove the references to the timestamps from db/schema.rb and db/migrate/20100228142908_create_movies.rb, and re-run "rake db:migrate". From what I can tell so far, that's OK.


 * &lt;YIKES&gt; I missed one more thing to worry about. Rails has added an "id" integer to each database entry, and I don't see any way to remove it. The relevant table in my customer's code already has an "id" integer field. When I do "rake db:migrate", will all those integer id's be reassigned?


 * Probably the best approach would be to back up the existing database to some other form of storage, and then be able to reconstruct it in its exact original (pre-Rails) form if I screw anything up. This could be done by collecting the output from "SELECT * FROM tablename" statements, and then do some Python scripting of those. &lt;/YIKES&gt;

URLs and URL Routing
If we skip all the contents in config/routes.rb, this is what we have immediately after the scaffold command. Note that routes can include information about the format we want in the response. If we set up the movie example above and then browse http://localhost:3000/movies.xml, we see this. This is very useful because it means we can get Rails to serve XMLHttpRequests used in AJAX applications.

Routing examples
These are taken from the comments that are automatically inserted into config/routes.rb when you run the scaffold command.

The priority is based upon order of creation: first created -> highest priority. You'll want to install the default routes as the lowest priority. Default routes make all actions in every controller accessible via GET requests. You should consider removing the them or commenting them out if you're using named routes and resources.

Sample of regular route: You can assign values other than :controller and :action.

Sample of named route: This route can be invoked with purchase_url(:id => product.id)

Sample resource route (maps HTTP verbs to controller actions automatically):

Sample resource route with options:

Sample resource route with sub-resources:

Sample resource route with more complex sub-resources

Sample resource route within a namespace:

You can have the root of your site routed with map.root -- just remember to delete public/index.html.

You can see how all your routes lay out with "rake routes". When applied to the movie database app, we get this.

URL parameters
You want to use a URL parameter like this:
 * http://localhost:3000/things/events/609440379.xml?x=73

Then you can do something along these lines in the controller.

The console
Go into your project directory and type This will allow you to interact in irb-like fashion with the classes in your Rails app. You can create instances of models. You can also use a very cool GDB-like debugger called ruby-debug, but in order to do that in Ubuntu, you need to have done a little bit of preparation. The best thing to do next is to put the method call "debugger" in your source code as a sort of breakpoint. Then run your code in the console and when it reaches this point, you'll pop right into ruby-debug. An example might look like this: You can print expressions in the debugger by typing "p ". You can see the lines of source code by typing "list". You can see all the other available commands by typing "help", and find out about any specific command by typing "help ".
 * It would be really useful here to show an example debugging session, especially one that really uncovers some unexpected problem.

On Weds 3/24/2010, I went to the Metrowest Ruby Users Group and there was some discussion of the best strategy for developing Rails apps. The recommendation was to build models and tests for models first, get all that stuff debugged in the console, and then write views. This ran counter to what I had been thinking in developing my Django app. I had been thinking that if I mocked up my views first, it would force me to think about my database schemas, and then I'd build the models and business logic after the schemas were done.

I think that recommendation makes plenty of sense if you've already got your head around what your schemas should look like. Maybe it's tolerable to make a relatively brief early diversion into the views, just to clarify one's thinking about schemas. So maybe I'm not so far off target as I would have thought.

Ajax
Let's suppose you have a database table, where each row has a field of type, and it's natural for the user to want to look at the rows whose start times fall within a particular span of time.

On each Ajax request, the client requests a subset of table rows specified by a time span defined by variables and, a couple of objects. The goal on the server side is to package these into an XML file and send them back to the client. The client's job is to receive the XML, parse it in Javascript, and handle presentation to the user.

The code in the controller looks like this. This method is used to populate an embedded-Ruby XML file,. Javascript initiates each Ajax transaction, and then sorts out the details of presentation, using jQuery to parse the XML. The selected rows from the database table are stored as Javascript arrays in the list. Depending on your application, it might instead make sense to do the presentation immediately in the Ajax callback.

Prototype, Scriptaculous, etc
Hmm, looking at some of comments on a Ruby blog, it looks like the Ruby-community-preferred thing to do is to forego jQuery in favor of Prototype. Sorry, I tried that, and doing tooltips without jQuery is a nightmare.

Rails makes available some Javascript libraries including Prototype and Script.aculo.us. My tinkering with them has been minimal. In poking around the web, these were the resources that looked likely to be useful.
 * http://weblog.rubyonrails.org/2007/11/7/prototype-1-6-0-and-script-aculo-us-1-8-0-released
 * http://blog.codahale.com/2006/01/14/a-rails-howto-simplify-in-place-editing-with-scriptaculous/
 * http://www.refreshaustin.org/presentations/prototype-scriptaculous-crash-course/
 * http://prototypejs.org/learn
 * http://particletree.com/features/quick-guide-to-prototype/
 * http://www.oreillynet.com/xml/blog/2006/06/the_power_of_prototypejs_1.html
 * http://www.sergiopereira.com/articles/prototype.js.html
 * http://domscripting.com/book/contents/
 * http://www.phpriot.com/articles/beginning-with-prototype/6

Dumb lessons, hard won
If an Ajax request isn't working as expected, always look at the XML that comes back in response.
 * http://localhost:3000/entries/events/609440379.xml?param=74

Obviously, use Firefox's error console to look at Javascript errors, but it's also good to add a little JS logging of one's own. I do this in the HTML. and then I do this in Javascript: I find I am much more comfortable with Python than Ruby, and I'm thinking about switching to Django for the current project and coming back to Rails later, when I'm not trying to rush to get something working.