ruby on-rails-101-presentation-slides-for-a-five-day-introductory-course-119407436077654-3

Download Ruby on-rails-101-presentation-slides-for-a-five-day-introductory-course-119407436077654-3

If you can't read please download the document

Upload: nilesh-panchal

Post on 16-May-2015

4.544 views

Category:

Documents


1 download

TRANSCRIPT

  • 1. Ruby on Rails 101 Presentation slides for a ve day introductory course by Peter MarklundNOTE: These slides have been updated for Rails 2.3 and areavailable at http://marklunds.com/rails101 1

2. This work is licensed under a Creative Commons Attribution 3.0 United States License (http://creativecommons.org/licenses/by/3.0/us).The material was developed by Peter Marklund for a ve day Ruby on Rails course in June 2007in Sweden. Please direct any feedback to the author at [email protected] or via http://marklunds.com.NOTE: An updated version of these slides for Rails 2.3 is available at http://marklunds.com/rails1012 3. AgendaMondayTuesday Wednesday Thursday FridayInstallation Migrations ActiveRecordRouting ExercisesPhilosophy ActiveRecord,Associations,RESTWorking onand MVC ActionController, Validations, and ActionMaileryour appStart an app and ActionView CallbacksBasicsActionViewPluginsFiles,FormsActiveSupportgenerators, andFilters Railsscripts 2.0CachingRuby TestingAJAXDeployment, Exercises Security, and Working on Performance your app3 4. railsintroduction 4 5. Kung-Fu?Ruby on Rails is astounding. Using it is like watching a kung-fu movie,where a dozen bad-ass frameworks prepare to beat up the little newcomeronly to be handed their asses in a variety of imaginative ways.-Nathan Torkington, OReilly Program Chair for OSCON 5 6. Installation InstantRails - One-Click install on Windows Locomotive - One-Click install on OS X Better options for Mac: MacPorts and installing fromsource On Linux: install from source Editors: TextMate (OS X), jEdit, SciTE, RadRails,Aptana, NetBeans, vim, Emacs 6 7. Rails background Jason Fried + David Heinemeier Hanson =>BaseCamp, Ruby on Rails Java, PHP => Ruby Hottest Hacker on the Earch, Best Hacker of theyear 2005, Jolt Awards Getting Real - Less is More Dave Thomas and the Pragmatic Programmers7 8. Rails Elevator Pitch Ruby on Rails is an open-source web framework thats optimized for programmer happiness andsustainable productivity. It lets you write beautifulcode by favoring convention over conguration. 8 9. Rails Philosophy andStrengths Ruby - less and more readable code, shorter development times, simple butpowerful, no compilation cycle Convention over conguration => almost no cong les, predened directorystructure, naming conventions => less code, easier maintenance Best practices: MVC, DRY, Testing Almost everything in Rails is Ruby code (SQL and JavaScript are abstracted) Integrated AJAX support. Web services with REST. Good community, tools, and documentation Extracted from a real application9 10. MVC Request Cycle10 11. MVC Request Cycle1. A request is made from the browser to the URL http://localhost:3000/users/show/1.2. A Rails server running locally on port 3000 receives the request and thedispatcher is invoked. Rails tries to nd a route (in the le cong/routes.rb) tomatch the URI /users/show/1. The default route :controller/:action/:idmatches the URI and the dispatcher instantiates the users controller andinvokes its show method with the :id parameter set to 1.3. The show method in the users controller fetches the user model object fromthe database with id equal to 1.4. The show method renders its view (the le show.rhtml) which generates anHTML page with the user info from the user model object.5. Rails sends the generated HTML from the view back to the browser.11 12. building a Demo Application Installing plugins with script/plugin install Code generation with script/generate model|scaffold Migrations The MVC request cycle Rails command line - script/console Testing with rake Routes - :controller/:action/:id12 13. Directory Structure appcontrollershelpersmodelsviews layouts congenvironment.rbroutes.rb dbdatabase.ymlmigrations lib log public script test vendor plugins rails 13 14. Rake Rake lets you dene a dependency tree of tasks to beexecuted. Rake tasks are loaded from the le Rakele Rails rake tasks are under railties/lib/tasks Put your custom tasks under lib/tasks 14 15. Useful Rake Tasks db:migrate db:sessions:create doc:app doc:rails log:clear rails:freeze:gems rails:freeze:edge rails:update :test (default task) :stats 15 16. script/* about breakpointer console generate plugin runner server script/igoruby/clear_sessions16 17. Environments Every environment has a database connection incong/database.yml and a conguration le undercong/environments. Its easy to add custom environments, i.e. for astaging server Rails always runs in only one environment, dictatedby ENV[RAILS_ENV] (same as RAILS_ENV)17 18. cong/environment.rb log level how sessions are stored schema format pluralization rules Load custom libraries that you need here 18 19. Debugging raise @object.inspect raise User.instance_methods(false).sort.join(", ") script/console The error log script/breakpointer 19 20. Ruby 20 21. I always thought Smalltalk would beat Java. I justdidnt know it would be called Ruby when it did- Kent Beck21 22. The Ruby Language Generic, interpreted, reective, with garbagecollection Optimized for people rather than computers More powerful than Perl, more object oriented thanPython Everything is an object. There are no primitive types. Strong dynamic typing 22 23. Everything in Ruby is: Assignment - binding names to objects Control structures - if/else, while, case Sending messages to objects - methods23 24. Ruby is Line Oriented Statements are separated by line breaks You can put several statements on one line if youseparate them by semicolon For long statements you can escape the line breakwith backslash After an operator, comma, or the dot in a methodinvocation you can have a line break and Ruby willknow that the statement continues on the next line You can have line breaks in strings 24 25. Dening a class andInstantiating an Objectclass Person # Constructor - invoked by Person.newdef initialize(name) # Instance variables start with @ @name = nameenddef say_hi # Instance methodputs "#{@name} says hi"endendandreas = Person.new(Andreas)andreas.say_hi 25 26. Class Inheritance# Programmer is a Person and extends it# with additional characteristicsclass Programmer < Person def initialize(name, favorite_ide) super(name)@favorite_ide = favorite_ide end # We are overriding say_hi in Persondef say_hisuperputs "Favorite IDE is #{@favorite_ide}endendpeter = Programmer.new(Peter, TextMate)peter.say_hi26 27. Getter- and Settter Methodsclass Persondef initialize(name)self.name = nameenddef name@nameenddef name=(name)@name = nameendendperson = Person.new("Andreas")puts person.nameperson.name = "David"puts person.name 27 28. attr_accessorclass Personattr_accessor :namedef initialize(name)self.name = nameendendperson = Person.new("Andreas")puts person.nameperson.name = "David"puts person.name 28 29. Variable/MethodAmbiguity Gotchaclass Personattr_writer :paiddef initialize@paid = falseenddef paid?@paidenddef make_payment... code that makes payment ...paid = true # Indicate payment is doneendendperson = Person.newperson.make_paymentputs person.paid? 29 30. Methods When invoking a method argument parenthesis are optional Methods always have a receiver. The implicit receiver is self. Methods are identied by their name only. No overloading onargument signatures. There are class methods and instance methods Methods can be public, protected, or private The last evaluated expression in a method is the return value Arguments can have default values: def my_method(a, b = {}) 30 31. Dening Class Methodsclass Persondef self.class_methodputs class method invokedendclass Fixnum 2.class.methods - Object.methods andreas.capitalize 39 40. Constants Constants dened in a class/module are availablewithin that class/module and outside the class withthe scope operator :: Constants dened outside any class/module can beaccessed anywhere Constants cannot be dened in methods40 41. Introspectionandreas = Person.new(Andreas)andreas.inspectandreas.class # => Personandreas.class.superclass # => Objectandreas.class.superclass.superclass # => nilandreas.ancestors # lists ModulesPerson.instance_methods(false)puts Kernel.methods.join(n) 41 42. Arithmetic andConversions2.class == FixnumFixnum.superclass == IntegerInteger.superclass == Numeric3.0.class == FloatFloat.superclass == Numeric2/3 == 02/3.0 # => 0.66666672 + 3.0 == 5.02.to_i + 3.0.to_f == 5.010000000000.class == BignumBignum.superclass == Integer2 + 3 # => TypeError: String cant be coerced into Fixnum42 43. String classruby.upcase + + rails.capitalizetime is: #{Time.now}n second lineno interpolation here #{Time.now}I /bin/bash}ENV.each {|k, v| puts #{k}=#{v} }45 46. Symbols# Symbols start with a colon:action.class == Symbol:action.to_s == action:action == action.to_sym# There is only one instance of every symbol:action.equal?(:action) # => trueaction.equal?(action) # => false# Symbols are typically used as keys in hasheslink_to Home, :controller => home 46 47. More About methods Arbitrary number of arguments: def my_methods(*args) Converting Array to arguments: my_method([a, b]*) Dynamic method invocation: object.send(:method_name) Duck typing: object.respond_to?(:method_name) If the last argument is a Hash, the braces can be omitted:link_to Home, :controller => home 47 48. Range Class# Two dots is inclusive, i.e. 1 to 5(1..5).each { |x| puts x**2 }# Three dots excludes the last item,# i.e. 1 to 4(1...5).each { |x| puts x }(1..3).to_a == [1, 2, 3] 48 49. StructsRating = Struct.new(:name, :ratings)rating = Rating.new("Rails", [ 10, 10, 9.5, 10 ])puts rating.nameputs rating.ratings49 50. if, unless and the ?Operatormessage = if count > 10Try againelsif tries == 3You loseelseEnter commandendraise Unauthorized if !current_user.admin?raise Unauthorized unless current_user.admin?status = input > 10 ? Number too big : ok50 51. Iterators: while, until,and for. Keywords: breakand nextwhile count < 100 puts count count += 1end# Statement modifier version of whilepayment.make_request while (payment.failure? and payment.tries < 3)for user in @usersnext if user.admin?if user.paid? puts user break endenduntil count > 5 puts count count += 1end# Statement modifier version of untilputs(count += 1) until count > 551 52. casecase xwhen 0when 1, 2..5puts "Second branch"when 6..10puts "Third branch"when *[11, 12]puts Fourth branchwhen String: puts Fifth branchwhen /d+.d+/puts Sixth branchwhen x.downcase == peterputs Seventh branchelseputs "Eight branch"end 52 53. blocks, closures, and proc objectsdef invoke_blockputs "before block"yield 5puts "after block"endname = "Ruby"invoke_block { |n| puts "In block with #{name}, received #{n}"}my_proc = Proc.new { |n| puts "In proc, received #{n}"}my_proc.call 2invoke_block &my_proc53 54. blocks - usageexamples# Iteration[1, 2, 3].each {|item| puts item }# Resource Managementfile_contents = open(file_name) { |f| f.read }# Callbackswidget.on_button_press doputs Got button pressend# Convention: one-line blocks use {...} and multiline# blocks use do...end54 55. common stringoperations .blank? == truemy_string.each_with_index { |line, i| puts #{i}: #{line} }abc.scan(/./).each { |char| puts char }we split words.split.join(, )strip space .stripsprintf(value of %s is %.2f, PI, 3.1416)I Go Ruby[2, 2] == I Go Ruby[2..3] == Go 55 56. Using the dup Method on Method Arguments# Methods that change their receiver end with an exclamation mark by convention.# If you need to invoke an exclamation mark method on a method argument and you want# to avoid the object from being changed, you can duplicate the object first# with the Object#dup method. Core classes such as String, Hash, and Array all have# meaningful implementations of the dup method. Here is an example from Rails:class ActiveRecord::Base...def attributes=(new_attributes)return if new_attributes.nil?attributes = new_attributes.dup # duplicate argument to avoid changing itattributes.stringify_keys! # modify the duplicated objectmulti_parameter_attributes = []remove_attributes_protected_from_mass_assignment(attributes).each do |k, v|k.include?("(") ? multi_parameter_attributes excSTDERR.puts "Error loading #{file_name}: #{exc.message}"raiseend58 59. invoking externalprogramssystem(ls -l)puts $?.exitstatus if !$?.success?puts `ls -l` 59 60. Ruby Scripts With RDoc and Option Parsing#!/usr/bin/env ruby# == Synopsis# This script takes photographs living locally on my desktop or laptop# and publishes them to my homepage at http://marklunds.com.## == Usage## Copy config file publish-photos.yml.template to publish-photos.yml# and edit as appropriate.## ruby publish-photos [ -h | --help ] ... # Load the Rails environmentrequire File.dirname(__FILE__) + /../config/environmentrequire optparserequire rdoc/usageopts = OptionParser.newopts.on("-h", "--help") { RDoc::usage(usage) }opts.on("-q", "--quiet") { Log::Logger.verbose = false }opts.parse!(ARGV) rescue RDoc::usage(usage)Photos::Publisher(ARGV) 60 61. Ruby on the CommandLine# Query and replaceruby -pi.bak -e "gsub(/Perl/, Ruby)" *.txt# Grepruby -n -e "print if /Ruby/" *.txtruby -e "puts ARGF.grep(/Ruby/)" *.txt 61 62. open class denitions and Method Aliasingclass Peterdef say_hiputs "Hi"endendclass Peteralias_method :say_hi_orig, :say_hidef say_hiputs "Before say hi"say_hi_origputs "After say hi"endend 62 63. Core Classes are AlsoOpenclass Integerdef even?(self % 2) == 0endendp (1..10).select { |n| n.even? }# => [2, 4, 6, 8, 10] 63 64. method_missing: A VCRclass VCRdef initialize@messages = []end def method_missing(method, *args, &block) @messages 167 68. instance_evalandreas = Person.new(Andreas)name = andreas.instance_eval { @name } 68 69. class_eval/ module_evalclass Persondef add_method(method)class_eval %Q{def #{method}puts "method #{method} invoked"end}endadd_method(:say_hi)endperson = Person.new.say_hi69 70. dene_methodclass Array{:second => 1, :third => 2}.each do |method,element|define_method(method) doself[element]endendendarray = %w(A B C)puts array.firstputs array.secondputs array.third 70 71. Object SpaceObjectSpace.each_object(Numeric) { |x| p x } 71 72. Class Reection# Using Class#superclassklass = Fixnumbeginpring klassklass = klass.superclassprint < if klassend while klass# => Fixnum < Integer < Numeric < Object# Using Class#ancestorsp Fixnum.ancestors# => Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel# Inspecting methods and variablesFixnum.public_instance_methods(false)Fixnum.class_variablesFixnum.constants1.instance_variables 72 73. System Hooks: Class#inheritedclass ActiveRecord::Base# Invoked when a new class is created that extends this# classdef self.inherited(child)@@subclasses[self] ||= []@@subclasses[self] true do |t| t.column :login,:string t.column :email, :string t.column :crypted_password, :string, :limit => 40 t.column :salt,:string, :limit => 40 t.column :created_at, :datetime t.column :updated_at,:datetime t.column :remember_token,:string t.column :remember_token_expires_at, :datetimeend 80 81. Lets Bring Sexy Back # Note: this is only available in Edge Rails, *not* # in Rails 1.2.3 create_table "users", :force => true do |t|t.string :login, :email, :remember_tokent.string :salt, :crypted_password, :limit => 40t.timestampst.datetime :remember_token_expires_at end 81 82. ActiveRecord Basics 82 83. Fundamentals One database table maps to one Ruby class Ruby classes live under app/models and extendActiveRecord::Base Table names are plural and class names are singular Database columns map to attributes, i.e. get and setmethods, in the model class All tables have an integer primary key called id Database tables are created with migrations 83 84. Overriding Namingconventions self.table_name = my_legacy_table self.primary_key = my_id self.pluralize_table_names = false self.table_name_prex = my_app84 85. CRUD Create: create, new Read: nd, nd_by_ Update: save, update_attributes Delete: destroy85 86. Create = new + saveuser = User.new user = User.new(user.rst_name = Dave :rst_name => Dave,user.last_name = Thomas :last_name => Thomasuser.new_record? # true )user.save user.saveuser.new_record? # falseuser = User.create(:rst_name => Dave,:last_name => Thomas)user.new_record? # false 86 87. Save!user = User.new(:rst_name => Dave,:last_name => Thomas)if user.savebegin# All is ok user.save!elserescue RecordInvalid => e# Could not save user :-(# Could not save!end end87 88. create! = new + save!beginuser = User.create!(:rst_name => Dave,:last_name => Thomas)rescue RecordInvalid => e # Could not create user...end88 89. Column/Attribute Data TypesMySQL Ruby Class integer Fixnum clob, blob, text Stringoat, doubleFloat char, varcharString datetime, time Time 89 90. Custom Attribute Accessorsclass Song < ActiveRecord::Base def length=(minutes) # self[:length] = minutes*60 write_attribute(:length, minutes * 60) end def length# self[:length] / 60read_attribute(:length) / 60 endend90 91. Default Attribute Values class User < ActiveRecord::Base def languageself[:language] ||= sv end end 91 92. Boolean Attributes Everything except nil and false is true in Ruby However, in MySQL boolean columns are char(1)with values 0 or 1, both of which are true in Ruby. Instead of saying user.admin, say user.admin? When you add the question mark, false is the number0, one of the strings 0, f , false, or , or theconstant false92 93. nd User.nd(:rst) # => User object User.nd(:all) # => Array with all User objects User.nd(3) # => User object with id 393 94. nd with :conditionsUser.nd(:all, :conditions =>[rst_name = ? and created_at > ?, David, 1.year.ago])User.nd(:all, :conditions =>[rst_name = :rst_name, last_name = :last_name,{:rst_name => David, :last_name => Letterman}])User.nd(:all, :conditions => {:rst_name => Jamis, :last_name => Buck})User.nd(:all, :conditions => [ "category IN (?)", categories])94 95. Power Find with Additional Attributesusers = User.nd(:all,:conditions => [users.id = taggings.taggable_id and users.age > ?, 25],:limit => 10,:offset => 5,:order => users.last_name,:joins => , tagggings,:select => count(*) as count, users.last_name,:group => users.last_name)puts users.rst.count # => 3puts users.rst.attributes # => {last_name => Svensson, count => 3}95 96. Everything is aFind :all# select * from users limit 1User.nd(:rst) User.nd(:all, :limit => 1).rst# select * from users where id = 1User.nd(1) User.nd(:all, :conditions => users.id = 1)96 97. Like Clauses# Like thisUser.nd(:all, :conditions => [name like ?, % + params[:name] + %)# Not like thisUser.nd(:all, :conditions => [name like %?%, params[:name]) 97 98. Dynamic FindersUser.nd_by_rst_name PeterUser.nd_all_by_last_name HansonUser.nd_by_age 20User.nd_by_last_name(Buck, :conditions => {:country = Sweden, :age => 20..30})User.nd_by_rst_name_and_last_name Andreas, Kviby 98 99. RecordNotFound ExceptionUser.exists?(999) # => falseUser.nd(999) # => raises ActiveRecord::RecordNotFoundUser.nd_by_id(999) # => nilUser.nd(:rst, :conditions => {:id => 999}) # => nil 99 100. Find or Create# No Summer tag existsTag.nd_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")# Now the Summer tag does existTag.nd_or_create_by_name("Summer") # equal to Tag.nd_by_name("Summer")# No Winter tag existswinter = Tag.nd_or_initialize_by_name("Winter")winter.new_record? # true 100 101. Updateorder = Order.nd(12)order.name = Bill Gatesorder.charge = 10000order.save!order = Order.nd(13)order.update_attributes!(:name => Steve Jobs,:charge => 1)Order.nd(12).update_attribute(:charge, -1) # => Does not trigger validation 101 102. update_attributes is Syntactic Sugardef update_attributes(attributes)self.attributes = attributessaveenddef update_attributes!(attributes)self.attributes = attributessave!enddef update_attribute_with_validation_skipping(name, value)send(name.to_s + =, value)save(false) # Passing false to save bypasses validationend 102 103. Locking# SELECT * FROM accounts WHERE (account.`id` = 1) FOR UPDATEaccount = Account.nd(id, :lock => true)account.status = disabledaccount.save!# Optimistic locking with integer column lock_version in the accounts table:account1 = Account.nd(4)account2 = Account.nd(4)account1.update_attributes(:status => disabled)account2.update_attributes(:status => enabled) # => Raises StaleObjectError103 104. Destroy# Instance method User#destroyUser.count # => 5u = User.nd(:rst)u.destroyUser.count # => 4# Class method User.destroyUser.destroy(2, 3)User.count # => 2User.exists?(2) # => falseUser.nd(:all).map(&:id) # => [4, 5]# Class method User.destroy_allUser.destroy_all(id >= 5)User.count # => 1User.destroy_allUser.count # => 0 104 105. Destroy ClassMethodsdef destroy(id) id.is_a?(Array) ? id.each { |id| destroy(id) } : nd(id).destroyenddef destroy_all(conditions = nil) nd(:all, :conditions => conditions).each { |object| object.destroy }end 105 106. Delete: Does not Instantiate Objects# Class method User.deleteUser.count # => 5# delete from users where id = 3User.delete 3User.count # => 4User.exists?(3) # => false# Class method User.delete_allUser.delete_all(:conditions => id >= 4)User.count # => 2# delete from usersUser.delete_allUser.count # => 0106 107. CalculationsPerson.minimum(age)Person.maximum(age)Person.sum(age)Person.count(:conditions => [age > ?, 25])Person.average(age)Person.calculate(:std, :age) 107 108. Executing SQL# Works like nd(:all) but takes a SQL string or conditions Array# Post.nd_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"def nd_by_sql(sql) connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }end ActiveRecord::Base.connection.execute(select * from users) AtiveRecord::Base.select_one|select_all|select_value|select_values 108 109. Virtual Attributes# Virtual attributes are atttributes that do not correspond# directly to database columns like normal ActiveRecord# attributes.class Person < ActiveRecord::Basedef full_namerst_name + + last_nameenddef full_name=(full_name)rst_name = full_name.split.rstlast_name = full_name.split.lastendend109 110. Serializing AttributeValuesclass Person < ActiveRecord::Baseserialize paramsendperson = Person.newperson.params = {:height => 190,:weight => 80,:eye_color => blue}person.save110 111. Composite Attributesclass Name attr_reader :rst, :initials, :last def initialize(rst, initials, last)@rst = rst@initials = initials@last = last end def to_s[ @rst, @initials, @last ].compact.join(" ") endendclass Customer < ActiveRecord::Base composed_of :name,:class_name => Name,:mapping =>[ #database ruby[ :rst_name, :rst ],[ :initials, :initials ],[ :last_name, :last ]end111 112. TransactionsAccount.transaction do account1.deposit(100) account2.withdraw(100)endAccount.transaction(account1, account2) do account1.deposit(100) account2.withdraw(100)end 112 113. Chad Fowler Says ActiveRecord is an example of a leaky abstraction and you need to understand theSQL that it generates to avoid gotchas such asthe N+1 problem. 113 114. ActionControllerBasics 114 115. Controllers Controllers are Ruby classes that live under app/controllers Controller classes extend ActionController::Base An action is a public method and/or a correspondingview template115 116. ControllerEnvironment cookies[:login] = { :value => peter, :expires => 1.hour.from_now headers[Content-Type] = application/pdf; charset=utf-8 params request: env, request_uri, get?, post?, xhr?, remote_ip response session logger.warn(Something pretty bad happened) 116 117. request.envSERVER_NAME = localhostPATH_INFO = /HTTP_ACCEPT_ENCODING = gzip,deateHTTP_USER_AGENT = Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3SCRIPT_NAME = /SERVER_PROTOCOL = HTTP/1.1HTTP_CACHE_CONTROL = no-cacheHTTP_ACCEPT_LANGUAGE = en-us,en;q=0.5HTTP_HOST = localhost:3000REMOTE_ADDR = 127.0.0.1SERVER_SOFTWARE = Mongrel 0.3.13.4HTTP_KEEP_ALIVE = 300REQUEST_PATH = /HTTP_COOKIE = volunteer_id=ff4cc4f37c77a4efbee41e9e77a5d3d4bb619d22;fcP=C=0&T=1176293486407&DTO=1176293486401&U=103359521886406&V=1176293592419;_session_id=819e71f41ab4e64a111358374c3b662fHTTP_ACCEPT_CHARSET = ISO-8859-1,utf-8;q=0.7,*;q=0.7HTTP_VERSION = HTTP/1.1REQUEST_URI = /SERVER_PORT = 3000GATEWAY_INTERFACE = CGI/1.2HTTP_PRAGMA = no-cacheHTTP_ACCEPT = text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5HTTP_CONNECTION = keep-alive 117 118. rendering a response A response is rendered with the render command An action can only render a response once Rails invokes render automatically if you dont Redirects are made with the redirect_to command You need to make sure you return from an actionafter an invocation of render or redirect_to118 119. Render examples render :text => Hello World render :action => some_other_action render :partial => top_menu render :xml => xml_string Options: :status, :layout, :content_type send_le(/les/some_le.pdf ) 119 120. Redirect Examples redirect_to :back redirect_to(/help/order_entry.html) redirect_to :controller => blog, :action => list 120 121. Cookies A hash stored by the browser cookies[:preference] = { :value => icecream, :expires => 10.days.from_now, :path => /store } Valid options: :domain, :expires, :path, :secure, :value 121 122. Sessions A hash stored on the server, typically in a databasetable or in the le system. Keyed by the cookie _session_id Avoid storing complex Ruby objects, instead put id:sin the session and keep data in the database, i.e. usesession[:user_id] rather than session[:user] 122 123. Conguring Sessions session :off, :only => %w{ fetch_rss fetch_atom } session :session_key =>_my_app_session_id, :session_domain => my.domain.com cong.action_controller.session_store= :active_record_store rake db:sessions:create 123 124. The Flash The ash is a way to set a text message to the user inone request and then display it in the next (typicallyafter a redirect) The ash is stored in the session ash[:notice], ash[:error] ash.now[:notice] = Welcome unless ash[:notice] ash.keep(:notice) 124 125. Best Practice Dont put SQL and too much code in yourcontrollers/views - its a code smell, andmaybe the most common design mistakeRails developers make. Actions should be 3-4lines that script business objects. The goal isfat models and skinny controllers. Always access data via the logged in userobject (i.e. current_user.visits.recent).125 126. ActionViewBasics 126 127. What is ActionView? ActionView is the module in the ActionPack librarythat deals with rendering a response to the client. The controller decides which template and/or partialand layout to use in the response Templates use helper methods to generate links,forms, and JavaScript, and to format text. 127 128. Where Templates Live Templates that belong to a certain controller typicallylive under app/view/controller_name, i.e. templatesfor Admin::UsersController would live under app/views/admin/users Templates shared across controllers are put underapp/views/shared. You can render them withrender :template => shared/my_template You can have templates shared across Railsapplications and render them with render :le =>path/to/template128 129. Template Environment Templates have access to the controller objects ash,headers, logger, params, request, response, andsession. Instance variables (i.e. @variable) in the controllerare available in templates The current controller is available as the attributecontroller.129 130. Three Types ofTemplates rxml - Files with Ruby code using the Builder libraryto generate XML. Typically used for RSS/Atom. rhtml - The most common type of template used forHTML. They are HTML les with embedded Rubyand they use the ERb library. rjs - Ruby code with a Rails specic API thatgenerate JavaScript. Used for AJAX functionality.130 131. Builder Template Example: RSSxml.instruct!xml.rss "version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/" doxml.channel doxml.title "Recent comments for #{@user.login}"xml.link @rss_urlxml.pubDate CGI.rfc1123_date(@comments.first ? @comments.first.updated_at :Time.now)xml.description ""@comments.each do |comment|xml.item doxml.title "Comment by #{comment.creation_user.login} #{time_ago_in_wordscomment.created_at} ago"xml.link @server_url + comment_url(comment)xml.description h(comment.body)xml.pubDate CGI.rfc1123_date(comment.updated_at)xml.guid @server_url + comment_url(comment)xml.author h(comment.creation_user.login)endendendend 131 132. rhtml Templates - Evaluates the Ruby codeand prints the last evaluated value to the page. - Evaluates Ruby codewithout outputting anything to the page. Use a minus sign (i.e. and )to avoid the newline after the tag to be printed to thepage. Remember to quote especially user inputted datawith the helper h: .132 133. Partials Partials are templates that render a part of a page,such as a header or footer, or a menu, or a listing ofarticles Partials help promote reuse of page elements Partials work just like page templates (views) and runin the same environment. They also live in the samedirectory as page templates. The lenames of partials always start with anunderscore. 133 134. Rendering Partials Render a partial from an action with render :partial=> name_of_partial Render a partial in a page template with the samecommand: name_of_partial %> 134 135. Passing Variables to Partials Controller instance variables are available in partials If you pass :object => @an_article to the render command thenthat variable will be available in a local variable in the partialwith the same name as the partial. If there is an instance variable with the same name as the partialthen it will be available as a local variable in the partial with thesame name, i.e. @article = Article.nd(1); render :partial =>article. You can pass any objects into local variables in the partial withthe :locals argument: render :partial => article, :locals =>{ :author => @author, :options => @options }135 136. Partials and Collections article, :object => article %> Can be written more concisely with the :colletions argument: article, :collection => @articles %>136 137. Layouts Layouts are templates under app/views/layouts thatcontain common page elements around pages such asheaders, footers, menus etc. The layout template contains the invocation which will render the action output. 137 138. Determining WhichLayout to Use If no layout is specied in the controller or render method thenRails looks for a controller layout at app/views/layouts/controller_name.rhtml and uses that. If there is no controller layout then Rails will use anyapplication layout at app/views/layouts/application.rhtml You can pass the :layout argument to the layout command:render some_template, :layout => my_special_layout. Youcan also turn off layouts by saying :layout => false. You can declare in your controller which layout should be usedby saying something like: layout standard, :except =>[:rss, :atom]. Turn off layouts by saying layout nil. 138 139. Dynamic LayoutSelectionclass BlogController < ActionController::Baselayout :determine_layoutprivatedef determine_layoutuser.admin? ? admin : standardendend139 140. Passing Data toLayouts You can pass data to layouts via instance variables You can also wrap parts of your template in ... invocations and then in your layout render that with 140 141. Helpers Helpers are Ruby modules with methods that areavailable in your templates. Helpers can avoid duplication and minimize theamount of code in your templates. By default each controller has a corresponding helperle at app/helpers/controller_name_helper.rb141 142. text_helper.rbtruncate("Once upon a time in a world far far away", 14)highlight(You searched for: rails, rails)excerpt(This is an example, an, 5)pluralize(2, person)word_wrap(Once upon a time, 4)textilize(text)markdown(text)simple_format(text)auto_link(text)strip_links(text)sanitize(html)strip_tags(html)"> 142 143. url_helper.rburl_for({:controller => blog}, :only_path => false)link_to "Other Site", "http://www.rubyonrails.org/", :confirm => "Sure?"link_to "Image", { :action => "view" },:popup => [new_window,height=300,width=600]link_to "Delete Image", { :action => "delete", :id => @image.id }, :method=> :deletebutton_to "New", :action => "new"link_to_unless_current("Home", { :action => "index" })mail_to "[email protected]", "My email", :encode => "javascript"# => mail_to "[email protected]", "My email", :encode => "hex"# => My email 143 144. Try a differentTemplating System: HAML#content .left.column %h2 Welcome to our site! %p= print_information .right.column= render :partial => "sidebar"

Welcome to our site!
"sidebar" %>

144 145. Testing145 146. Rails Testing Landscape Rails Ruby Tool Interface HTTP from Browser Selenium, Watir (IE, Firefox)WWW::MechanizeHTTPIntegration testsDispatcherFunctionalRSpec, test/spec ControllerUnitRSpec, test/specModel 146 147. Test::Unit:TestCase Test::Unit is a Ruby testing library, very similar to JUnit. Rails ships with three types of tests: unit, functional, andintegration. Those tests are all structured into test case classesthat extend the Test::Unit::TestCase class. Every method in the test case with a name that starts withtest_ represents a single test that gets executed by theframework. Before every test method the setup method is invoked, andafterwards the teardown method. Every test method makes one or more assertions about thebehaviour of the class under test 147 148. Unit Tests Every model MyModel in Rails has a correspondingunit test case in the class TestMyModel in test/units/test_my_model.rb Unit tests are created for you by script/generatemodel 148 149. Unit Test Examplerequire File.dirname(__FILE__) + /../test_helperclass UserTest < Test::Unit::TestCasefixtures :customers, :services, :users, :calls def test_should_create_user assert_difference User.count do # Requires Edge Rails user = create_user assert !user.new_record?, "#{user.errors.full_messages.to_sentence}" end endprotecteddef create_user(options = {})User.create({:name => "Quire",:email => [email protected],:password => quire,:password_confirmation => quire,:role => super}.merge(options))endend149 150. test_helper.rbENV["RAILS_ENV"] = "test"require File.expand_path(File.dirname(__FILE__) + "/../config/environment")require test_helpclass Test::Unit::TestCase# Transactional fixtures accelerate your tests by wrapping each test method# in a transaction thats rolled back on completionself.use_transactional_fixtures = true # Instantiated fixtures are slow, but give you @david where otherwise you # would need people(:david) self.use_instantiated_fixtures = false# Add more helper methods to be used by all tests here...end150 151. Test Data withFixtures Fixtures are les that load test data into the test database thattests can run against. Every model and database table has acorresponding xture le at test/xtures/table_name.yml Fixture les are in YAML format, a readable alternative toXML. You can also keep xture les in CSV format if you like. The xture command will delete from the specied tables andthen load their xture les. The xtures will then be available inyour tests as table_name(:xture_name), i.e. users(:joe). 151 152. Fixture Example:users.ymlquentin:id: 1login: quentinemail: [email protected]: 7e3041ebc2fc05a40c60028e2c4901a81035d3cdcrypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # testcreated_at: aaron:id: 2login: aaronemail: [email protected]: 7e3041ebc2fc05a40c60028e2c4901a81035d3cdcrypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # testbio: Aaron is a weird guycreated_at: 152 153. Assertions assert(actual, comment) # Asserts truth assert_equal(expected, actual, comment) assert_in_delta(expected_oat, actual_oat, delta,message) assert_match(pattern, string, message) assert_nil(object, message)/assert_not_nil assert_raise(Exception, ..., message) { block ... } assert_difference(expressions, difference = 1, &block) 153 154. Functional Testing of Controllers Functional tests run against a single controllerinstance, simulate requests against it, and makeassertions about the responses Requests are made via the methods get/post/put/delete and they end up invoking the process methodon the controller and executing an action. 154 155. Functional TestExamplerequire File.dirname(__FILE__) + /../test_helperrequire comments_controllerclass CommentsController; def rescue_action(e) raise e end; endclass CommentsControllerTest < Test::Unit::TestCasefixtures :users, :commentsdef setup@controller = CommentsController.new@request= ActionController::TestRequest.new@response = ActionController::[email protected][HTTP_HOST] = "localhost"@request.session[:user] = users(:aaron)enddef test_rssget :rss, :id => users(:quentin)assert_response :successassert_select "rss > channel" doassert_select "title", /Recent comments/assert_select "item", 1assert_select "item > title", Regexp.new(users(:aaron).login)assert_select "item > description", users(:quentin).comments.first.bodyendend155 156. Assertions in Functional Tests assert_response :success|:redirect|:missing|:error assert_redirected_to(:controller => blog, :action =>list) assert_template store/index assert_not_nil assigns(:items) assert session[:user] assert_not_nil ash[:notice] 156 157. assert_selectassert_select p.warning #

...

assert_select p#warning #

...

assert_select html p.warning # Ancestor chainingassert_select html > body > p.warning # Direct parent chainingassert_select div#cart table tr, 3 # Integer, n timesassert_select div#cart table tr, 3..5 # Range, n timesassert_select div#cart table tr, false # Not present on pageassert_select div#cart table tr td#price, $23 # Tag contentsassert_select div#cart table tr td#price, /23/ # Regexpassert_select "form input" doassert_select "[name=?]", /.+/# Not emptyend 157 158. Integration Tests Test against the Rails dispatcher and can span allcontrollers Simulate user scenarios/stories. Can involve multiple simultaneous sessions You make requests with the methods get/post etc. You have access to pretty much the sameenvironment and assertions as in functional tests 158 159. Integration TestExampleclass TracerBulletTest < ActionController::IntegrationTestdef test_tracer_bulletget("/mcm/user/login")assert_response :success post("/mcm/user/login", :email => self.mail, :password => self.password) assert_redirected_to :controller => mcm/general follow_redirect! assert_response :success expect_count = contacts(:adam_sandler).jobs.size post("/mcm/contacts/search", :q => sandler new york) assert_response :success assert_n_search_results(expect_count)get "/mcm/lists/show/#{list.id}"assert_response :successassert_template mcm/lists/showendend159 160. Integration TestExample: With DSLclass TracerBulletTest < ActionController::IntegrationTestdef test_tracer_bulletsetup_test_userscarl = new_session_as(:carl)carl.logs_incarl.searches_for_contacts...endmodule TestingDSLattr_accessor :mail, :passworddef logs_inget("/mcm/user/login")assert_response :successpost("/mcm/user/login", :email => self.mail, :password => self.password)assert_redirected_to :controller => mcm/generalfollow_redirect!assert_response :successend...enddef new_session_as(person)open_session do |sess|sess.extend(TestingDSL)endend 160 161. Running Tests rake - runs all tests rake test:units rake test:functionals rake test:integration ruby test/unit/user_test.rb161 162. Stubbing and Mocking Sometimes you may need to stub out interactions with externalsystems (payment gateways etc.) and isolate the code under test. Mock and stub objects are similar, but mock objects tend to bemore intelligent and verify that the right messages are received. Mock classes that should be in place for all tests (static mocks)can be put under test/mocks/test. You may use the libraries Mocha and Stubba or FlexMock fordynamic stubbing/mocking. The stubs/mocks that you set upare isolated to the test.162 163. Mocha and StubbaExamplesclient = Goyada::HttpClient.new({})client.expects(:http_timeout).returns(0.01)client.expects(:get_connection).returns(lambda { sleep 10 })response = client.send(:https_response, "http://www.test.com", nil, nil)assert_equal(client.send(:error_response).code, response.code)assert_equal(Timeout::Error, response.exception.class)::HttpClient.expects(:get_iso8859).with(http_url).returns("a piece of text")get :read, :dtmf => 3assert_response :successassert_vxml "prompt", /a piece of text/ 163 164. Submitting Forms and Clicking Links A limitation in most controller and integration tests isthat they bypass forms and links in the views. To be able to submit forms you may use the FormTest Helper plugin, or alternatively Hpricot Forms.# Form Test Helper usage example:submit_form "/account/signup", :user => { :login => "Dave Thomas", :email => "[email protected]", :password => "dave", :password_confirmation => "dave"}select_link("/user/aaron").follow 164 165. rcov rcov is a Ruby library that measures code coverage oftests. It can be used to nd gaps in your test coverage. rcov will generate a report of how many percent ofeach Ruby class is covered and indicate which lines ofcode are not executed by the tests.# Installation of rcov:gem install rcovruby script/plugin install http://svn.codahale.com/rails_rcovrake test:test:rcov165 166. Heckle Heckle will mutate your code by inverting booleanexpressions and run your tests and make sure they fail Heckle helps nd missing assertions and can also ndredundancies in your code.# Installation of Heckle:gem install -y heckleheckle -t test/functional/comments_controller_test.rb CommentsController create166 167. AJAX and RJS Testing When you develop AJAX functionality you writeactions generate and return JavaScript with the RJSAPI. The best way to test AJAX functionality is with abrowser testing tool like Selenium. With the ARTS plugin you can make assertionsagainst the RJS code in your controller andintegration tests.167 168. ARTS Plugin UsageExample# In a controller test...def test_editxhr :get, :edit, :id => users(:aaron), :attribute => bioassert_response :successassert_rjs :page, dom_id(bio, :edit_link), :hideassert_rjs :replace, dom_id(bio, :div), / services(:outcall).idresponse.code.should == "200"endend173 174. ActiveRecordAssociations 174 175. Three Kinds of Relationships class A class BForeign keys Mappingclass User class Weblog One user has_one :weblogbelongs_to :userweblogs.user_id maps to zeroendendor one weblogclass Weblog class Post One weblog has_many :postsbelongs_to :weblogposts.weblog_id maps to zeroendendor more posts Any numberclass Post class Categoryof posts mapscategories_posts.post_id has_and_belongs_to_many :categories has_and_belongs_to_many :postsendendcategories_posts.category_id to any number of categories 175 176. has_onehas_one :credit_card, :dependent => :destroyhas_one :credit_card, :dependent => :nullifyhas_one :last_comment, :class_name => "Comment", :order => "posted_on"has_one :project_manager, :class_name => "Person",:conditions => "role = project_manager"has_one :attachment, :as => :attachable # Polymorphic association 176 177. belongs_toclass LineItem < ActiveRecord::Basebelongs_to :paid_order,:class_name => Order,:foreign_key => order_id,:conditions => paid_on is not nullendli = LineItem.find(1)puts li.product.nameli.product = Product.new(:name => I Go Ruby)li.saveli.build_product(:name => MacBook Pro) # Equivalent to product = Product.newli.create_product(:name => SoundsSticks II) # build_product + save 177 178. has_manyhas_many :comments, :order => "posted_on"has_many :comments, :include => :authorhas_many :people, :class_name => "Person",:conditions => "deleted = 0", :order => "name"has_many :tracks, :order => "position", :dependent => :destroyhas_many :comments, :dependent => :nullifyhas_many :tags, :as => :taggablehas_many :subscribers, :through => :subscriptions, :source => :userhas_many :subscribers, :class_name => "Person", :nder_sql =>SELECT DISTINCT people.*+FROM people p, post_subscriptions ps+WHERE ps.post_id = #{id} AND ps.person_id = p.id+ORDER BY p.rst_name178 179. Methods added byhas_manyFirm#clients (similar to Clients.nd :all, :conditions => "rm_id = #{id}")Firm#clients< "rm_id = #{id}"))Firm#clients.build (similar to Client.new("rm_id" => id))Firm#clients.create (similar to c = Client.new("rm_id" => id); c.save; c)179 180. has_many exampleblog = User.nd(1).weblogblog.posts.count # => 0blog.posts Hi, this is my rst post!)blog.posts.count # => 1blog.posts.nd(:conditions => [created_at > ?, 1.minute.ago]) = blog.posts.rst180 181. has_and_belongs_to_many# Requires a join tablecreate_table :categories_posts, :id => false dot.column :category_id, :integer, :null => falset.column :post_id, :integer, :null => falseend# Indices for performanceadd_index :categories_posts, [:category_id, :post_id]add_index :categories_posts, :post_idproduct = Product.nd_by_name MacBook Procategory = Category.nd_by_name(Laptops)product.categories.count # => 0category.products.count # => 0product.categories 1category.products.count # => 1181 182. Join Modelsclass Article < ActiveRecord::Basehas_many :readingshas_many :users, :through => :readingsendclass User < ActiveRecord::Basehas_many :readingshas_many :articles, :through => :readingsendclass Reading < ActiveRecord::Basebelongs_to :articlebelongs_to :userenduser = User.nd(1)article = Article.nd(1)Reading.create(:rating => 3,:read_at => Time.now,:article => article,:user => user)article.users.first == user182 183. Join Model with Conditionsclass Article < ActiveRecord::Basehas_many :happy_users, :through => :readings,:source => :user, :conditions => readings.rating >= 4endarticle = Article.find(1)article.happy_users183 184. Extending Associationsclass User < ActiveRecord::Basehas_many :articles, :through => :readings dodef rated_at_or_above(rating)nd :all, :conditions => [rating >= ?, rating]endendenduser = User.find(1)good_articles = user.articles.rated_at_or_above(4) 184 185. PolymorphicAssociationscreate_table :images, :force => true do |t|t.column :comment, :stringt.column :file_path, :stringt.column :has_image_id, :integert.column :has_image_type, :stringendclass Image < ActiveRecord::Basebelongs_to :has_image, :polymorphic => trueendclass User < ActiveRecord::Basehas_one :image, :as => :has_imageendclass Post < ActiveRecord::Basehas_one :image, :as => :has_imageend185 186. Single Table Inheritance: TableDenitioncreate_table :people, :force => true do |t|t.column :type, :string# common attributest.column :name, :stringt.column :email, :string# attributes for type=Customert.column :balance, :decimal, :precision => 10, :scale => 2# attributes for type=Employeet.column :reports_to, :integert.column :dept, :integer# attributes for type=Manager# - none -end 186 187. Single Table Inheritance: ClassHierarchyclass Person < ActiveRecord::Baseendclass Customer < Personendclass Employee < Personbelongs_to :boss, :class_name =>"Employee", :foreign_key => :reports_toendclass Manager < Employeeend187 188. Single Table Inheritance: Usagewilma = Manager.create(:name => Wilma Flint, :email =>"[email protected]",:dept => 23)Customer.create(:name => Bert Public, :email => "[email protected]",:balance => 12.45)barney = Employee.new(:name => Barney Rub, :email => "[email protected]",:dept => 23)barney.boss = wilmabarney.save!manager = Person.find_by_name("Wilma Flint")puts manager.class #=> Managerputs manager.email #=> [email protected] manager.dept #=> 23customer = Person.find_by_name("Bert Public")puts customer.class #=> Customerputs customer.email #=> [email protected] customer.balance #=> 12.45188 189. Acts As Listclass Parent < ActiveRecord::Basehas_many :children, :order => :positionendclass Child < ActiveRecord::Basebelongs_to :parentacts_as_list :scope => parentend189 190. Acts As List: Usageparent = Parent.new%w{ One Two Three Four}.each do |name| parent.children.create(:name => name)endparent.savedef display_children(parent) puts parent.children(true).map {|child| child.name }.join(", ")enddisplay_children(parent) #=> One, Two, Three, Fourputs parent.children[0].first? #=> truetwo = parent.children[1]puts two.lower_item.name #=> Threeputs two.higher_item.name #=> Oneparent.children[0].move_lowerdisplay_children(parent) #=> Two, One, Three, Fourparent.children[2].move_to_topdisplay_children(parent) #=> Three, Two, One, Fourparent.children[2].destroydisplay_children(parent) #=> Three, Two, Four 190 191. Acts As Treecreate_table :categories, :force => true do |t|t.column :name, :stringt.column :parent_id, :integerendclass Category < ActiveRecord::Baseacts_as_tree :order =>"name"end191 192. Acts As Tree: Usageroot = Category.create(:name =>"Books")fiction = root.children.create(:name =>"Fiction")non_fiction = root.children.create(:name =>"NonFiction")non_fiction.children.create(:name =>"Computers")non_fiction.children.create(:name =>"Science")non_fiction.children.create(:name =>"ArtHistory")fiction.children.create(:name =>"Mystery")fiction.children.create(:name =>"Romance")fiction.children.create(:name =>"ScienceFiction")display_children(root) # Fiction, Non Fictionsub_category = root.children.firstputs sub_category.children.size #=> 3display_children(sub_category) #=> Mystery, Romance, Science Fictionnon_fiction = root.children.find(:first, :conditions => "name = Non Fiction")display_children(non_fiction) #=> Art History, Computers, Scienceputs non_fiction.parent.name #=> Books192 193. Eager Loading: From N+1 to 1 Query# Joins posts, authors, comments# in a single select@posts = Post.find(:all,:conditions => "posts.title like %ruby%",:include => [:author, :comments]): Comments:193 194. Counter Cachecreate_table :posts do...t.column comments_count, :integerendclass Post < ActiveRecord::Basehas_many :commentsendclass Comment < ActiveRecord::Basebelongs_to :post, :counter_cache => trueend 194 195. Association Callbacksclass Project# Possible callbacks: :after_add, :before_add, :after_remove, :before_removehas_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]def evaluate_velocity(developer)...end end 195 196. ActiveRecord Validations 196 197. Validation Validations are rules in your model objects to helpprotect the integrity of your data Validation is invoked by the save method. Savereturns true if validations pass and false otherwise. If you invoke save! then a RecordInvalid exception israised if the object is not valid Use save(false) if you need to turn off validation197 198. Validation Callback Methodsclass Person < ActiveRecord::Basedef validate puts validate invokedenddef validate_on_createputs validate_on_create invokedenddef validate_on_updateputs validate_on_update invokedendendpeter = Person.create(:name => Peter) # => validate, validate_on_create invokedpeter.last_name = Forsbergpeter.save # => validate_on_update invoked 198 199. Validation Errorsclass Person < ActiveRecord::Basedef validate if Person.find_by_name(name) errors.add(:name, is already being used) endif name.blank? and email.blank?errors.add_to_base(You must specify name or email)endendpeter = Person.create(:name => Peter) # => validate, validate_on_create invokedpeter.valid? # => truepeter.errors # => []peter2 = Person.create(:name => Peter)peter2.valid? # => falsepeter2.errors.on(:name) # => is already being usedpeter2.errors.full_messages # => [Name is already being used] 199 200. Validation Macros validates_acceptance_of validate_associated validates_confirmation_of validates_each validates_exclusion_of validates_format_of validates_inclusion_of validates_length_of validates_numericality_of validates_presence_of validates_size_of validates_uniqueness_of 200 201. Validation Macros:Usageclass User < ActiveRecord::Base validates_presence_of :name, :email, :password validates_format_of :name,:with => /^w+$/,:message => may only contain word characters validates_uniqueness_of :name,:message => is already in use validates_length_of :password,:within => 4..40 validates_confirmation_of :password validates_inclusion_of :role, :in => %w(super admin user), :message => must be super, admin, or user, :allow_nil => true validates_presence_of :customer_id,:if => Proc.new { |u| %w(admin user).include?(u.role) } validates_numericality_of :weight,:only_integer => true,:allow_nil => trueend201 202. ActiveRecord Callbacks 202 203. Callback Sequence createupdate destroybefore_validation before_validationbefore_validation_on_create before_validation_on_updateafter_validationafter_validationbefore_destroyafter_validation_on_createafter_validation_on_updatebefore_save before_savebefore_create before_updatecreate operationupdate operationdestroy operationafter_createafter_updateafter_destroyafter_saveafter_save203 204. Three Common Ways toDene Callbacks # 1. Defining a method def before_save # encrypt password here end # 2. Referring to a method via a symbol before_save :encrypt_password # 3. With a Proc object before_save Proc.new { |object| ... } 204 205. before_save Exampleclass User < ActiveRecord::Basebefore_save :encrypt_passwordprivatedef encrypt_passwordreturn if password.blank?if new_record? self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--")endself.crypted_password = encrypt(password)endend205 206. after_save Exampleclass Comment < ActiveRecord::Basebelongs_to :user def after_save user.ferret_update enddef after_destroyuser.ferret_updateendend 206 207. Observersclass AuditObserver < ActiveRecord::Observerobserve Order, Payment, Refunddef after_save(model)model.logger.info("Audit: #{model.class.name}#{model.id} created")endend# List your observers in cong/environment.rbconfig.active_record.observers = :order_observer, :audit_observer207 208. after_nd andafter_initialize: must be methods def after_find end def after_initialize end208 209. ActionViewForms 209 210. How Forms WorkForm helpers => HTML => params => ActiveRecord validate + save update, :id => @user} do %> params = {:user => {:email => [email protected]}}@user = User.find(params[:id])@user.update_attributes(params[:user])210 211. form_for - Wrapping Model Objects {:action => save},:html => {:class => admin_form, :multipart => true} do |f| -%> 211 212. Forms with MultipleObjects: elds_forProduct 212 213. Processing Multiple Object FormSubmissiondef create@company = Company.new(params[:company])@product = Product.new(params[:product])Company.transaction do @company.save! @product.save! redirect_to :action => :show, :id => @companyendrescue ActiveRecord::RecordInvalid => [email protected]? # Force validation as it may not have been validatedrender :action => :newend 213 214. form_helper.rbfields_for :permission, @person.permission do |fields| ...text_field("post", "title", "size" => 20)password_fieldhidden_fieldfile_fieldtext_area("post", "body", "cols" => 20, "rows" => 40)check_box("post", "validated") # => 0/1 booleansradio_button("post", "category", "rails") 214 215. Select Boxes "name") form.collection_select(:name, @users, :id, :name)%> 215 216. form_options_helper.rbselect("post", "category",Post::CATEGORIES, {:include_blank => true})select("post", "person_id",Person.find(:all).collect {|p| [ p.name, p.id ] })select_tag "service[users][]",options_for_select(User.find(:all, :order => name).map { |u| [u.name, u.id] }, @service.users.map(&:id)),{:multiple => "multiple", :size => 10, :id => users}collection_select(user, role_ids, @roles, :id, :name,{}, :multiple => true)time_zone_selectcountry_selectoption_groups_from_collection_for_select216 217. date_helper.rbdistance_of_time_in_words(from_time, to_time)time_ago_in_words(from_time)date_select("post", "written_on",:start_year => 1995, :use_month_numbers => true,:discard_day => true, :include_blank => true)datetime_select("post", "written_on") 217 218. Custom Form Builders class TaggedBuilder < ActionView::Helpers::FormBuilder #

# Description
# #

def self.create_tagged_field(method_name)define_method(method_name) do |label, *args| @template.content_tag("p", @template.content_tag("label" ,label.to_s.humanize,:for => "#{@object_name}_#{label}") +"
" + super)end end field_helpers.each do |name| create_tagged_field(name) end end ActionView::Helpers::Base.default_form_builder = TaggedBuilder218 219. form_tag_helper.rb - Working with Non- model Fields true) do %> ... ... 219 220. File Uploadclass Fax < ActiveRecord::Basedef fax_file=(fax_file_field)@fax_file_field = fax_file_fieldreturn if fax_file_field.blank? or fax_file_field.original_filename.blank?self.fax_file_name =File.basename(fax_file_field.original_filename) if !fax_file_field.original_filename.blank?self.fax_file_type =fax_file_field.content_type.chomp if !fax_file_field.content_type.blank?enddef after_savewrite_fax_fileenddef after_destroyFile.delete(self.fax_file_path) if File.exists?(self.fax_file_path)enddef write_fax_filereturn if @fax_file_field.blank? or @fax_file_field.original_filename.blank?FileUtils.mkdir_p(File.dirname(self.fax_file_path)) if !File.exists?(File.dirname(self.fax_file_path))if @fax_file_field.instance_of?(Tempfile)FileUtils.copy(@fax_file_field.local_path, self.fax_file_path)elseFile.open(self.fax_file_path, "wb") { |f| f.write(@fax_file_field.read) }end@fax_file_field = nilendend220 221. There is a Better Way: attachment_fuclass User < ActiveRecord::Basehas_attachment :content_type => :image, :storage => :file_system, :max_size => 500.kilobytes, :resize_to => 320x200>, :thumbnails => { :thumb => 100x100> }validates_as_attachmentend 221 222. activerecord_helper.rb error_message_on "post", "title" error_messages_for post form("post") # => generate whole form, uses Post.content_columns 222 223. Styling Error MessagesCustomize the CSS styles #errorExplanation and .fieldWithErrorsActionView::Base.field_with_error_proc =Proc.new do |html_tag, instance| "

#{html_tag}

"end223 224. Filters224 225. Filters Typically used to implement authentication andauthorization. Can also be used for logging,compression, or just code reuse between actions There are before, after, and around lters before_lter :authenticate, :except => [:rss] after_lter :log_access, :only => [:rss] If a before lter returns false then the request isaborted225 226. Around Filtersclass BlogController < ApplicationController around_filter :time_an_actionenddef time_an_actionstarted = Time.nowyieldelapsed = Time.now - startedlogger.info("#{action_name} took #{elapsed} seconds")end226 227. Filter Blocks and Classesaround_filter do |controller, action|started = Time.nowaction.callelapsed = Time.now - startedendclass TimingFilterdef filter(controller)started = Time.nowyieldelapsed = Time.now - startedendendaround_filter TimingFilter.new227 228. Filter Inheritance skip_before_lter :authorize, :only => [:an_action] skip_lter :logging228 229. Vericationverify :only => :post_comment,:session => :user_id,:add_flash => { :note => "You must log in to comment"},:redirect_to => :indexverify :method => :post,:only => [:destroy, :create, :update],:redirect_to => {:action => :list}229 230. Caching230 231. Caching Rails provides three types of caching: page, action,and fragment caching Page caching creates whole html pages under publicthat get served by the web server without Rails Action caching caches only the output of an action.Filters (i.e. authentication) still get run You can use Memcached with plugins such asCached Model and Cache Fu to cache some or all ofyour database queries 231 232. Cache Declarationsand Conguration caches_page :public_content expire_page :action => public_content caches_action :premium_content expire_action :action => premium_content, :id => 2 cong.action_controller.perform_caching = true 232 233. Cache Storage Can store fragments in le system, DRb, memcached If you have multiple servers and store in the lesystem you need to setup a shared network drive Page caches are always kept as HTML les under thepublic directory and needs sharing across servers aswell 233 234. Cache Sweepersclass ArticleSweeper < ActionController::Caching::Sweeper observe Articledef after_create(article) expire_public_pageenddef after_update(article) expire_article_page(article.id)enddef after_destroy(article) expire_public_page expire_article_page(article.id)end private def expire_public_pageexpire_page(:controller => "content", :action => public_content) end def expire_article_page(article_id)expire_action(:controller => "content", :action => "premium_content", :id => article_id) endend 234 235. Cache SweeperDeclarationclass ContentController < ApplicationControllercache_sweeper :article_sweeper, :only =>[ :create_article, :update_article, :delete_article ]end235 236. Fragment Caching Fragment caching is used for pages where only certain parts(fragments) should be cached whereas other parts should bedynamic Parts of the page that should be cached are included in a ... block. The cache method can take a hash of options to identifythe fragment, i.e. list, :part =>articles) %> You expire cached fragments with an invocation such asexpire_fragment(:action => list, :part => articles)236 237. Fragment Cache Storage Fragment caches can be stored in les, in memory(for single server), on a DRb server, or in memcached You congure storage with the parameterActionController::Base.fragment_cache_store incong/environement.rb See fragment_store_setting_test.rb and caching.rb inthe Rails sources for more details about cache storageand caching in general. 237 238. AJAX 238 239. AJAX - Introduction AJAX stands for Asynchronous JavaScript and XML AJAX uses an XMLHttpRequest object that doesHTTP requests in the background. This avoids fullpage reloads and instead update parts of the page.This makes the application more responsive andinteractive. Rails ships with two AJAX JavaScript libraries:Prototype and Scriptaculous. Prototype makesremote requests and interacts with the DOM.Scriptaculous uses Prototype to generate visualeffects, auto completion, drag-and-drop etc. 239 240. What Can we Use AJAX For? Post something in the background and add it to a liston the page In-Place form editing Autocompletion of a text eld Drag-and-drop sortable lists Live searching240 241. ActionView::Helpers::PrototypeHelperlink_to_remote(name, options = {}, html_option = {})periodically_call_remote(options = {})form_remote_tag(options = {}, &block)remote_form_for(object_name, *args, &proc)submit_to_remote(name, value, options = {})remote_function(options)observe_field(field_id, options = {})observe_form(form_id, options = {}) 241 242. ActionView::Helpers::ScriptaculousHelpervisual_effect(name, element_id = false, js_options = {})sortable_element(element_id, options = {})draggable_element(element_id, options = {})drop_receiving_element(element_id, options = {}) 242 243. ActionView::Helpers::JavaScriptMacrosHelper# Note: this helper module will be moved out of Rails with 2.0 and# end up in a pluginin_place_editor(field_id, options = {})in_place_editor_field(object, method, tag_options = {},in_place_editor_options = {})auto_complete_field(field_id, options = {})auto_complete_result(entries, field, phrase = nil)text_field_with_auto_complete(object, method,tag_options = {}, completion_options = {}) 243 244. AJAX Links and Formslink_to_remote(Destroy, :url => {:action => destroy, :id => item},:confirm => Are you sure?link_to_function "Cancel", "$(create_form).hide();"link_to_function Cancel do |page|page[object.dom_id(rate_link)].showpage[object.dom_id(rate)].hideend {:action => update} do %> form, :locals => {:mode => edit} %> 244 245. RJS RJS is a Rails Ruby API for generating JavaScriptcode that is sent back to the browser and executedthere. RJS can be used inline in the action by passingthe :update argument to the render command.Alternatively you can use an RJS template le withthe ending .rjs for your action. RJS is especially useful when you need to updateseveral parts of the page. 245 246. Example: Posting a Comment: The View# In your .rhtml view: {:controller => "comments", :action => "create", :id => user}, :html => { :id => "comment_form"}, :before => "$(spinner).show()", :complete => "$(spinner).hide()" do %> 80 %>
# The form tag generated by form_remote_tag: 246 247. Example: Posting a Comment: The Controller Actiondef create @user = User.find(params[:id]) @comment = @user.comments.build(params[:comment]) @comment.creation_user_id = current_user.id if @comment.save render :update do |page|page.insert_html :bottom, comment_list, :partial => commentpage.visual_effect :highlight, @comment.dom_idpage[comment_form].reset end else render :update do |page|page.alert "Could not add comment for the following reasons:n" [email protected]_messages.map{|m| "* #{m}"}.join("n") +"nPlease change your input and submit the form again." end end end247 248. Example: Deleting a Comment# In your .rhtml view:

Hey, nice photo!

{:controller => "comments", :action => destroy, :id=> comment}, :confirm => "Are you sureyou want to delete your comment?", :update => comment.dom_id%>

# The following HTML and JavaScript is generated by link_to_remote:Delete# The action in the controller. The innerHTML of the comment div is replaced with# nothing and thus the comment disappears from the page.def destroy@comment = Comment.find(params[:id])[email protected] :text => end 248 249. Options for Remote Links and Forms# Callbacks::before - before request is initiated and before request object is created:after - request objects open method has not been called yet:loading - request has not been sent yet:loaded - request has been initiated:interactive - response is being received:success - response is ready and in 200 range:failure - response is ready and is not in 200 range:complete - response is ready# Other options:submit - id of a form to submit, can also be just a div with form elements:confirm - JavaScript confirm dialog before request:condition - JavaScript expression that must be true for request to happen249 250. Server Responses toAjax Requests nothing, just HTTP headers An HTML snippet to be injected into the page Structured data (JSON, XML, YAML, CSV etc.) tobe processed with JavaScript JavaScript code to be executed by the browser.Typically generated with RJS. 250 251. Options for Updatingthe Page If the action returns HTML you can use the :updateoptions which takes a DOM id where the HTMLshould be inserted. You can specify different DOMids for success and failure: :update => {:success =>list, :failure => error} In conjunction with the :update options you canspecify the :position option which says where theHTML should be inserted. Possible valuesare: :before, :top, :bottom, :after 251 252. Prototype Basics $A(document.getElementsByTagName(a)).rst() $H({ren:happy, stimpy:joy}).keys() $(some_id).hide()|show() # instead ofdocument.getElementById(some_id) $F(login) # The value of eld input login $$(div#footer).invoke(hide) # CSS selector $$(a).each( function(element) { element.hide() })252 253. Example: An AJAXShopping Cart# In index.rhtml: { :action => :add_to_cart, :id => product } do %># The action:def add_to_cartproduct = Product.find(params[:id])@current_item = @cart.add_product(product)redirect_to_index unless request.xhr?end# The RJS template add_to_cart.rjs:page.select("div#notice").each { |div| div.hide }page.replace_html("cart", :partial => "cart", :object => @cart)page[:cart].visual_effect :blind_down if @cart.total_items == 1page[:current_item].visual_effect :highlight,:startcolor => "#88ff88",:endcolor => "#114411" 253 254. RJS Methods# Position argument is one of :before, :top, :bottom, :afterpage.insert_html :bottom todo_list, #{todo.name}page.replace_html flash_notice, Todo added: #{todo_name}page.replace flash_notice, :partial => flash, :object => todopage[:flash_notice].remove|show|hide|toggle # page[:flash_notice] $(flash_notice)page.alert The form contains the following errors: #{errors.join(, )}page.redirect_to :controller => blog, :action => listpage.assign cur_todo, todo.id # Assign a JavaScript variablepage.call show_todo, todo.id # Invoke a JavaScript methodpage search_form}) %>... {:action => search_count}, :frequency => 3, :condition => (@model_name == Contact ? "!$(search_form).submitting && !contact_search_form_empty()" : "!$(search_form).submitting && !outlet_search_form_empty()"), :with => "search_form", :loading => "$(spinner).show();", :complete => "$(spinner).hide();") %> { :action => toggle_column, :item_count => item_count }, :with => "column_name= + value") %> 255 256. Example Response to Form Change# This is a search form where we want to display a preview of the number# of search hitsdef search_countquery = params[:search_form][/^q=(.*)/, 1]if form_has_errors? render :update do |page|page.alert(@form_errors.join("n"))end and returnend...render :update do |page|page["search_count_preview"].showpage["search_count_preview"].replace_html :partial => /mcm/search/search_count_preview page.visual_effect :highlight, "search_count_preview"if @search_count > 0page.mcm.set_color("search_count_preview", "green")elsepage.mcm.set_color("search_count_preview", "red")endendend 256 257. Options for Observers:url - url_for style options for URL to submit to:function- JavaScript function to invoke instead of remote call:frequency - seconds between updates, if not set :on => change is used:update- dom id of element to update with response:with- parameter to submit, defaults to value:on- event to observe: changed, click, blur, focus... 257 258. Dashed DOM ID Plugin When we write AJAX applications we rely heavily on DOM idsfor referencing different parts of a page. The plugin provides a dom_id helper and method forActiveRecord objects so that their DOM ids can be generatedconsistently across your application. person.dom_id(name).split(/-/) # => [person, 3, name]258 259. The Spinner Iconmodule ApplicationHelperdef spinner_icon(id)%Q{}enddef spinner_dom_id(id)dom_id(:spinner, id)endend259 260. Drag and Drop Example: The Request Side# This example is about dragging books in list to a shopping cart in the menu bar.# In index.rhtml...book info here... true) %># In application.rhtml

"cart/cart" %>

{ :controller => "cart", :action => "add" }) %> 260 261. Drag and Drop Example: The Response# The actiondef addparams[:id].gsub!(/book_/, "")@book = Book.find(params[:id])if request.xhr?@item = @cart.add(params[:id])flash.now[:cart_notice] = "Added #{@item.book.title}"render :action => "add_with_ajax"elsif request.post?@item = @cart.add(params[:id])flash[:cart_notice] = "Added #{@item.book.title}"redirect_to :controller => "catalog"elserenderendend# add_with_ajax.rjs:page.replace_html "shopping_cart", :partial => "cart"page.visual_effect :highlight, "cart_item_#{@item.book.id}", :duration => 3page.visual_effect :fade, cart_notice, :duration => 3261 262. Autocompletion Auto completion of text elds can be done with theauto_complete_eld tag. You can do fast client side JavaScript auto completionby using the AutoCompleter.Local Scriptaculousclass. This is described in the Rails Recipes book. 262 263. Autocompletion:Example# In text field view:

{:action=>autocomplete_favorite_language}, :tokens => ,, :frequency => 0.5, :min_chars => 3 %># The actiondef autocomplete_favorite_language re = Regexp.new("^#{params[:user][:favorite_language]}", "i") @languages= LANGUAGES.find_all do |l|l.match re end render :layout=>falseend# The response view in autocomplete_favorite_language.rhtml

263 264. In-Place-Edit# In the view

{:action => "set_user_email", :id => @user} %># In the controllerclass UsersController < ApplicationControllerin_place_edit_for :user, :email...end# WARNINGS# 1) in_place_edit_for does *not* use validation# 2) in_place_editor quotes the value edited. TODO: details on this# Options:# :rows, :cols, :cancel_text, :save_text, :loading_text, :external_control,# :load_text_url264 265. Global AJAX Hooks# In public/javascripts/application.js. Will show the spinner whenever# an AJAX request is in process.Ajax.Responders.register({onCreate: function(){ $(spinner).show();},onComplete: function() { if(Ajax.activeRequestCount == 0) $(spinner).hide();}}); 265 266. Degradability for When There is noJavaScriptform_remote_tag will by default fall and submit to the AJAX URL. To submit to adifferent URL, you can specify :html => {:action => {:action => some_action}}You can get link_to_remote to make a normal GET request if there is noJavaScript with the :href HTML option.In your actions you can give different responses depending on if the request isan AJAX request or not (using request.xhr?).266 267. RJS Reusemodule ApplicationHelperdef replace_article(article)update_page do |page| page[:article].replace partial => :article, :locals => {:article => article}endendenddef update@article = Article.nd(:rst)render :update do |page| page users, :action => list map.home , :controller => home link_to Home, home_url 271 272. Defaults and requirementsmap.connect "blog/:year/:month/:day",:controller => "blog",:action => "show_date",:requirements => { :year => /(19|20)dd/,:month => /[01]?d/,:day => /[0-3]?d/},:day => nil,:month => nil 272 273. splat paramsmap.connect *anything, :controller => blog, :action => unknown_requestmap.connect download/*path, :controller => le :action => download273 274. URL Generation ActionController::Base#url_for generates a URLfrom the params hash link_to and redirect_to use url_for 274 275. Routes in the console rs = ActionController::Routing::Routes puts rs.routes rs.recognize_path /store/add_to_cart/1 rs.generate :controller => store, :id => 123275 276. Testing Routesdef test_movie_route_properly_splitsopts = {:controller => "plugin", :action => "checkout", :id => "2"}assert_routing "plugin/checkout/2", opts end def test_route_has_optionsopts = {:controller => "plugin", :action => "show", :id => "12"}assert_recognizes opts, "/plugins/show/12" end276 277. REST 277 278. REST - The Theory REST is an alternative to SOAP and is a way to adda web service API to your application. Representional State Transfer (REST) is anarchitecture for hypermedia system. The state of a system is divided into resources thatare addressable with hyperlinks. All resources share auniform interface with well dened operations.Connections between client and server are stateless. REST is designed to support scalability anddecoupling. 278 279. Rails Implementation of REST: CRUD Resources are typically ActiveRecord models andeach model has a controller with seven actions: index,create, new, show, update, edit, destroy We are constrained to four types of operations:Create, Read, Update, and Delete (CRUD) The four operations correspond to the HTTP verbsGET, POST, PUT, DELETE In REST we strive to have associations be joinmodels so that they can be exposed as resources. 279 280. REST and Rails building Blocks Naming conventions for controllers and actions A set of URLs for the CRUD operations. URLs areresource IDs and not verbs. A set of named routes for the CRUD URLs (frommap.resources) Using the the HTTP Accept header via the respond_tomethod and ActiveRecord::Base#to_xml to render aresponse. The ActiveResource Ruby client for consuming RESTservices. Modeled after ActiveRecord.280 281. map.resources :articlesMethod URLActionHelper GET/articles index articles_url POST /articles createarticles_url GET /articles/new new new_article_url GET /articles/1 showarticle_url(:id => 1) PUT /articles/1update article_url(:id => 1) GET /articles/1;editedit edit_article_url(:id => 1)DELETE /articles/1destroyarticle_url(:id => 1) 281 282. REST Gives you NamedRoutes for Free# Named route helpers:article_urlarticles_urlnew_article_urledit_articles_url# Oldlink_to :controller => articles,:action => destroy,:post => :true# Newlink_to articles_url(@article), :method => :delete 282 283. The Accept Header and Extensions# The respond_to method will use the HTTP Accept header or# any suffix in the URL. So if the URL is /people/1.xml you# will get XML back:# GET /posts/1# GET /posts/1.xmldef show@post = Post.find(params[:id])respond_to do |format|format.html # show.html.erbformat.xml { render :xml => @post }endend283 284. Custom REST Actions# Adding a new recent action for a collection# GET /articles;recent, recent_articles_urlmap.resources :articles, :collection => { :recent => :get }# Adding an action for an individual resource# PUT /articles/1;release release_article_url(:id => 1)map.resources :articles, :member => { :release => :put }284 285. Nested Resources# Nested resources are used for one-to-many relationships. Suppose we have# Post class with has_many :comments, and a Comment class with belongs_to :post.# The posts controller will be the parent of the comments controller. The# comments controller needs the post model of the parent.map.resources :posts do |post|post.resources :commentsend# Actions need to be rewritten to use the parent post objectdef index# Old retrieval# @comments = Comment.find(:all)@post = Post.find(params[:post_id])@comments = @post.comments.find(:all)end# Links in the views need to be rewritten to include the# parent post: 285 286. Scaffolding# In Rails 1.2.Xscript/generate scaffold_resource post title:string body:text published:boolean# In Rails 2.0 (Edge Rails) scaffolding is restful by default:script/generate scaffold post title:string body:text published:boolean286 287. Consuming REST# You can any HTTP client to consume a REST service, such as curl or wget on the# command line, or Net::HTTP in your Ruby scripts.# Using the ActiveRecord client:# You need to to get activeresource from Edge Rails for this to work, i.e.# do rake rails:freeze:gems, svn co http://svn.rubyonrails.org/rails/trunk# and cp -r activeresource vendor/railsclass Post < ActiveResource::Base self.site = http://localhost:3000endpost = Post.find(1)post.inspectpost.body = new bodypost.save 287 288. Authentication The plugin restful_authentication provides basicHTTP authentication. It sets up a login_requiredbefore lter to all your actions that needauthentication. 288 289. ActionMailer 289 290. Email Conguration# In environment.rb or in config/environments/*. Defaults to :smtpconfig.action_mailer.delivery_method = :test|:smtp|:sendmail# Authentication is one of :plain, :login, and :cram_md5ActionMailer::Base.server_settings = {:address => "mail.messagingengine.com",:port => 25,:domain => "mail.messagingengine.com",:authentication => :login,:user_name => "[email protected]",:password => "..."}config.action_mailer.perform_deliveries = true | falseconfig.action_mailer.raise_delivery_errors = true | falseconfig.action_mailer.default_charset = iso-8859-1 290 291. Creating a Mailer# Creates app/models/statistics_mailer.rbscript/generate mailer StatisticsMailerclass StatisticsMailer < ActionMailer::BaseMONTHLY_SUBJECT = "Monthly call statistics"FROM_EMAIL = [email protected]_EMAILS = [email protected] monthly(user, stats, sent_on = Time.now)@subject= MONTHLY_SUBJECT@body = {:user => user,:stats => stats}@recipients = user.email@from = FROM_EMAIL@sent_on= sent_on@headers= {}@bcc = BCC_EMAILS# content_type text/html # If we wanted to send HTML emailendend 291 292. The Mailer Template# In app/views/statistics_mailer/monthly.rhtmlDear ,here is the statistics for for .*** Service Number of calls: Number of minutes: Hour breakdown:HTML: Excel: List of all calls: RegardsPeter Marklund 292 293. Sending EmailStatisticsMailer.deliver_monthly(user, stats)# Or, you can do:email = StatisticsMailer.create_monthly(user, stats)# Set any properties on the email hereemail.set_content_type(text/html)StatisticsMailer.deliver(email) 293 294. Multipart Emails# If we have several templates with the naming convention# mail_name.content.type.rhtml then each such template# will be added as a part in the email.monthly.text.plain.rhtmlmonthly.text.html.rhtmlmonthly.text.xml.rhtml# Alternatively we can use the part method within the# mailer delivery methoddef signup_notification(recipient)...part :content_type => "text/html",:body => body here...part "text/plain" do |p|p.body = body here...p.transfer_encoding = "base64"endend294 295. Attachmentsclass ApplicationMailer < ActionMailer::Basedef signup_notification(recipient)recipientsrecipient.email_address_with_namesubject "New account information"from"[email protected]" attachment :content_type => "image/jpeg", :body => File.read("an-image.jpg")attachment "application/pdf" do |a|a.body = generate_your_pdf_here()endendend295 296. Receiving Emailclass BulkReceiver < ActionMailer::Basedef receive(email)return unless email.content_type == "multipart/report"bounce = BouncedDelivery.from_email(email)msg= Delivery.find_by_message_id(bounce.original_message_id)msg.status_code = bounce.status_codemsg.status = bounced if bounce.status =~ /^bounced/msg.save!endendclass ActionMailer::Base...def receive(raw_email)logger.info "Received mail:n #{raw_email}" unless logger.nil?mail = TMail::Mail.parse(raw_email)mail.base64_decodenew.receive(mail)end# Configure your mail server to pipe incoming email to the program# /path/to/app/script/runner BulkReceiver.receive(STDIN.read) 296 297. Unit Testing Mailersdef setupActionMailer::Base.delivery_method = :testActionMailer::Base.perform_deliveries = trueActionMailer::Base.deliveries = []@expected = TMail::[email protected]_content_type "text", "plain", { "charset" => CHARSET }@expected.mime_version = 1.0enddef test_monthlyuser = users(:gordon)@expected.subject = StatisticsMailer::[email protected] = StatisticsMailer::FROM_EMAIL@expected_admin_url = "#{Voxway::TAX_URL}/admin/statistics"@expected.body= ERB.new(read_fixture(monthly).join).result(binding)@expected.date= [email protected] = [email protected] = StatisticsMailer::BCC_EMAILSstats = [services(:smhi), services(:smhi2)].inject({}) { |stats, service|stats[service.id] = service.stats; stats }assert_equal @expected.encoded.strip,StatisticsMailer.create_monthly(user, stats, @expected.date).encoded.stripend 297 298. Plugins298 299. Plugins Introduction Plugins is the mechanism by which Rails applicationsshare code with each other Often plugins add application specic functionality orextend Rails in some way There is an effort by Marcel Molina, Chad Fowler,and others to make plugins be gems in Rails 2.0 299 300. Finding Plugins agilewebdevelopment.com/plugins - has a searchabledatabase containing pretty much all plugins Rick Olsons plugins at svn.techno-weenie.net/project/plugins are known to be high quality Rails core plugins are at: dev.rubyonrails.org/svn/rails/plugins300 301. Creating Plugins Generate your plugin with script/generate plugin The les install.rb and uninstall.rb are hooks that runon install/uninstall The init.rb le should load the plugins modules andclasses that are kept under lib. In init.rb you haveaccess to special variables cong, directory,loaded_plugins, and name. Rake tasks are under tasks and tests under test. Youcan run the tests with rake in the plugin root.301 302. Controllers, Models,and Views# Note: deprecated in Edge Rails: use view_paths= and append/prepend_view_pathsclass PluginController < ActionController::Baseself.template_root = File.join(File.dirname(__FILE__), .., views)endconfig.autoload_paths a Hash object 306 307. Enumerationsgroups = posts.group_by {|post| post.author_id}us_states = State.find(:all)state_lookup = us_states.index_by {|state| state.short_name}total_orders = Order.find(:all).sum {|order| order.value }total_orders = Order.find(:all).sum(&:value) 307 308. Stringstring = I Go Rubyputs string.at(2)pust string.from(5)puts string.to(3)puts string.first(4)puts string.last(4)puts string.starts_with?(I)puts string.ends_with?(Perl)count = Hash.new(0)string.each_char {|ch| count[ch] += 1}person.pluralizepeople.singularizefirst_name.humanize # => First Namei go ruby.titleize # => I Go Ruby 308 309. Numbers20.bytes20.megabytes20.seconds20.hours20.months20.years20.minutes.ago20.weeks.from_now20.minutes.until(2007-12-01 12:00.to_time) 309 310. Time and Datetime = Time.parse(2007-01-01 13:00)time.at_beginning_of_daytime.at_beginning_of_weektime.at_beginning_of_month310 311. UTF8"".size=> 6"".chars.size=> 3"".upcase=> """".chars.upcase.inspect=> #"".chars.upcase.to_s=> ""311 312. Rails 2.0312 313. Rails 2.0 No radical changes, mostly polishing Better REST support New breakpoint mechanism that works with Ruby1.8.5 HTTP authentication support HTTP performance and ActiveRecord caching Deprecated code is removed313 314. Whats New in EdgeRails: ryandaigle.com* RESTful URL helpercategory_article_url(@category, @article) -> url_for([@category, @article])* New database rake tasks: db:drop and db:reset* validates_numericality_of pimpedNow takes args: :greater_than, :equal_to, :less_than, :odd, :even* A more flexible to_xmluser.to_xml(:except => [:id, :created_at], :include => :posts)* Object transactions are out but there is a pluginNo more Account.transaction(from, to) do ... end with object state rollback* Code annotations: FIXME, TODO, OPTIMIZEList annotations with rake notes* Accessing custom helpersBy default ApplicationController now has helper :all* Better organization of environment.rbYou can modularize initialization code and put it under config/initializers* ActiveRecord cachingActiveRecord::Base.cache do .. end* New file extensions: .erb and .builder... and much more!314 315. Deployment 315 316. Hosting Most production Rails servers run on Unix -primarily Linux, but also FreeBSD, OpenSolaris,Mac OS X etc. You can deploy on Windows, but itsfairly unsupported and doesnt work with Capistrano. There is a multitude of hosting companies in the USspecialized in Rails hosting such as Engine Yard,RailsMachine, Slicehost, Rimuhosting. In Europethere is HostingRails.com, brightbox.co.uk etc. Virtual Private Servers (VPS), typically on Xen, arebecoming an increasingly popular hosting strategy 316 317. FastCGI FastCGI is a long running process that runs a Railsapplication and handles requests from a front-endweb server like Apache or Lighttpd When Rails came out FastCGI was therecommended deployment setup. However, it turnsout it is hard to debug and unstable in many casesand the community is moving away from it.317 318. Mongrel A light-weight web server written in Ruby Well documented, tested, stable and fast. Secure by being strict about the HTTP protocol 318 319. Typical Mongrel Architecture There is a front-end web server that receives HTTPrequests, such as Apache, Lighttpd, or Nginx. Anystatic les under the Rails public directory are serveddirectly from this web server without touching Rails. There is a reverse proxy that load balances requeststo the Mongrel servers. For Apache this ismod_proxy_balancer, for Lighttpd it is typically Pen,or Pound. There are one or more servers running multipleMongrel servers (called Mongrel clusters).319 320. Apache + Mongrel:Example Installation Install Apache 2.2 with mod_proxy_balancer Install MySQL 5 Install Ruby and RubyGems Install MySQL/Ruby driver Install Ruby gems: rake, rails, termios, capistrano,mongrel, mongrel_cluster There is the deprec gem that can automate all theseinstallation steps on Ubuntu 6.06 320 321. Mongrel Alternative:LiteSpeed LiteSpeed is a commercial web server that isoptimized for speed and is conguration compatiblewith Apache LiteSpeed has its own API for acting as a Railsapplication server so Mongrel is not needed in thepicture anymore LiteSpeed can handle load balanc