Upload
jan-berkel
View
1.130
Download
0
Embed Size (px)
Citation preview
techmosis @last.fm, feb 2009
Ruby on Rails
... some fundamentals
“one stack” approach[M+V+C]
“thou shalt not repeat thyself”
(it’s a religion)
convention overconfiguration(table names)
brought established softwareengineering principles
into “scripting” environments
model: ActiveRecord
class Foo < ActiveRecord::Base belongs_to :bar # insert rails magic hereend
# standard queryFoo.find(:all, :conditions=>["name=?", "kittens"])
# provided by ARFoo.find_by_name("kittens")
# avoid N+1Foo.find(:all, :include=>:bar)
# customFoo.find_by_sql("select foos.* from foos where....")
view: erb or haml?
erb: php style <%= foo %>
haml: nested structure
!!!%html{ :xmlns => "http://www.w3.org/1999/xhtml", "xml:lang" => "en" } %head %meta{ "http-equiv" => "content-type", :content => "text/html; charset=utf-8" }/ %title= page_title = render :partial => "shared/icon" = stylesheet_link_tag 'screen', :media => 'screen' /[if IE] %style{:type=>"text/css"} * html { overflow-y: scroll; overflow-x: hidden; } = javascript_include_tag 'prototype' = javascript_include_tag 'lowpro' = javascript_include_tag 'velcro'
= controller_assets /[if IE] = stylesheet_link_tag 'ie', :media => 'screen' %body %div#page %div#content = @content_for_layout %div#footer = render :partial => "shared/footer"
testing: rspec{model, view, controller}
BDD(behaviour
driven development)
# model specdescribe "generating api key" do it "should create an api key entry in the db" do lambda { @howard.generate_api_key! }.should change(ApiKey, :count).by(1) endend
# controller specdescribe "an admin" do describe "with a valid API key" do before do @howard.generate_api_key! end it "should be able to fetch the latest graph" do get :latest, :params=>{:api_key => @howard.api_key}, :response=>200 end it "should be able to fetch meta data" do #... end endend
deployment
historically very painful(mod_fcgi, mongrel+
mod_proxy_balancer,mod_rewrite...)
too many options...
complicated to set up.
<VirtualHost *:80> ServerName <%= servername>> DocumentRoot "/rails_root/public"
<Directory "/rails_root/public"> Options FollowSymLinks AllowOverride None Order allow,deny Allow from all Header append Cache-Control "private, must-revalidate, max-age=0" </Directory>
<Proxy balancer://<%= clustername %>> <% startport.upto(startport + servers - 1) do |p| -%> BalancerMember http://127.0.0.1:<%= p %> <% end -%> ProxySet lbmethod=bytraffic ProxySet maxattempts=5 </Proxy> ProxyTimeout 300
RewriteEngine On # Check for maintenance file and redirect all requests RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f RewriteCond %{SCRIPT_FILENAME} !maintenance.html RewriteRule ^.*$ /system/maintenance.html [L]
# Rewrite index to check for static RewriteRule ^/$ /index.html [QSA]
# Rewrite to check for Rails cached page RewriteRule ^([^.]+)$ $1.html [QSA]
# Redirect all non-static requests to cluster RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f RewriteRule ^/(.*)$ balancer://<%= clustername %>%{REQUEST_URI} [P,QSA,L]</VirtualHost>
btw: don’t use mongrel
(it leaks memory)
a new standard emerges:rack
not tied to rails, can be used with many ruby
based frameworks
app.call(env)
call a method...
[200, 'Content-Type'=>'text/plain', 'Hello World']
which returns an array with 3 elements...
for apache2 there is...
mod_rails / mod_rack(phusion passenger)
it’s quite simple...
LoadModule passenger_module /usr/lib/passenger/mod_passenger.soPassengerRoot /usrPassengerRuby /usr/bin/ruby1.8
<VirtualHost *:80> ServerName myserver.com DocumentRoot "/rails_root/public"</VirtualHost>
but...
how to get the code deployed?
capistrano
what cap does for you:
• remote code checkout via VCS
• shell execution on remote servers (in parallel)
• rollback to last release (symlink)
• extensible (not just for rails!)
set :repository, "http://myrepo.com/myapp/trunk/"
role :web, "www.example.com"role :db, "db1.example.com", :primary => true
desc "reloads apache config"task :reload , :roles=>:web do sudo "/etc/init.d/httpd reload", :pty => trueend
monitoring apps: god
(“like monit, but awesome”)
start_port = 3000count = 5
(start_port...start_port+count).each do |port| God.watch do |w| w.start = "sh -c 'mongrel_rails start -p #{port} -c #{RAILS_ROOT}" w.interval = 5.seconds w.grace = 5.seconds
# restart if memory or cpu is too high w.restart_if do |restart| restart.condition(:memory_usage) do |c| c.interval = 20 c.above = 500 * 1024 # max_mem MB c.times = [3, 5] end end w.notify = “[email protected]” endend
exception_notifier
console
scaling rails
processes vs threads
def dispatch if ActionController::Base.allow_concurrency dispatch_unlocked else @@guard.synchronize do dispatch_unlocked end end end
new in rails 2.2: multithreading!
(finally...)
new: connection pooling(actually required formultithreaded apps)
what’s good / interesting?
active community (some call it “ghetto”)
some really good plugins(acts_as_*)
quick development +testing
ActiveRecord scopes
Post.with_scope(:find => {:conditions => ["user_id = ?", current_user.id]} do @user_posts = Post.find(:all)end
easy RESTful development
map.resources :tasks, :member => { :run => :post }, :collection => { :run_all => :post }
# tasks => GET /tasks# new_task => PUT /tasks/new# update_task(@task) => POST /tasks/1/update# run_task(@task) => POST /tasks/1/run# run_all_tasks => POST /tasks/run_all
class TasksController < ApplicationController def index @task = Task.find(:all) end def update @task = Task.find(params[:id]) @task.update_attributes!(params[:task]) end def run @task = Task.find(params[:id]) @task.run! endend
ActiveResource
class Task < ActiveResource::Base self.site = "http://api.example.com"end
@task = Task.new(:when=>Time.now)@task.save # POST http://api.example.com/tasks/[email protected] # => 1
@task = Task.find(1) # GET http://api.example.com/tasks/1.xml
creating feeds
class TasksController < ApplicationController def index @tasks = Task.find(:all) respond_to do |format| format.html format.atom end endend
# from tasks/index.atom.builderatom_feed(:url => formatted_tasks_url(:atom)) do |feed| feed.title("Tasks") feed.updated(Time.now.utc) for task in @tasks feed.entry(task) do |entry| entry.title(task.name) entry.content(task.content) end endend
what’s bad?
too much magic(method_missing, really...)
monkey patching / metaprogramming
(too much !)
module ActiveSupport #:nodoc: module CoreExtensions #:nodoc: module Array #:nodoc: # Makes it easier to access parts of an array. module Access # Equal to <tt>self[1]</tt>. def second self[1] end
# Equal to <tt>self[2]</tt>. def third self[2] end
# Equal to <tt>self[3]</tt>. def fourth self[3] end #.... #WTF? # Equal to <tt>self[41]</tt>. Also known as accessing "the reddit". def forty_two self[41] end end endend
monolithic architecture
“If you’re not doing it the ‘Rails way’...
...ur doing it wrong!”
DHH
native extensions =deployment headaches
alternatively...
deploy your app on the JVM: JRuby on Rails
you need: ActiveRecord-JDBC(more options)
integrate with java libraries
deploy on: jetty, glassfish, ...
it’s faster!(about twice as fast)
future
rails3 aka merb
merb?(mongrel+erb)
“clean-room” implementationof rails
modular design, choice of frameworks
(no “merb way”)
merb-slices(apps in django?)
current trends
micro-frameworks: sinatra