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
- cd /oap/rails
- wget ftp://ftp.ruby-lang.org/pub/ruby/ruby-1.8.5.tar.gz
- tar -zxvf ruby-1.8.5.tar.gz
- cd ruby-1.8.5
- ./configure –prefix=/oap/rails/ruby
- make
- make install
Check that your path is correct
- which ruby
- should return -> /oap/rails/ruby/bin/ruby
Ok now lets get rubygems
- cd /oap/rails
- wget http://rubyforge.org/frs/download.php/20585/rubygems-0.9.3.tgz
- tar -zxvf rubygems-0.9.3.tgz
- cd rubygems-0.9.3
- ruby setup.rb config --prefix=/oap/rails/gem
- ruby setup.rb setup
- ruby setup.rb install
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
- ruby script/server webrick
- http://localhost:3000/admin
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