Rails v1 Notes

Dec 20, 2011 Published by Tony Primerano

Documentation

API -> http://ap.rubyonrails.org/

Conventions

Rails philosophy is Convention over Configuration

  • URL (with default routing)
    • 1st part is controller
    • 2nd is action
    • 3rd (if any is id)

ex: http://tonycode.com/store/view_item/1

Table Names are Plural but the Model is singular

  • ruby script/generate model Author
    • Generates Authors table and Author class

Local Variables, method parameters and method names all start with lowercase letter or underscore

Instance variables start with @

Underscore is used in ruby. Not camelCase for methods and variables

Class Names, module names and constants start with uppercase. CamelCase is used instead of underscore

Symbols start with :. Treat : as the thing named.  :id is the thing named id. They are used as keys for method parameters and hash lookups.

Migrations use the verb create when adding a table and uses add when adding columns to an existing table

Gotchas

Rails picks up changes to files in development and test but in production you need to restart the server. This keeps production fast by allowing the dispatcher to load files once.

Common functions

  • h outputs escaped html
    • <%= h("I <3 Rails & Rails") %>

 

Variables

Variables starting with @ are instance varaibles. They can are passed from controllers to views

 

Tools and Commands

  • gem is used to keep packages up to date. Like rpm
  • rake is ruby version of make
    • rake db:migrate does migrations
    • rake rails:update updates rails'
  • The ruby script directory contains various commands
    • ruby script/generate model <model name> -- sets up new table (muse migrate before created)
    • ruby script/generate scaffold ModelName [ControllerName] [action, ...] -- builds static scaffolding
    • ruby script/generate migration MigrationName [options] -- builds a migration file
    • ruby script/generate controller ControllerName [options] -- builds a controller
    • ruby script/server [server type]-- starts the web server
    • ruby script/console [environment] -- brings up IRB
    • ruby script/destroy generator [options] [args] -- used to delete model, controller, etc.
    • ruby script/about --- shows versions of everything

Updating rails

  • gem update rails --include-dependencies
  • gem cleanup if you want to remove old libraries
  • from app run
    • rake rails:update
    • I needed to edit the RAILS_GEM_VERSION in <my app>/config/environment.rb before rake worked. New apps are probably fine.

 

Ubuntu setup notes

  • Install ruby and mysql
    • sudo apt-get install ruby ri rdoc mysql-server libmysql-ruby mysql-client
  • install rails
    • sudo gem install rails
  • sudo gem install mysql -- update db driver -- this fails for me.  :-\

Before I could build mongrel I needed to get build-essential

  • sudo apt-get install build-essential

Without this I think the install ignored the http11 errors and continued on. Then you get another http11 error when you try to run it. no such file to load -- http11 (LoadError)

then the install worked

  • sudo gem install mongrel

 

Linux non-root install

Most examples I see on the net install in a users home directory and place the files in ~/bin. This hides some of the details that I think are important and can make a mess out of your home directory.

Here is what I am going to install

  • /oap/rails is where I am installing everything
    • /oap/rails/apps is where I will place rails applications
    • /oap/rails/ruby is where I will place ruby
    • /oap/rails/gem is where I will install gem
    • /oap/rails/gems is where I will install my gems

Lets setup our environment variables ahead of time

  • export PATH=/oap/rails/gem/bin:/oap/rails/ruby/bin:/oap/rails/gems/bin:$PATH
  • export GEM_PATH=/oap/rails/gems
  • export GEM_HOME=/oap/rails/gems

1st item in the path is for the gem binary, 2nd is for ruby and 3rd is for installed gems

Lets get ruby

Check that your path is correct

  • which ruby
    • should return -> /oap/rails/ruby/bin/ruby

Ok now lets get rubygems

check install

  • which gem
    • should return /oap/rails/gem/bin/gem

Cleanup

  • cd /oap/rails
  • rm ruby-1.8.5.tar.gz rubygems-0.9.3.tgz

Install rails via GEM

  • gem install rails --remote -y

Lets install mongrel and json for fun

  • gem install mongrel --remote -y
  • gem install json --remote -y

MySql can be tricker. Safest if you point the installer to the correct location >gem install mysql --remote -y -- --with-mysql-dir=/you/mysql/dir

 

Setting up an application

  • rails depot -- creates depot project. db can be changed with --database=<db name>
  • cd depot
  • mysqladmin -u root create depot_development -- creates development db -- rails will find this db by name
  • rake db:migrate -- tests the db
  • generate model for products table
    • ruby script/generate model product
    • edit the db/migrate/001_create_products.rb file to add columns
  • rake db:migrate -- pulls in db changes -- rake db-migrate VERSION=0 will revert to original.
  • To see the current version look at the schema_info table that rake added.
    • mysql -u root depot_development
    • select * from schema_info;
  • Setup the controller
    • ruby script/generate controller admin -- if you want tab completion use backslash - script\generate
  • edit the app/controllers/admin_controller.rg to have
    • scaffold :product
    • This sets up dynamic scaffolding (framework for manipulating the model)
  • Start the webrick server and bring up the admin application

Debugging

In controllers you can inspect your parameters by raising an exception

    • raise params.inspect

tail -f logs/development.log

ruby script/about --- shows versions of everything

Adding a new Column

  • create a migration
    • ruby script/generate migration add_price
  • edit the 002_add_price.rb
    • add_column :products :price, :decimal, :precision => 8, :scale => 2, :default => 0
  • migrate it
    • rake db:migrate

 

Adding test data

Use a data only migration to load test data

  • ruby script/generate migration add_test_data
  • add data to the db/migrate/003_add_test_data.rb file
  • rake db:migrate
    • add VERSION=2 to revert
    • If any row fails validation it is not added and no error is displayed.

 

Adding validation to the Model

Validation is done in app/models/product.rb

examples

 validates_presence_of :title, :description, :image_url
 validates_numericality_of :price
 validates_uniqueness_of :title
 validates_format_of :image_url,
                     :with => %r{\.(gif|jpg|png)$}i,
                     :message => "gif, jpg or png"

 protected
 def validate
   errors.add(:price, "should be at least 0.01") if price.nil? || price < 0.01
 end

Creating a static scaffold

A static scaffold allows you to modify the view code

  • Usage: script/generate scaffold ModelName [ControllerName] [action, ...]
  • ruby script/generate scaffold product admin - product is model and admin is controller
    • if the controller name is omitted the plural form of the model is used as the controller
    • Action names can also be given with this command

Now the code is in

  • app/views/admin

ERb/rhtml notes

ERb (Embedded Ruby) processes ruby in rhtml files

  • <%= %> is interpreted by ruby and results are placed on page. Like php and jsp
  • <% %> is interpreted by ruby but no stdout. Like php and jsp
  • <% -%> prevents newlines from being left where the <% %> lines are
<% 10.times do -%>
<li>hello</li>
<% end -%>

h method is used to escape text containing html. <%= h("&, < and > can mess up your page") %>

Talk about poor method names.

For link portability it is recommended that link_to is used.

<%= link_to "dirt", :action => "dirt" %>

Different controllers can be specified.\

How much control do I have over the anchor parameters? the above call builds a minimalist link.

Helpers

Helpers are used view reuse. They live in app/helpers/*_helper.rb

app/views/something/ files use methods in app/helpers/something_helper.rb

Seems like the helpers should be in app/views/helpers but its not.  :-\

Active Record Notes

  • Rails uses ORM (Object Relational Mapping) ActiveRecord is the ORM layer. Note. ActionPack is the controller and the view
    • maps tables to classes
    • rows to objects
    • columns to object attributes
    • Table names are plural (but the model is singular)
    • All tables have auto incrementing id field
    • created_at and updated_at are managed by rails

New tables/models are created with

  • ruby script/generate model <model name>
  • Once created update the rb file and then run rake db:migrate to create the table
  • from this point on you need to create migrations to update the table layout. ex: ruby script/generate migration <migration name>

 

Joins

wip

Queries

Basic find

  • item = TableName.find(:first) -- returns 1st item
  • items = TableName.find(:all) -- returns all records in table
    • items.size => 3
  • item = TableName.find(3) -- finds a specific record
    • item.id => 2

advanced find

conditional

  • item = TableName.find(:first, :conditions => ['title =?', 'string to find'])
  • item = TableName.find(:first, :conditions => ['title =? AND time=?', 'string to find', 2.days.ago])
  • item = TableName.find(:all, :order => 'title DESC'. :limit => 10)

find_by_* -- these are autogenerated

  • item.find_by_title 'fred'
  • item.find_by_rating 4

calculations

  • TableName.count
  • TableName.average(:rating)

destory TableName.find(:first, :conditions => ['title =?', 'spam']).each do |p| p.destroy end )

 

Migration notes

Use ruby script/generate migration <migration name> to create migrations. Then edit the associated db/migrate/ file and run rake db:migrate
Migrations use the verb create when adding a table and uses add when adding columns to an existing table
Migrations can also be used to add data to tables.

Environments

Rails has development, test and production environments. To switch between them specify the environment on the console command

ruby script/console production -- loads production ruby script/console -- loads development (default)

This is changing rails environment with RAILS_ENV=development

console command loads interactive ruby (IRB)

View

Layouts and Partials

app/views/layouts

to wrap all views in a controller <controller name>.rhtml

can do application.rhtml to apply to all controllers (site look and feel)

Most specific one is used. articles.rhtml will win over application.rhtml
You can also add this to your controller

  • layout '<layout name>'
    • layout 'main'

in templates yield is called.. returns control to your page (yield your page) <%= yield %>
Partials are HTML snippets that are referenced in fields with

  • <%render :partial => 'form' %>
  • pulls in _form.rhtml

partial templates start with _

app/views/blog/_post.rhtml

<%= render :partial => "blog/post" %>
pass in object

<%= render :partial => "person/profile", :object => @person %>

  • passes person to app/views/person/_profile.rhtml

render collection of objects <%= render :partial => "person/profile", :collection => Person.find(:all) %>

locals can be set to hash <%= render :partial => "something", :locals => { :name => "joe", :profession => "barber" } %>

Filters

Filter are specified in the controller

  • simple
    • before_filter : authorize
    • after_filter: blah
  • except and only options
    • before_filter : authorize, :except => [:rss] -- ignore rss actions. authorize is called for all other actions
    • after_filter : runReport, :only => [:report, :audit] -- only run runReport on report and audit actions

around_filter runs before and after but is not commonly used

Put filter in application controller to impact all controllers (application.rb)

If filter is defined in application.rb it runs on all controllers. You can override the filter in individual controllers by overriding the method that the filter is calling.

 

Ruby Notes

Ruby has classes that have instances (objects). Functions can be added to objects (unlike other OO languages.

Ruby has constructors. The standard constructor is called new

  • object = ClassName.new

each object has instance variables and instance methods that are defined in the class.

ruby comments start with #

2 character indentation is the ruby standard (yuck)

compound statements do not use brackets. instead they use end.

non-compound statments flip order and skip end

errors.add(:price, "should be at least 0.01") if price.nil? || price < 0.01

instead of

if price.nil? || price < 0.01
 errors.add(:price, "should be at least 0.01") if price.nil? || price < 0.01
end

Class/Static methods are defined with self.function_name
Arrays

ages = [] # empty array ages << "27" # add to array
ruby convenience methods attr_reader, attr_writer, attr_accessor

Naming Conventions

Local Variables, method paramerers and method names should all start with a lowercase letter or an underscore

Instance variables should begin with an @

Underscores are prefered over camelcase. add_to_cart vs addToCart
Class Names, module names and constants start with upppercase letter. These use capitalization instead of underscores

ShoppingCart, Object, etc..
Symbols are are prefixed with colons. These are like string literals but more efficient.

action, :id

Methods

def hello (name)

 tmp = "hello " + name  #tmp local variable is created here.  Not need to define it
 return tmp

end

invoke

  • puts hello ("world")
  • puts (hello ("world"))

String processing

Strings created with single quotes are used literally When double quotes are used substitution sequences like \n are replaced with binary characters. Ruby also does expression interpolation on these strings. #{expression} is replaced by value of expression

def hello (name)

 "hello #{name}"

end

ruby returns the value of the last expression evaluated so return is optional.

 

Ruby

Ruby Objects

ruby is more OO than Java

object = Object.new object.object_id object.kind_of Object #=> true object.inspect object.something_dne -- gives run time error

animal = Object.new def animal.eat(food)

puts "yummmy"

end

dog = animal.dup -- copies animal and can then dog can override or add methods. clone

instantiating classes

old_dog = Dog.new -- new is a method on all classes. In this case Dog is a class

  • new creates object and calls initialize function. new is a class method (defined on Class object).

Ruby Namespaces

Modules are used for namespacing.

module Organizer  
   class File
   end
end

file = Organizer::File.new

module can also contain methods. make them class methods for ease of execution. (otherwise need Organizer.new.method_name)

module Organizer  
   class File
   end
   def self.create
     puts "hi"
   end
end

Ruby Syntax

return is not required

see ||= in Sessions section

Class Notes

questions

    • cache control and other headers
    • accessing web services

render partial from other view

<view name>/<partial name>

else just use <partial name>

this also works and makes more sense /<view name>/<partial name>
<% form_remote_for :comment,  :url => {:action => 'createD', :controller => 'articles'}, :update => 'foobar' do |f|%>

Author: <%= f.text_field 'author' %>
<%= f.text_area 'content', :rows => 5 %> <%= f.hidden_field 'article_id' %> <%= submit_tag 'Save' %>

<% end %>

 

rescue catches errors

   <%= h(comment.author.name rescue 'Anonymous') %> says:

 

Cleanup later

"".blank?

boolean functions often end with ?.  :-)
text_field_tag -- is general text_field -- active record specific

redirect_to :back -- sends to referer
Author.find_by_name("tony").update_attribute(:password, "haha");
Models are singular.. the table is plural. When u create a scaffold, controller, etc use the singular
Random install notes

  • Install ruby
  • Install gem -- gem is like rpm?
  • gem install rails -- not from ruby directory
  • gem install mysql -- update db driver
  • gem install mongrel -- installs mongrel.. makes default web server? ruby script/server or ruby script/server mongrel

mysql innodb is best for rails
using symbol objects is more efficient than using strings.  :comments is only created once.
ruby script/console -- loads ruby irb (interactive ruby)

  • 1+1
  • Article.find(:all)
  • a = Article.new
  • a
  • a.save
  • Article.count
  • Article.find(:all).size
  • a.title="test"
  • a.created_at
  • a.created_at.class

 

  • article = Article.find(:first)
    • #<Article:0x48ba110 @attributes={"title"=>"tseting 123", "id"=>"1", "content", "tset test ster sjdfhsk sfh sd", "author_id"=>nil, "created_at"=>"2007-02-20 1

0:52:21"}>

  • article.author
    • nil
  • author = Author.create(:name => "Tony")
    • #<Author:0x48ac448 @new_record=false, @new_record_before_save=true, @errors=# <ActiveRecord::Errors:0x48aaa44 @errors={}, @base=#<Author:0x48ac448 ...>>, @attributes={"name"=>"Tony", "id"=>2}>
  • article.author = author
    • #<Author:0x48ac448 @new_record=false, @new_record_before_save=true, @errors=# <ActiveRecord::Errors:0x48aaa44 @errors={}, @base=#<Author:0x48ac448 ...>>, @attributes={"name"=>"Tony", "id"=>2}>
  • article.author.name
    • "Tony"

a = Article.create(:title => "Another article", :content="sdfsdfs", :author="fred")

ruby script/generate model Author edit 003_create_authors.rb rake db:migrate ruby script/generate scaffold Author #Generates scaffolding for a specific Model

colors.methods - Object.methods -- removed base methods (colors.methods - Object.methods).grep /map/ -- look for map functions colors.map{|color| color.reverse} x = colors.map{|color| color.size=4}

 

methods ending with ! are typically destructive methods ending with ? usually return booleans. "".blank?

 

More Active Record

Callbacks

Observers are callbacks outside of the model

acts_as -- acts_as_list, acts_as_tree. these currently ship with rails but will move to plugin extends behavior of ActiveRecord models

validations allow checking to make save fails also used with article.valid? method

show errors on view with <%= error_messsages_for 'list' %>

 

with custom validations you fail it by adding to errors @errors.add "u messed up" irb... (1..2).class (1..2).to_a

Testing

Test order is undefined (actually they are run in alphabetical.. but that could change)

Each test is run in a transaction and the changes are rolled back in the DB before the next test. This allows tests to be run in any order and ensures they are independent. So if you have a test that deletes a record it will be available for the next test.

Unit tests (model tests)

tests a specific model

rake test:units
cool thing to add to test_helper.rb

check syntax def assert_not_valid (model)

 assert(!model.valid?)

end
Do tests in memory (not db) when possible to keep fast. Doesn't hit db unless .save . Fast tests mean you'r emore likely to run them.  :-)

   article = Article.new 
   assert(!article.valid?)
   article.content="foo"
   assert(!article.valid?)
   article.title="title foo"
   assert(!article.valid?)
   article.author_id=1
   assert(article.valid?)

Functional tests (Controller tests)

Unit tests for a single controller

assert_equal("joe", assigns(:person).name)

assigns only on Functional tests

Integration Tests

 

 

Routes

Is mod_rewrite for rails

YOU MUST RESTART SERVER WHEN CHANGING

Default behavior is

  • /lists/show/111
    • lists = controller
    • action = show
    • id = 111
  • config/routes.rb
    • map.connect ':controller/:action/:id'
controller, :id, etc become variables available to code

Pretty URLs are good for SEO.

  • map.connect 'products/:id', controller => 'catalog', :action => 'view'
  • map.connect , controller => 'welcome'
  • map.connect ':controller/:action/:id' this is default
    • /blog/show/123

note that any file in public will win over route. index.html, etc This is because cached files are put in public

mephisto does some custom routing (rich alston)
map.<route name> <pattern to match> <app location mapping>

defaults

  • map.blog_post 'blog/show/:id' controller => 'blog', :action => 'show' , :id=1
    • if blog/show or blog/show/ entered id is set to 1

splat parameters (splat = *)

map.tag_it 'tag/:id/*tags', :controller => 'posts', :action => 'tag'

params[:tags] contains the remainder of the URI
named routes used in URL generation too .... add

 

 

random

exception notifier emails

 

caches_page

in development file is created but rebuilt each time because development

enviroments/development.rb lest you change this

generated files are put in public.

cached files live forever so use is limited.
cache sweeper can clear items. after_save erch ActionController::Caching::Sweeping

better than observer because you have access to ??

 

CRUD and REST

Rails 2.0 is a big change

 

Don't Repeat yourself --- the pragmatic programmer

(Almost) every model gets a controller with pluralized name:
POST /profiles will do create action in profiles controller GET /profiles/123 will do show action in profiles controller PUT /profiles/1 (simulated with _method parameter on POST) will do update action in profiles controller DELETE /profiles/1 (simulated with _method parameter on POST) will do destroy action in profiles controller

map.resources :articles sets up mapping in 1.2 to use the RESTful principals

  • 1st line in routes.rb
  • to add non-rest actions
    • To allow hello_world on GET now you need to
    • map.resources :articles, :collection => {:hello_world => :get } -- something wrong

you can see the mappings in IRB via

  • puts ActionController::Routing:Routes.routes

scaffold_resource generates new 2.0 type scaffolding with the resources map

new command lets you specify model in command line. no more need to generate model separately.

now adding new actions won't work .. need to explicitly set allowed methods for actions. In map.resources command. see above

If request is in XML then rails will treat it like params format

returning active record items as xml is easy with .to_xml

  • puts Profile.find(:all).to_xml

Has someone written a to_json extension?

respond_to lets code perform different actions depending on format parameter.

 

ActionMailer

Usage: script/generate mailer MailerName [options]

./script/generate mailer Notifications signup forgot_password invoice

by default it is using smtp server on localhost

Plugins

Simple way to extend or patch a Rails application or Rails itself

Monkey patching -- changing rails by overriding methods

ruby script/plugin list ruby script/plugin sources

This page is scraped by plugin script. messy http://wiki.rubyonrails.com/rails/pages/Plugins

ruby script/plugin discover runs through items on site
install exception_notification plugin

  • ruby script/plugin install exception_notification

remove exception_notification plugin

  • ruby script/plugin remove exception_notification

or just filesystem remove. but better to do plugin remove in case there are install/uninstall hooks
vendor/plugins

random

Handling null values using rescue <%= article.author.name rescue 'anonymous'%>

changing the log level environment.rb config.log_level = :debug

to save query

Article.find(1, :include => :author}.author Article.find(1, :include => [:author, :comments]} -- gets author and comments. can be heavy
specific query Article.find(1, :joins => 'LEFT OUTER JOIN commens ON comments.article_id = articles.id')

Article.find(1, :select => 'articles.*, comments.content', :joins => 'LEFT OUTER JOIN commens ON comments.article_id = articles.id')
just sql.. Article.find_by_sql("select * from articles").first.title

 

keep custom SQL out of controller. make an method that does the SQL and calls it from the controller

paginator is being removed so don't use

 

Sessions

sessions are in app/tmp unless say we want in db via

  • rake db:sessions:create special rake task creates migration for us
  • rake db:migrate
  • In environment.rb set config.action_controller.session_store = :active_record_store

Example from book. shows unless block and teh fact that a return statement is not needed in ruby

 def find_cart
   unless session[:cart]
     session[:cart]= Cart.new
   end
   session[:cart] # returns the cart
 end

A simpler way is

 def find_cart
   session[:cart] ||= Cart.new
 end

confusing eh?

Deployment

apache 2 (w/mod_proxy_balance) -- mongrel/rails -- db server

 

db fields do not expire themselves. use cron?

rails is single threaded so no thread safe stuff in rails.

i think should have a filter that purges after every X requests.

Capistrano deployment system

sudo gem install capistrano termios terminos has posix terminal support

run with cap -A . use dot if in root of app

edit config/deploy.rb

cap setup sets up dir structure on remote machines

cap deploy starts it up

cap rollback rolls back to last version. all deployments have a timestamp in directory name

debugging

raise @article.inspect
breakpoint -- put method in code. hangs code until you jump in with breakpointer

ruby script/breakpointer

puts in irb prompt self.controller_name self.action_name @articles.first.title

can set vars..

exit causes rails app to resume (to next breakpoint) exit! tells to leave now.. ignore rest

this is broken in 1.8.5

ruby -c test.rb -- checks syntax on test.rb

scratch

http://localhost:3001/visitors/visit?KEY=null