Rails BootCamp

Chew Choon Keat
choonkeat@gmail.com


SMU, Singapore

14 March, 2006


 
Not Covering
  • Production Setup
  • Capistrano (Switchtower)
  • Engines
  • Rails 1.1 (ok, just a little)


 
Scope
  • Essentials
    • Ruby
    • Controller
    • View
    • Model
  • Email
  • Webservices
  • Plugins
  • Rails 1.1


 
Ruby
  • irb, script/console & 5.days.ago
  • Everything is an Object
    • nil, "string", :symbol, 
    • hash{}, array[]
  • Method, (), ?, !, return
  • Exceptions
  • Blocks, ;, {} & Closures
  • Threading
  • method_missing, send, eval


 
Ruby
class OpenClass
  def methodX
    "hello"
  end
end

class OpenClass
  alias :old_methodX :methodX
  def methodX
    "hoohoo" +  old_methodX
  end
end

obj = OpenClass.new
puts obj.methodX



 
Ruby
  • Useful idioms

variable ||= "default value"

{ :property => "default" }.merge(params)
params.merge :property => "default"

<%=h object.method rescue nil %>


 
Essentials
  • Start Server
  • Environment
    • app, db, log, config, script (console, generate), public, vendor
  • Application Code
    • controller, helpers, models, views
  • Testing, Debugging
    • rake test_functional, test_units (more later)
    • script/breakpointer


 
Controller
  • Standard stuff
    • params[], session[], headers[]
  • "New" concept: flash[]
    • Tapestry 4.0
  • filters
  • routes aka "Pretty URLs"


 
View
  • app/view/layouts, shared
    • render partial
  • rhtml
    • link_to, form, javascripts, stylesheets, select
    • h(), debug(), <%- -%>
  • rxml
  • rjs
  • markaby


 
Model
  • Convention over Configuration

Item =>  items
Person => people

EmailMessage => email_messages


ActiveRecord::Base.pluralize_table_names = false


updated_at, created_at, id, xxx_id, position, type...

more at,
http://wiki.rubyonrails.com/rails/pages/MagicFieldNames



 
Model
  • Core operations
    • new, create
    • find, save
    • delete, delete_all
    • destroy, destroy_all
  • Inheritance
    • Manager < Employee


 
Model
  • Object relations
Picture has_one :thumbnail
Thumbnail belongs_to :picture
# SQL: pictures.id == thumbnails.picture_id
# Ruby: pic01.thumbnail, thumbnail02.picture

User has_many :computers
# SQL: users.id == computers.user_id
# Ruby: bob.computers

User has_and_belongs_to_many :cults
# SQL:
#   users.id == cults_users.user_id
#   cults.id == cults_users.cult_id
# Ruby: john.cults, mac_cult.users


 
Model
  • Composite objects

class Customer < ActiveRecord::Base
  composed_of :balance,
              :class_name => "Money",
              :mapping => %w(balance amount)
  composed_of :address,
              :mapping => [%w(addr_street street),
                           %W(addr_city city)]
end

customer = Customer.find(:first)
customer.balance.credit(5)
customer.address.city
 


 
Model
  • Callstack
     (-) save,
     (-) valid?
     (1) before_validation
     (2) before_validation_on_create
     (-) validate,
     (-) validate_on_create
     (4) after_validation
     (5) after_validation_on_create
     (6) before_save
     (7) before_create
     (-) create
     (8) after_create
     (9) after_save


 
Model
  • Multiple Databases
Class.establish_connection "ext_#{RAILS_ENV}"

  • Transaction
Account.transaction(david, mary) do
  david.withdrawal(100)
  mary.deposit(100)
end
Account.transaction do ... end

  • Lots more...
    • validate vs validate_on_update,
    • composed_of,


 
Model
  • Migrations 
    • upgrading, patching

    create_table "caches", :options => ' ' do |t|
      t.column "feed_id", :integer
      t.column "digest", :text
      t.column "content_digest", :string, :limit => 32
      t.column "created_at", :datetime
    end    
    drop_table "caches"

    class MockItem < ActiveRecord::Base
      set_table_name "items"
    end
    MockItem.find(:all)


 
Email
  • script/generate mailer
  • deliver_xxx(), create_xxx()
    • app/views
    • multipart
  • receive()
    • setup like spam filters
    • e.g. procmail


 
Webservices
  • WSDL, SOAP
  • script/generate web_service
  • web_service_scaffold :scaffold


 
Plugins
  • Open classes
  • vendor/plugins

xxx/init.rb # require 'yyy'
xxx/lib/yyy.rb


  • script/plugin
    • discover, source, unsource,
    • install,... rm -rf


 
Rails 1.1
Polymorphic Associations

class Address < ActiveRecord::Base

  belongs_to :addressable, :polymorphic => true
  #
sql cols: addressable_id, addressable_type
end

class User < ActiveRecord::Base
 has_one :address, :as => :addressable
end

User.find(:first).address
Shipment.find(:first).address


 
Rails 1.1
with_scope

Member.count # => SELECT COUNT(*) FROM members
Member.find(1) # => SELECT * FROM members WHERE id = 1
Member.delete_all # => DELETE FROM members
Member.with_scope(:find=>{:conditions=>"group_name = 'Berryz'"},
:create=>{:group_name => 'Berryz'}) do

Member.count # => ... WHERE group_name = 'Berryz'
Member.find(1) # => ... WHERE group_name = 'Berryz' AND id = 1
Member.delete_all # => ... WHERE group_name = 'Berryz'
Member.create
  # >> #<Member: @attributes=>{:group_name=>"Berryz"}>
end

more at
http://scottraymond.net/articles/2006/02/28/rails-1.1


More at,
http://scottraymond.net/articles/2006/02/28/rails-1.1


 
Resources

Ruby

Programmning Ruby (1st Edition)
http://www.rubycentral.com/book/index.html

Why’s (Poignant) Guide to Ruby
http://poignantguide.net/ruby/

Rubyforge: Open Source Ruby Projects
http://rubyforge.org/

Rails

Rails Weblog
http://weblog.rubyonrails.org/

Rails Wiki: HowTos, FAQ
http://wiki.rubyonrails.com/

Rails API
http://api.rubyonrails.org/
http://corelib.rubyonrails.org/

Rails Weenie: Tips, Questions, Answers
http://rails.techno-weenie.net/

Ajax

Ajax Effects with Prototype.js
http://script.aculo.us/

Prototype.js
http://prototype.conio.net/

Dojo
http://dojotoolkit.org/

Ajaxian Blog
http://ajaxian.com






















Patch#1
 

Topics that were regretfully not covered during the bootcamp...


1. Qn: "I've installed Rails... but where's my Rails?"


After installing, you only have the core libraries and command. No web directory. Think like installing Java. You have java, and the standard libraries. But you write your Java apps wherever you want.

Similarly, just run this command:
rails mywebapp

and you should see mywebapp sub directory created in your current directory. all the config, logs, app, stuff are inside that directory, including script/server. So you can immediately use the build-in Ruby webserver to run your app:
cd mywebapp
ruby script/server
(windows user type script\server instead)

2. More on params[]

Try not to use @params. Use params method instead. Why? Same reasons as when in Java, use user.getName() and not user.name

Say.. given the HTML form like this:
<form action="/student/save_new" method="post">
<input type="textfield" name="user_id" />
<input type="textfield" name="user_password" />
<input type="submit" />
</form>
If you submit, you can retrieve the parameters in your controller via params[:user_id], params[:user_password]
i.e. you can do
class StudentController < ApplicationController
  def save_new
    User.create(params) # params is a Hash
  end
end
But did you know that, is the form fields becomes...
<form action="/student/save_new" method="post">
<input type="textfield" name="user[id]" />
<input type="textfield" name="user[password]" />
<input type="submit" />
</form>
If you submit, you retrieve the parameters with params[:user][:id], params[:user][:password]
i.e. you do
def save_new
  User.create(params[:user]) # params[:user] is a Hash with :id and :password as keys
end
As a bonus,
<input type="checkbox" name="hobby[]" value="basketball" /> Basketball
<input type="checkbox" name="hobby[]" value="baseball" /> Baseball
<input type="checkbox" name="hobby[]" value="soccer" /> Soccer

can be retrieved by params[:hobby] and it is an.... Array,
params[:hobby] == ["basketball", "baseball", "soccer"] # if all checkboxes were selected
So, extrapolate a bit..
<input type="checkbox" name="user[hobby][]" value="basketball" /> Basketball
<input type="checkbox" name="user[hobby][]" value="baseball" /> Baseball
<input type="checkbox" name="user[hobby][]" value="soccer" /> Soccer

will be...
params[:user][:hobby] == ["basketball", "baseball", "soccer"] # if all checkboxes were selected
What's all these [] magic good for? Well, if you have a form for creating only 1 user, any of the HTML forms above is fine. Really. However, if your form need to allow user to enter say,.. 10 records at the same time (i.e. 10 pairs of names and ids... )... in the Rail-ish way, you can now do:
<input type="textfield" name="user[0][id]" />
<input type="textfield" name="user[0][password]" />
<input type="textfield" name="user[1][id]" />
<input type="textfield" name="user[1][password]" />
<input type="textfield" name="user[2][id]" />
<input type="textfield" name="user[2][password]" />
... blah blah blah, of course you can do a for loop inside the rhtml instead of hand coding..

You can parse the form values in your controller action like this:

10.times do |x|                        # loops thru 0 to 9
  User.create(params[:user][x])  # params[:user][x] is a Hash of :id and :password
end
Now, doesn't that look much more elegant and succinct than what other platforms allow you to do?


3. Watch the 15 minutes video CLOSELY. Everything that matters is already in there.


Patch #2

More on render :partial - If I do this inside my View, e.g. list.rhtml
render :partial => "template_name", :collection => ["Alpha", "Beta", "Charlie"]
I can then reference the objects in _template_name.rhtml with automagically created local variables:
<%= template_name %>, index <%= template_name_counter %><br />
The above should produce the following HTML:
Alpha, index 0<br />
Beta, index 1<br />
Charlie, index 2<br />